5 |
6 | Reactive communications library across Apple platforms.
7 |
8 | [](https://swift.org)
9 | [](http://twitter.com/brightdigit)
10 | 
11 | 
12 | 
13 |
14 | [](https://swiftpackageindex.com/brightdigit/SundialKit)
15 | [](https://swiftpackageindex.com/brightdigit/SundialKit)
16 |
17 |
18 | [](https://codecov.io/gh/brightdigit/SundialKit)
19 | [](https://www.codefactor.io/repository/github/brightdigit/SundialKit)
20 | [](https://codebeat.co/projects/github-com-brightdigit-SundialKit-main)
21 | [](https://codeclimate.com/github/brightdigit/SundialKit)
22 | [](https://codeclimate.com/github/brightdigit/SundialKit)
23 | [](https://codeclimate.com/github/brightdigit/SundialKit)
24 | [](https://houndci.com)
25 |
26 |
27 | 
28 |
29 | # Table of Contents
30 |
31 | * [**Introduction**](#introduction)
32 | * [**Features**](#features)
33 | * [**Installation**](#installation)
34 | * [**Usage**](#usage)
35 | * [**Listening to Networking Changes**](#listening-to-networking-changes)
36 | * [**Communication between iPhone and Apple Watch**](#communication-between-iphone-and-apple-watch)
37 | * [**Connection Status**](#connection-status)
38 | * [**Sending and Receiving Messages**](#sending-and-receiving-messages)
39 | * [**Using Messagable to Communicate**](#using-messagable-to-communicate)
40 | * [**License**](#license)
41 |
42 | # Introduction
43 |
44 | For easier use in reactive user interfaces, especially with `SwiftUI` and `Combine`, I've created a library which abstracts and maps common connectivity APIs. Particularly in my app Heartwitch, I mapped the functionality of _WatchConnectivity_ and _Network_ over to track the user's ability to connect to the Internet as well as the ability for their iPhone to connect to their Apple Watch via _WatchConnectivity_
45 |
46 | # Features
47 |
48 | Here's what's currently implemented with this library:
49 |
50 | - [x] Monitor network connectivity and quality
51 | - [x] Communicate between iPhone and Apple Watch
52 | - [x] Monitor connectivity between devices
53 | - [x] Send messages back and forth between iPhone and Apple Watch
54 | - [x] Abstract messages for easier _encoding_ and _decoding_
55 |
56 | # Installation
57 |
58 | Swift Package Manager is Apple's decentralized dependency manager to integrate libraries to your Swift projects. It is now fully integrated with Xcode 13.
59 |
60 | To integrate **SundialKit** into your project using SPM, specify it in your Package.swift file:
61 |
62 | ```swift
63 | let package = Package(
64 | ...
65 | dependencies: [
66 | .package(url: "https://github.com/brightdigit/SundialKit.git", from: "0.2.0")
67 | ],
68 | targets: [
69 | .target(
70 | name: "YourTarget",
71 | dependencies: ["SundialKit", ...]),
72 | ...
73 | ]
74 | )
75 | ```
76 |
77 | # Usage
78 |
79 | ## Listening to Networking Changes
80 |
81 | In the past `Reachability` or `AFNetworking` has been used to judge the network connectivity of a device. **SundialKit** uses the `Network` framework to listen to changes in connectivity providing all the information available.
82 |
83 | **SundialKit** provides a `NetworkObserver` which allows you to listen to a variety of publishers related to the network. This is especially useful if you are using `SwiftUI` in particular. With `SwiftUI`, you can create an `ObservableObject` which contains a `NetworkObserver`:
84 |
85 | ```swift
86 | import SwiftUI
87 | import SundialKit
88 |
89 | class NetworkConnectivityObject : ObservableObject {
90 | // our NetworkObserver
91 | let connectivityObserver = NetworkObserver()
92 |
93 | // our published property for pathStatus initially set to `.unknown`
94 | @Published var pathStatus : PathStatus = .unknown
95 |
96 | init () {
97 | // set the pathStatus changes to our published property
98 | connectivityObserver
99 | .pathStatusPublisher
100 | .receive(on: DispatchQueue.main)
101 | .assign(to: &self.$pathStatus)
102 | }
103 |
104 | // need to start listening
105 | func start () {
106 | self.connectivityObserver.start(queue: .global())
107 | }
108 | }
109 | ```
110 |
111 | There are 3 important pieces:
112 |
113 | 1. The `NetworkObserver` called `connectivityObserver`
114 | 2. On `init`, we use `Combine` to listen to the publisher and store each new `pathStatus` to our `@Published` property.
115 | 3. A `start` method which needs to be called to start listening to the `NetworkObserver`.
116 |
117 | Therefore for our `SwiftUI` `View`, we need to `start` listening `onAppear` and can use the `pathStatus` property in the `View`:
118 |
119 | ```swift
120 |
121 | struct NetworkObserverView: View {
122 | @StateObject var connectivityObject = NetworkConnectivityObject()
123 | var body: some View {
124 | // Use the `message` property to display text of the `pathStatus`
125 | Text(self.connectivityObject.pathStatus.message).onAppear{
126 | // start the NetworkObserver
127 | self.connectivityObject.start()
128 | }
129 | }
130 | }
131 | ```
132 |
133 | Besides `pathStatus`, you also have access to:
134 |
135 | * `isExpensive`
136 | * `isConstrained`
137 |
138 | ### Verify Connectivity with ``NetworkPing``
139 |
140 | In addition to utilizing `NWPathMonitor`, you can setup a periodic ping by implementing ``NetworkPing``. Here's an example which calls the _ipify_ API to verify there's an ip address:
141 |
142 | ```swift
143 | struct IpifyPing : NetworkPing {
144 | typealias StatusType = String?
145 |
146 | let session: URLSession
147 | let timeInterval: TimeInterval
148 |
149 | public func shouldPing(onStatus status: PathStatus) -> Bool {
150 | switch status {
151 | case .unknown, .unsatisfied:
152 | return false
153 | case .requiresConnection, .satisfied:
154 | return true
155 | }
156 | }
157 |
158 | static let url : URL = .init(string: "https://api.ipify.org")!
159 |
160 | func onPing(_ closure: @escaping (String?) -> Void) {
161 | session.dataTask(with: IpifyPing.url) { data, _, _ in
162 | closure(data.flatMap{String(data: $0, encoding: .utf8)})
163 | }.resume()
164 | }
165 | }
166 | ```
167 |
168 | Next, in our `ObservableObject`, we can create a ``NetworkObserver`` to use this with:
169 |
170 | ```swift
171 | @Published var nwObject = NetworkObserver(ping:
172 | // use the shared `URLSession` and check every 10.0 seconds
173 | IpifyPing(session: .shared, timeInterval: 10.0)
174 | )
175 | ```
176 |
177 | ## Communication between iPhone and Apple Watch
178 |
179 | Besides networking, **SundialKit** also provides an easier reactive interface into `WatchConnectivity`. This includes:
180 |
181 | 1. Various connection statuses like `isReachable`, `isInstalled`, etc..
182 | 2. Send messages between the iPhone and paired Apple Watch
183 | 3. Easy encoding and decoding of messages between devices into `WatchConnectivity` friendly dictionaries.
184 |
185 | 
186 |
187 | Let's first talk about how `WatchConnectivity` status works.
188 |
189 | ### Connection Status
190 |
191 | With `WatchConnectivity` there's a variety of properties which tell you the status of connection between devices. Here's a similar example to `pathStatus` using `isReachable`:
192 |
193 |
194 | ```swift
195 | import SwiftUI
196 | import SundialKit
197 |
198 | class WatchConnectivityObject : ObservableObject {
199 | // our ConnectivityObserver
200 | let connectivityObserver = ConnectivityObserver()
201 | // our published property for isReachable initially set to false
202 | @Published var isReachable : Bool = false
203 | init () {
204 | // set the isReachable changes to our published property
205 | connectivityObserver
206 | .isReachablePublisher
207 | .receive(on: DispatchQueue.main)
208 | .assign(to: &self.$isReachable)
209 | }
210 |
211 | func activate () {
212 | // activate the WatchConnectivity session
213 | try! self.connectivityObserver.activate()
214 | }
215 | }
216 | ```
217 |
218 | Again, there are 3 important pieces:
219 |
220 | 1. The `ConnectivityObserver` called `connectivityObserver`
221 | 2. On `init`, we use `Combine` to listen to the publisher and store each new `isReachable` to our `@Published` property.
222 | 3. An `activate` method which needs to be called to activate the session for `WatchConnectivity`.
223 |
224 | Therefore for our `SwiftUI` `View`, we need to `activate` the session at `onAppear` and can use the `isReachable` property in the `View`:
225 |
226 | ```swift
227 |
228 | struct WatchConnectivityView: View {
229 | @StateObject var connectivityObject = WatchConnectivityObject()
230 | var body: some View {
231 | Text(
232 | connectivityObject.isReachable ?
233 | "Reachable" : "Not Reachable"
234 | )
235 | .onAppear{
236 | self.connectivityObject.activate()
237 | }
238 | }
239 | }
240 | ```
241 |
242 | Besides `isReachable`, you also have access to:
243 |
244 | * `activationState`
245 | * `isReachable`
246 | * `isPairedAppInstalled`
247 | * `isPaired`
248 |
249 | Additionally there's also a set of publishers for sending, receiving, and replying to messages between the iPhone and paired Apple Watch.
250 |
251 | ### Sending and Receiving Messages
252 |
253 | To send and receive messages through our ``ConnectivityObserver`` we can access two properties:
254 |
255 | - ``ConnectivityObserver/messageReceivedPublisher`` - for listening to messages
256 | - ``ConnectivityObserver/sendingMessageSubject`` - for sending messages
257 |
258 | **SundialKit** uses `[String:Any]` dictionaries for sending and receiving messages, which use the typealias ``ConnectivityMessage``. Let's expand upon the previous `WatchConnectivityObject` and use those properties:
259 |
260 | ```swift
261 | class WatchConnectivityObject : ObservableObject {
262 |
263 | // our ConnectivityObserver
264 | let connectivityObserver = ConnectivityObserver()
265 |
266 | // our published property for isReachable initially set to false
267 | @Published var isReachable : Bool = false
268 |
269 | // our published property for the last message received
270 | @Published var lastReceivedMessage : String = ""
271 |
272 | init () {
273 | // set the isReachable changes to our published property
274 | connectivityObserver
275 | .isReachablePublisher
276 | .receive(on: DispatchQueue.main)
277 | .assign(to: &self.$isReachable)
278 |
279 | // set the lastReceivedMessage based on the dictionary's _message_ key
280 | connectivityObserver
281 | .messageReceivedPublisher
282 | .compactMap({ received in
283 | received.message["message"] as? String
284 | })
285 | .receive(on: DispatchQueue.main)
286 | .assign(to: &self.$lastReceivedMessage)
287 | }
288 |
289 | func activate () {
290 | // activate the WatchConnectivity session
291 | try! self.connectivityObserver.activate()
292 | }
293 |
294 | func sendMessage(_ message: String) {
295 | // create a dictionary with the message in the message key
296 | self.connectivityObserver.sendingMessageSubject.send(["message" : message])
297 | }
298 | }
299 | ```
300 |
301 | We can now create a simple SwiftUI View using our updated `WatchConnectivityObject`:
302 |
303 | ```swift
304 | struct WatchMessageDemoView: View {
305 | @StateObject var connectivityObject = WatchMessageObject()
306 | @State var message : String = ""
307 | var body: some View {
308 | VStack{
309 | Text(connectivityObject.isReachable ? "Reachable" : "Not Reachable").onAppear{
310 | self.connectivityObject.activate()
311 | }
312 | TextField("Message", text: self.$message)
313 | Button("Send") {
314 | self.connectivityObject.sendMessage(self.message)
315 | }
316 |
317 | Text("Last received message:")
318 | Text(self.connectivityObject.lastReceivedMessage)
319 | }
320 | }
321 | }
322 | ```
323 |
324 | ### Using `Messagable` to Communicate
325 |
326 | We can even abstract the ``ConnectivityMessage`` using a ``MessageDecoder``. To do this we need to create a special type which implements ``Messagable``:
327 |
328 | ```swift
329 | struct Message : Messagable {
330 | internal init(text: String) {
331 | self.text = text
332 | }
333 |
334 | static let key: String = "_message"
335 |
336 | enum Parameters : String {
337 | case text
338 | }
339 |
340 | init?(from parameters: [String : Any]?) {
341 | guard let text = parameters?[Parameters.text.rawValue] as? String else {
342 | return nil
343 | }
344 |
345 | self.text = text
346 | }
347 |
348 | func parameters() -> [String : Any] {
349 | return [
350 | Parameters.text.rawValue : self.text
351 | ]
352 | }
353 |
354 | let text : String
355 | }
356 | ```
357 |
358 | There are three requirements for implementing ``Messagable``:
359 |
360 | * ``Messagable/init(from:)`` - try to create the object based on the dictionary, return nil if it's invalid
361 | * ``Messagable/parameters()`` - return a dictionary with all the parameters need to recreate the object
362 | * ``Messagable/key`` - return a string which identifies the type and is unique to the ``MessageDecoder``
363 |
364 | Now that we have our implementation of ``Messagable``, we can use it in our `WatchConnectivityObject`:
365 |
366 | ```swift
367 | class WatchConnectivityObject : ObservableObject {
368 |
369 | // our ConnectivityObserver
370 | let connectivityObserver = ConnectivityObserver()
371 |
372 | // create a `MessageDecoder` which can decode our new `Message` type
373 | let messageDecoder = MessageDecoder(messagableTypes: [Message.self])
374 |
375 | // our published property for isReachable initially set to false
376 | @Published var isReachable : Bool = false
377 |
378 | // our published property for the last message received
379 | @Published var lastReceivedMessage : String = ""
380 |
381 | init () {
382 | // set the isReachable changes to our published property
383 | connectivityObserver
384 | .isReachablePublisher
385 | .receive(on: DispatchQueue.main)
386 | .assign(to: &self.$isReachable)
387 |
388 |
389 | connectivityObserver
390 | .messageReceivedPublisher
391 | // get the ``ConnectivityReceiveResult/message`` part of the ``ConnectivityReceiveResult``
392 | .map(\.message)
393 | // use our `messageDecoder` to call ``MessageDecoder/decode(_:)``
394 | .compactMap(self.messageDecoder.decode)
395 | // check it's our `Message`
396 | .compactMap{$0 as? Message}
397 | // get the `text` property
398 | .map(\.text)
399 | .receive(on: DispatchQueue.main)
400 | // set it to our published property
401 | .assign(to: &self.$lastReceivedMessage)
402 | }
403 |
404 | func activate () {
405 | // activate the WatchConnectivity session
406 | try! self.connectivityObserver.activate()
407 | }
408 |
409 | func sendMessage(_ message: String) {
410 | // create a dictionary using ``Messagable/message()``
411 | self.connectivityObserver.sendingMessageSubject.send(Message(text: message).message())
412 | }
413 | }
414 | ```
415 |
416 | # License
417 |
418 | This code is distributed under the MIT license. See the [LICENSE](https://github.com/brightdigit/SundialKit/LICENSE) file for more info.
419 |
--------------------------------------------------------------------------------
/Scripts/docc.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | docker rm -f sundialkit-docc || true
3 | xcodebuild docbuild -scheme SundialKit -destination 'generic/platform=iOS Simulator' -derivedDataPath DerivedData
4 | cp .htaccess DerivedData/Build/Products/Debug-iphonesimulator
5 | docker run --name sundialkit-docc -d -p 8080:80 -v "$(pwd)/DerivedData/Build/Products/Debug-iphonesimulator:/usr/local/apache2/htdocs/" --rm -it $(docker build -q .)
6 |
--------------------------------------------------------------------------------
/Scripts/gh-md-toc:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | #
4 | # Steps:
5 | #
6 | # 1. Download corresponding html file for some README.md:
7 | # curl -s $1
8 | #
9 | # 2. Discard rows where no substring 'user-content-' (github's markup):
10 | # awk '/user-content-/ { ...
11 | #
12 | # 3.1 Get last number in each row like ' ... sitemap.js.*<\/h/)+2, RLENGTH-5)
21 | #
22 | # 5. Find anchor and insert it inside "(...)":
23 | # substr($0, match($0, "href=\"[^\"]+?\" ")+6, RLENGTH-8)
24 | #
25 |
26 | gh_toc_version="0.7.0"
27 |
28 | gh_user_agent="gh-md-toc v$gh_toc_version"
29 |
30 | #
31 | # Download rendered into html README.md by its url.
32 | #
33 | #
34 | gh_toc_load() {
35 | local gh_url=$1
36 |
37 | if type curl &>/dev/null; then
38 | curl --user-agent "$gh_user_agent" -s "$gh_url"
39 | elif type wget &>/dev/null; then
40 | wget --user-agent="$gh_user_agent" -qO- "$gh_url"
41 | else
42 | echo "Please, install 'curl' or 'wget' and try again."
43 | exit 1
44 | fi
45 | }
46 |
47 | #
48 | # Converts local md file into html by GitHub
49 | #
50 | # -> curl -X POST --data '{"text": "Hello world github/linguist#1 **cool**, and #1!"}' https://api.github.com/markdown
51 | #
Hello world github/linguist#1 cool, and #1!
'"
52 | gh_toc_md2html() {
53 | local gh_file_md=$1
54 | URL=https://api.github.com/markdown/raw
55 |
56 | if [ ! -z "$GH_TOC_TOKEN" ]; then
57 | TOKEN=$GH_TOC_TOKEN
58 | else
59 | TOKEN_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/token.txt"
60 | if [ -f "$TOKEN_FILE" ]; then
61 | TOKEN="$(cat $TOKEN_FILE)"
62 | fi
63 | fi
64 | if [ ! -z "${TOKEN}" ]; then
65 | AUTHORIZATION="Authorization: token ${TOKEN}"
66 | fi
67 |
68 | # echo $URL 1>&2
69 | OUTPUT=$(curl -s \
70 | --user-agent "$gh_user_agent" \
71 | --data-binary @"$gh_file_md" \
72 | -H "Content-Type:text/plain" \
73 | -H "$AUTHORIZATION" \
74 | "$URL")
75 |
76 | if [ "$?" != "0" ]; then
77 | echo "XXNetworkErrorXX"
78 | fi
79 | if [ "$(echo "${OUTPUT}" | awk '/API rate limit exceeded/')" != "" ]; then
80 | echo "XXRateLimitXX"
81 | else
82 | echo "${OUTPUT}"
83 | fi
84 | }
85 |
86 |
87 | #
88 | # Is passed string url
89 | #
90 | gh_is_url() {
91 | case $1 in
92 | https* | http*)
93 | echo "yes";;
94 | *)
95 | echo "no";;
96 | esac
97 | }
98 |
99 | #
100 | # TOC generator
101 | #
102 | gh_toc(){
103 | local gh_src=$1
104 | local gh_src_copy=$1
105 | local gh_ttl_docs=$2
106 | local need_replace=$3
107 | local no_backup=$4
108 | local no_footer=$5
109 |
110 | if [ "$gh_src" = "" ]; then
111 | echo "Please, enter URL or local path for a README.md"
112 | exit 1
113 | fi
114 |
115 |
116 | # Show "TOC" string only if working with one document
117 | if [ "$gh_ttl_docs" = "1" ]; then
118 |
119 | echo "Table of Contents"
120 | echo "================="
121 | echo ""
122 | gh_src_copy=""
123 |
124 | fi
125 |
126 | if [ "$(gh_is_url "$gh_src")" == "yes" ]; then
127 | gh_toc_load "$gh_src" | gh_toc_grab "$gh_src_copy"
128 | if [ "${PIPESTATUS[0]}" != "0" ]; then
129 | echo "Could not load remote document."
130 | echo "Please check your url or network connectivity"
131 | exit 1
132 | fi
133 | if [ "$need_replace" = "yes" ]; then
134 | echo
135 | echo "!! '$gh_src' is not a local file"
136 | echo "!! Can't insert the TOC into it."
137 | echo
138 | fi
139 | else
140 | local rawhtml=$(gh_toc_md2html "$gh_src")
141 | if [ "$rawhtml" == "XXNetworkErrorXX" ]; then
142 | echo "Parsing local markdown file requires access to github API"
143 | echo "Please make sure curl is installed and check your network connectivity"
144 | exit 1
145 | fi
146 | if [ "$rawhtml" == "XXRateLimitXX" ]; then
147 | echo "Parsing local markdown file requires access to github API"
148 | echo "Error: You exceeded the hourly limit. See: https://developer.github.com/v3/#rate-limiting"
149 | TOKEN_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/token.txt"
150 | echo "or place GitHub auth token here: ${TOKEN_FILE}"
151 | exit 1
152 | fi
153 | local toc=`echo "$rawhtml" | gh_toc_grab "$gh_src_copy"`
154 | echo "$toc"
155 | if [ "$need_replace" = "yes" ]; then
156 | if grep -Fxq "" $gh_src && grep -Fxq "" $gh_src; then
157 | echo "Found markers"
158 | else
159 | echo "You don't have or in your file...exiting"
160 | exit 1
161 | fi
162 | local ts="<\!--ts-->"
163 | local te="<\!--te-->"
164 | local dt=`date +'%F_%H%M%S'`
165 | local ext=".orig.${dt}"
166 | local toc_path="${gh_src}.toc.${dt}"
167 | local toc_footer=""
168 | # http://fahdshariff.blogspot.ru/2012/12/sed-mutli-line-replacement-between-two.html
169 | # clear old TOC
170 | sed -i${ext} "/${ts}/,/${te}/{//!d;}" "$gh_src"
171 | # create toc file
172 | echo "${toc}" > "${toc_path}"
173 | if [ "${no_footer}" != "yes" ]; then
174 | echo -e "\n${toc_footer}\n" >> "$toc_path"
175 | fi
176 |
177 | # insert toc file
178 | if [[ "`uname`" == "Darwin" ]]; then
179 | sed -i "" "/${ts}/r ${toc_path}" "$gh_src"
180 | else
181 | sed -i "/${ts}/r ${toc_path}" "$gh_src"
182 | fi
183 | echo
184 | if [ "${no_backup}" = "yes" ]; then
185 | rm ${toc_path} ${gh_src}${ext}
186 | fi
187 | echo "!! TOC was added into: '$gh_src'"
188 | if [ -z "${no_backup}" ]; then
189 | echo "!! Origin version of the file: '${gh_src}${ext}'"
190 | echo "!! TOC added into a separate file: '${toc_path}'"
191 | fi
192 | echo
193 | fi
194 | fi
195 | }
196 |
197 | #
198 | # Grabber of the TOC from rendered html
199 | #
200 | # $1 - a source url of document.
201 | # It's need if TOC is generated for multiple documents.
202 | #
203 | gh_toc_grab() {
204 | common_awk_script='
205 | modified_href = ""
206 | split(href, chars, "")
207 | for (i=1;i <= length(href); i++) {
208 | c = chars[i]
209 | res = ""
210 | if (c == "+") {
211 | res = " "
212 | } else {
213 | if (c == "%") {
214 | res = "\\x"
215 | } else {
216 | res = c ""
217 | }
218 | }
219 | modified_href = modified_href res
220 | }
221 | print sprintf("%*s", (level-1)*3, "") "* [" text "](" gh_url modified_href ")"
222 | '
223 | if [ `uname -s` == "OS/390" ]; then
224 | grepcmd="pcregrep -o"
225 | echoargs=""
226 | awkscript='{
227 | level = substr($0, length($0), 1)
228 | text = substr($0, match($0, /a>.*<\/h/)+2, RLENGTH-5)
229 | href = substr($0, match($0, "href=\"([^\"]+)?\"")+6, RLENGTH-7)
230 | '"$common_awk_script"'
231 | }'
232 | else
233 | grepcmd="grep -Eo"
234 | echoargs="-e"
235 | awkscript='{
236 | level = substr($0, length($0), 1)
237 | text = substr($0, match($0, /a>.*<\/h/)+2, RLENGTH-5)
238 | href = substr($0, match($0, "href=\"[^\"]+?\"")+6, RLENGTH-7)
239 | '"$common_awk_script"'
240 | }'
241 | fi
242 | href_regex='href=\"[^\"]+?\"'
243 |
244 | # if closed is on the new line, then move it on the prev line
245 | # for example:
246 | # was: The command foo1
247 | #
248 | # became: The command foo1
249 | sed -e ':a' -e 'N' -e '$!ba' -e 's/\n<\/h/<\/h/g' |
250 |
251 | # find strings that corresponds to template
252 | $grepcmd '//g' | sed 's/<\/code>//g' |
256 |
257 | # remove g-emoji
258 | sed 's/]*[^<]*<\/g-emoji> //g' |
259 |
260 | # now all rows are like:
261 | # ... / placeholders"
290 | echo " $app_name - Create TOC for markdown from STDIN"
291 | echo " $app_name --help Show help"
292 | echo " $app_name --version Show version"
293 | return
294 | fi
295 |
296 | if [ "$1" = '--version' ]; then
297 | echo "$gh_toc_version"
298 | echo
299 | echo "os: `lsb_release -d | cut -f 2`"
300 | echo "kernel: `cat /proc/version`"
301 | echo "shell: `$SHELL --version`"
302 | echo
303 | for tool in curl wget grep awk sed; do
304 | printf "%-5s: " $tool
305 | echo `$tool --version | head -n 1`
306 | done
307 | return
308 | fi
309 |
310 | if [ "$1" = "-" ]; then
311 | if [ -z "$TMPDIR" ]; then
312 | TMPDIR="/tmp"
313 | elif [ -n "$TMPDIR" -a ! -d "$TMPDIR" ]; then
314 | mkdir -p "$TMPDIR"
315 | fi
316 | local gh_tmp_md
317 | if [ `uname -s` == "OS/390" ]; then
318 | local timestamp=$(date +%m%d%Y%H%M%S)
319 | gh_tmp_md="$TMPDIR/tmp.$timestamp"
320 | else
321 | gh_tmp_md=$(mktemp $TMPDIR/tmp.XXXXXX)
322 | fi
323 | while read input; do
324 | echo "$input" >> "$gh_tmp_md"
325 | done
326 | gh_toc_md2html "$gh_tmp_md" | gh_toc_grab ""
327 | return
328 | fi
329 |
330 | if [ "$1" = '--insert' ]; then
331 | need_replace="yes"
332 | shift
333 | fi
334 |
335 | if [ "$1" = '--no-backup' ]; then
336 | need_replace="yes"
337 | no_backup="yes"
338 | shift
339 | fi
340 |
341 | if [ "$1" = '--hide-footer' ]; then
342 | need_replace="yes"
343 | no_footer="yes"
344 | shift
345 | fi
346 |
347 | for md in "$@"
348 | do
349 | echo ""
350 | gh_toc "$md" "$#" "$need_replace" "$no_backup" "$no_footer"
351 | done
352 |
353 | echo ""
354 | echo "Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc)"
355 | }
356 |
357 | #
358 | # Entry point
359 | #
360 | gh_toc_app "$@"
361 |
362 |
--------------------------------------------------------------------------------
/Scripts/httpd.conf:
--------------------------------------------------------------------------------
1 | #
2 | # This is the main Apache HTTP server configuration file. It contains the
3 | # configuration directives that give the server its instructions.
4 | # See for detailed information.
5 | # In particular, see
6 | #
7 | # for a discussion of each configuration directive.
8 | #
9 | # Do NOT simply read the instructions in here without understanding
10 | # what they do. They're here only as hints or reminders. If you are unsure
11 | # consult the online docs. You have been warned.
12 | #
13 | # Configuration and logfile names: If the filenames you specify for many
14 | # of the server's control files begin with "/" (or "drive:/" for Win32), the
15 | # server will use that explicit path. If the filenames do *not* begin
16 | # with "/", the value of ServerRoot is prepended -- so "logs/access_log"
17 | # with ServerRoot set to "/usr/local/apache2" will be interpreted by the
18 | # server as "/usr/local/apache2/logs/access_log", whereas "/logs/access_log"
19 | # will be interpreted as '/logs/access_log'.
20 |
21 | #
22 | # ServerRoot: The top of the directory tree under which the server's
23 | # configuration, error, and log files are kept.
24 | #
25 | # Do not add a slash at the end of the directory path. If you point
26 | # ServerRoot at a non-local disk, be sure to specify a local disk on the
27 | # Mutex directive, if file-based mutexes are used. If you wish to share the
28 | # same ServerRoot for multiple httpd daemons, you will need to change at
29 | # least PidFile.
30 | #
31 | ServerRoot "/usr/local/apache2"
32 |
33 | #
34 | # Mutex: Allows you to set the mutex mechanism and mutex file directory
35 | # for individual mutexes, or change the global defaults
36 | #
37 | # Uncomment and change the directory if mutexes are file-based and the default
38 | # mutex file directory is not on a local disk or is not appropriate for some
39 | # other reason.
40 | #
41 | # Mutex default:logs
42 |
43 | #
44 | # Listen: Allows you to bind Apache to specific IP addresses and/or
45 | # ports, instead of the default. See also the
46 | # directive.
47 | #
48 | # Change this to Listen on specific IP addresses as shown below to
49 | # prevent Apache from glomming onto all bound IP addresses.
50 | #
51 | #Listen 12.34.56.78:80
52 | Listen 80
53 |
54 | #
55 | # Dynamic Shared Object (DSO) Support
56 | #
57 | # To be able to use the functionality of a module which was built as a DSO you
58 | # have to place corresponding `LoadModule' lines at this location so the
59 | # directives contained in it are actually available _before_ they are used.
60 | # Statically compiled modules (those listed by `httpd -l') do not need
61 | # to be loaded here.
62 | #
63 | # Example:
64 | # LoadModule foo_module modules/mod_foo.so
65 | #
66 | LoadModule mpm_event_module modules/mod_mpm_event.so
67 | #LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
68 | #LoadModule mpm_worker_module modules/mod_mpm_worker.so
69 | LoadModule authn_file_module modules/mod_authn_file.so
70 | #LoadModule authn_dbm_module modules/mod_authn_dbm.so
71 | #LoadModule authn_anon_module modules/mod_authn_anon.so
72 | #LoadModule authn_dbd_module modules/mod_authn_dbd.so
73 | #LoadModule authn_socache_module modules/mod_authn_socache.so
74 | LoadModule authn_core_module modules/mod_authn_core.so
75 | LoadModule authz_host_module modules/mod_authz_host.so
76 | LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
77 | LoadModule authz_user_module modules/mod_authz_user.so
78 | #LoadModule authz_dbm_module modules/mod_authz_dbm.so
79 | #LoadModule authz_owner_module modules/mod_authz_owner.so
80 | #LoadModule authz_dbd_module modules/mod_authz_dbd.so
81 | LoadModule authz_core_module modules/mod_authz_core.so
82 | #LoadModule authnz_ldap_module modules/mod_authnz_ldap.so
83 | #LoadModule authnz_fcgi_module modules/mod_authnz_fcgi.so
84 | LoadModule access_compat_module modules/mod_access_compat.so
85 | LoadModule auth_basic_module modules/mod_auth_basic.so
86 | #LoadModule auth_form_module modules/mod_auth_form.so
87 | #LoadModule auth_digest_module modules/mod_auth_digest.so
88 | #LoadModule allowmethods_module modules/mod_allowmethods.so
89 | #LoadModule isapi_module modules/mod_isapi.so
90 | #LoadModule file_cache_module modules/mod_file_cache.so
91 | #LoadModule cache_module modules/mod_cache.so
92 | #LoadModule cache_disk_module modules/mod_cache_disk.so
93 | #LoadModule cache_socache_module modules/mod_cache_socache.so
94 | #LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
95 | #LoadModule socache_dbm_module modules/mod_socache_dbm.so
96 | #LoadModule socache_memcache_module modules/mod_socache_memcache.so
97 | #LoadModule socache_redis_module modules/mod_socache_redis.so
98 | #LoadModule watchdog_module modules/mod_watchdog.so
99 | #LoadModule macro_module modules/mod_macro.so
100 | #LoadModule dbd_module modules/mod_dbd.so
101 | #LoadModule bucketeer_module modules/mod_bucketeer.so
102 | #LoadModule dumpio_module modules/mod_dumpio.so
103 | #LoadModule echo_module modules/mod_echo.so
104 | #LoadModule example_hooks_module modules/mod_example_hooks.so
105 | #LoadModule case_filter_module modules/mod_case_filter.so
106 | #LoadModule case_filter_in_module modules/mod_case_filter_in.so
107 | #LoadModule example_ipc_module modules/mod_example_ipc.so
108 | #LoadModule buffer_module modules/mod_buffer.so
109 | #LoadModule data_module modules/mod_data.so
110 | #LoadModule ratelimit_module modules/mod_ratelimit.so
111 | LoadModule reqtimeout_module modules/mod_reqtimeout.so
112 | #LoadModule ext_filter_module modules/mod_ext_filter.so
113 | #LoadModule request_module modules/mod_request.so
114 | #LoadModule include_module modules/mod_include.so
115 | LoadModule filter_module modules/mod_filter.so
116 | #LoadModule reflector_module modules/mod_reflector.so
117 | #LoadModule substitute_module modules/mod_substitute.so
118 | #LoadModule sed_module modules/mod_sed.so
119 | #LoadModule charset_lite_module modules/mod_charset_lite.so
120 | #LoadModule deflate_module modules/mod_deflate.so
121 | #LoadModule xml2enc_module modules/mod_xml2enc.so
122 | #LoadModule proxy_html_module modules/mod_proxy_html.so
123 | #LoadModule brotli_module modules/mod_brotli.so
124 | LoadModule mime_module modules/mod_mime.so
125 | #LoadModule ldap_module modules/mod_ldap.so
126 | LoadModule log_config_module modules/mod_log_config.so
127 | #LoadModule log_debug_module modules/mod_log_debug.so
128 | #LoadModule log_forensic_module modules/mod_log_forensic.so
129 | #LoadModule logio_module modules/mod_logio.so
130 | #LoadModule lua_module modules/mod_lua.so
131 | LoadModule env_module modules/mod_env.so
132 | #LoadModule mime_magic_module modules/mod_mime_magic.so
133 | #LoadModule cern_meta_module modules/mod_cern_meta.so
134 | #LoadModule expires_module modules/mod_expires.so
135 | LoadModule headers_module modules/mod_headers.so
136 | #LoadModule ident_module modules/mod_ident.so
137 | #LoadModule usertrack_module modules/mod_usertrack.so
138 | #LoadModule unique_id_module modules/mod_unique_id.so
139 | LoadModule setenvif_module modules/mod_setenvif.so
140 | LoadModule version_module modules/mod_version.so
141 | #LoadModule remoteip_module modules/mod_remoteip.so
142 | #LoadModule proxy_module modules/mod_proxy.so
143 | #LoadModule proxy_connect_module modules/mod_proxy_connect.so
144 | #LoadModule proxy_ftp_module modules/mod_proxy_ftp.so
145 | #LoadModule proxy_http_module modules/mod_proxy_http.so
146 | #LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
147 | #LoadModule proxy_scgi_module modules/mod_proxy_scgi.so
148 | #LoadModule proxy_uwsgi_module modules/mod_proxy_uwsgi.so
149 | #LoadModule proxy_fdpass_module modules/mod_proxy_fdpass.so
150 | #LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
151 | #LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
152 | #LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
153 | #LoadModule proxy_express_module modules/mod_proxy_express.so
154 | #LoadModule proxy_hcheck_module modules/mod_proxy_hcheck.so
155 | #LoadModule session_module modules/mod_session.so
156 | #LoadModule session_cookie_module modules/mod_session_cookie.so
157 | #LoadModule session_crypto_module modules/mod_session_crypto.so
158 | #LoadModule session_dbd_module modules/mod_session_dbd.so
159 | #LoadModule slotmem_shm_module modules/mod_slotmem_shm.so
160 | #LoadModule slotmem_plain_module modules/mod_slotmem_plain.so
161 | #LoadModule ssl_module modules/mod_ssl.so
162 | #LoadModule optional_hook_export_module modules/mod_optional_hook_export.so
163 | #LoadModule optional_hook_import_module modules/mod_optional_hook_import.so
164 | #LoadModule optional_fn_import_module modules/mod_optional_fn_import.so
165 | #LoadModule optional_fn_export_module modules/mod_optional_fn_export.so
166 | #LoadModule dialup_module modules/mod_dialup.so
167 | #LoadModule http2_module modules/mod_http2.so
168 | #LoadModule proxy_http2_module modules/mod_proxy_http2.so
169 | #LoadModule md_module modules/mod_md.so
170 | #LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so
171 | #LoadModule lbmethod_bytraffic_module modules/mod_lbmethod_bytraffic.so
172 | #LoadModule lbmethod_bybusyness_module modules/mod_lbmethod_bybusyness.so
173 | #LoadModule lbmethod_heartbeat_module modules/mod_lbmethod_heartbeat.so
174 | LoadModule unixd_module modules/mod_unixd.so
175 | #LoadModule heartbeat_module modules/mod_heartbeat.so
176 | #LoadModule heartmonitor_module modules/mod_heartmonitor.so
177 | #LoadModule dav_module modules/mod_dav.so
178 | LoadModule status_module modules/mod_status.so
179 | LoadModule autoindex_module modules/mod_autoindex.so
180 | #LoadModule asis_module modules/mod_asis.so
181 | #LoadModule info_module modules/mod_info.so
182 | #LoadModule suexec_module modules/mod_suexec.so
183 |
184 | #LoadModule cgid_module modules/mod_cgid.so
185 |
186 |
187 | #LoadModule cgi_module modules/mod_cgi.so
188 |
189 | #LoadModule dav_fs_module modules/mod_dav_fs.so
190 | #LoadModule dav_lock_module modules/mod_dav_lock.so
191 | #LoadModule vhost_alias_module modules/mod_vhost_alias.so
192 | #LoadModule negotiation_module modules/mod_negotiation.so
193 | LoadModule dir_module modules/mod_dir.so
194 | #LoadModule imagemap_module modules/mod_imagemap.so
195 | #LoadModule actions_module modules/mod_actions.so
196 | #LoadModule speling_module modules/mod_speling.so
197 | #LoadModule userdir_module modules/mod_userdir.so
198 | LoadModule alias_module modules/mod_alias.so
199 | LoadModule rewrite_module modules/mod_rewrite.so
200 |
201 |
202 | #
203 | # If you wish httpd to run as a different user or group, you must run
204 | # httpd as root initially and it will switch.
205 | #
206 | # User/Group: The name (or #number) of the user/group to run httpd as.
207 | # It is usually good practice to create a dedicated user and group for
208 | # running httpd, as with most system services.
209 | #
210 | User daemon
211 | Group daemon
212 |
213 |
214 |
215 | # 'Main' server configuration
216 | #
217 | # The directives in this section set up the values used by the 'main'
218 | # server, which responds to any requests that aren't handled by a
219 | # definition. These values also provide defaults for
220 | # any containers you may define later in the file.
221 | #
222 | # All of these directives may appear inside containers,
223 | # in which case these default settings will be overridden for the
224 | # virtual host being defined.
225 | #
226 |
227 | #
228 | # ServerAdmin: Your address, where problems with the server should be
229 | # e-mailed. This address appears on some server-generated pages, such
230 | # as error documents. e.g. admin@your-domain.com
231 | #
232 | ServerAdmin you@example.com
233 |
234 | #
235 | # ServerName gives the name and port that the server uses to identify itself.
236 | # This can often be determined automatically, but we recommend you specify
237 | # it explicitly to prevent problems during startup.
238 | #
239 | # If your host doesn't have a registered DNS name, enter its IP address here.
240 | #
241 | #ServerName www.example.com:80
242 |
243 | #
244 | # Deny access to the entirety of your server's filesystem. You must
245 | # explicitly permit access to web content directories in other
246 | # blocks below.
247 | #
248 |
249 | AllowOverride none
250 | Require all denied
251 |
252 |
253 | #
254 | # Note that from this point forward you must specifically allow
255 | # particular features to be enabled - so if something's not working as
256 | # you might expect, make sure that you have specifically enabled it
257 | # below.
258 | #
259 |
260 | #
261 | # DocumentRoot: The directory out of which you will serve your
262 | # documents. By default, all requests are taken from this directory, but
263 | # symbolic links and aliases may be used to point to other locations.
264 | #
265 | DocumentRoot "/usr/local/apache2/htdocs"
266 |
267 | #
268 | # Possible values for the Options directive are "None", "All",
269 | # or any combination of:
270 | # Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews
271 | #
272 | # Note that "MultiViews" must be named *explicitly* --- "Options All"
273 | # doesn't give it to you.
274 | #
275 | # The Options directive is both complicated and important. Please see
276 | # http://httpd.apache.org/docs/2.4/mod/core.html#options
277 | # for more information.
278 | #
279 | Options Indexes FollowSymLinks
280 |
281 | #
282 | # AllowOverride controls what directives may be placed in .htaccess files.
283 | # It can be "All", "None", or any combination of the keywords:
284 | # AllowOverride FileInfo AuthConfig Limit
285 | #
286 | AllowOverride All
287 |
288 | #
289 | # Controls who can get stuff from this server.
290 | #
291 | Require all granted
292 |
293 |
294 | #
295 | # DirectoryIndex: sets the file that Apache will serve if a directory
296 | # is requested.
297 | #
298 |
299 | DirectoryIndex index.html
300 |
301 |
302 | #
303 | # The following lines prevent .htaccess and .htpasswd files from being
304 | # viewed by Web clients.
305 | #
306 |
307 | Require all denied
308 |
309 |
310 | #
311 | # ErrorLog: The location of the error log file.
312 | # If you do not specify an ErrorLog directive within a
313 | # container, error messages relating to that virtual host will be
314 | # logged here. If you *do* define an error logfile for a
315 | # container, that host's errors will be logged there and not here.
316 | #
317 | ErrorLog /proc/self/fd/2
318 |
319 | #
320 | # LogLevel: Control the number of messages logged to the error_log.
321 | # Possible values include: debug, info, notice, warn, error, crit,
322 | # alert, emerg.
323 | #
324 | LogLevel warn
325 |
326 |
327 | #
328 | # The following directives define some format nicknames for use with
329 | # a CustomLog directive (see below).
330 | #
331 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
332 | LogFormat "%h %l %u %t \"%r\" %>s %b" common
333 |
334 |
335 | # You need to enable mod_logio.c to use %I and %O
336 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
337 |
338 |
339 | #
340 | # The location and format of the access logfile (Common Logfile Format).
341 | # If you do not define any access logfiles within a
342 | # container, they will be logged here. Contrariwise, if you *do*
343 | # define per- access logfiles, transactions will be
344 | # logged therein and *not* in this file.
345 | #
346 | CustomLog /proc/self/fd/1 common
347 |
348 | #
349 | # If you prefer a logfile with access, agent, and referer information
350 | # (Combined Logfile Format) you can use the following directive.
351 | #
352 | #CustomLog "logs/access_log" combined
353 |
354 |
355 |
356 | #
357 | # Redirect: Allows you to tell clients about documents that used to
358 | # exist in your server's namespace, but do not anymore. The client
359 | # will make a new request for the document at its new location.
360 | # Example:
361 | # Redirect permanent /foo http://www.example.com/bar
362 |
363 | #
364 | # Alias: Maps web paths into filesystem paths and is used to
365 | # access content that does not live under the DocumentRoot.
366 | # Example:
367 | # Alias /webpath /full/filesystem/path
368 | #
369 | # If you include a trailing / on /webpath then the server will
370 | # require it to be present in the URL. You will also likely
371 | # need to provide a section to allow access to
372 | # the filesystem path.
373 |
374 | #
375 | # ScriptAlias: This controls which directories contain server scripts.
376 | # ScriptAliases are essentially the same as Aliases, except that
377 | # documents in the target directory are treated as applications and
378 | # run by the server when requested rather than as documents sent to the
379 | # client. The same rules about trailing "/" apply to ScriptAlias
380 | # directives as to Alias.
381 | #
382 | ScriptAlias /cgi-bin/ "/usr/local/apache2/cgi-bin/"
383 |
384 |
385 |
386 |
387 | #
388 | # ScriptSock: On threaded servers, designate the path to the UNIX
389 | # socket used to communicate with the CGI daemon of mod_cgid.
390 | #
391 | #Scriptsock cgisock
392 |
393 |
394 | #
395 | # "/usr/local/apache2/cgi-bin" should be changed to whatever your ScriptAliased
396 | # CGI directory exists, if you have that configured.
397 | #
398 |
399 | AllowOverride None
400 | Options None
401 | Require all granted
402 |
403 |
404 |
405 | #
406 | # Avoid passing HTTP_PROXY environment to CGI's on this or any proxied
407 | # backend servers which have lingering "httpoxy" defects.
408 | # 'Proxy' request header is undefined by the IETF, not listed by IANA
409 | #
410 | RequestHeader unset Proxy early
411 |
412 |
413 |
414 | #
415 | # TypesConfig points to the file containing the list of mappings from
416 | # filename extension to MIME-type.
417 | #
418 | TypesConfig conf/mime.types
419 |
420 | #
421 | # AddType allows you to add to or override the MIME configuration
422 | # file specified in TypesConfig for specific file types.
423 | #
424 | #AddType application/x-gzip .tgz
425 | #
426 | # AddEncoding allows you to have certain browsers uncompress
427 | # information on the fly. Note: Not all browsers support this.
428 | #
429 | #AddEncoding x-compress .Z
430 | #AddEncoding x-gzip .gz .tgz
431 | #
432 | # If the AddEncoding directives above are commented-out, then you
433 | # probably should define those extensions to indicate media types:
434 | #
435 | AddType application/x-compress .Z
436 | AddType application/x-gzip .gz .tgz
437 |
438 | #
439 | # AddHandler allows you to map certain file extensions to "handlers":
440 | # actions unrelated to filetype. These can be either built into the server
441 | # or added with the Action directive (see below)
442 | #
443 | # To use CGI scripts outside of ScriptAliased directories:
444 | # (You will also need to add "ExecCGI" to the "Options" directive.)
445 | #
446 | #AddHandler cgi-script .cgi
447 |
448 | # For type maps (negotiated resources):
449 | #AddHandler type-map var
450 |
451 | #
452 | # Filters allow you to process content before it is sent to the client.
453 | #
454 | # To parse .shtml files for server-side includes (SSI):
455 | # (You will also need to add "Includes" to the "Options" directive.)
456 | #
457 | #AddType text/html .shtml
458 | #AddOutputFilter INCLUDES .shtml
459 |
460 |
461 | #
462 | # The mod_mime_magic module allows the server to use various hints from the
463 | # contents of the file itself to determine its type. The MIMEMagicFile
464 | # directive tells the module where the hint definitions are located.
465 | #
466 | #MIMEMagicFile conf/magic
467 |
468 | #
469 | # Customizable error responses come in three flavors:
470 | # 1) plain text 2) local redirects 3) external redirects
471 | #
472 | # Some examples:
473 | #ErrorDocument 500 "The server made a boo boo."
474 | #ErrorDocument 404 /missing.html
475 | #ErrorDocument 404 "/cgi-bin/missing_handler.pl"
476 | #ErrorDocument 402 http://www.example.com/subscription_info.html
477 | #
478 |
479 | #
480 | # MaxRanges: Maximum number of Ranges in a request before
481 | # returning the entire resource, or one of the special
482 | # values 'default', 'none' or 'unlimited'.
483 | # Default setting is to accept 200 Ranges.
484 | #MaxRanges unlimited
485 |
486 | #
487 | # EnableMMAP and EnableSendfile: On systems that support it,
488 | # memory-mapping or the sendfile syscall may be used to deliver
489 | # files. This usually improves server performance, but must
490 | # be turned off when serving from networked-mounted
491 | # filesystems or if support for these functions is otherwise
492 | # broken on your system.
493 | # Defaults: EnableMMAP On, EnableSendfile Off
494 | #
495 | #EnableMMAP off
496 | #EnableSendfile on
497 |
498 | # Supplemental configuration
499 | #
500 | # The configuration files in the conf/extra/ directory can be
501 | # included to add extra features or to modify the default configuration of
502 | # the server, or you may simply copy their contents here and change as
503 | # necessary.
504 |
505 | # Server-pool management (MPM specific)
506 | #Include conf/extra/httpd-mpm.conf
507 |
508 | # Multi-language error messages
509 | #Include conf/extra/httpd-multilang-errordoc.conf
510 |
511 | # Fancy directory listings
512 | #Include conf/extra/httpd-autoindex.conf
513 |
514 | # Language settings
515 | #Include conf/extra/httpd-languages.conf
516 |
517 | # User home directories
518 | #Include conf/extra/httpd-userdir.conf
519 |
520 | # Real-time info on requests and configuration
521 | #Include conf/extra/httpd-info.conf
522 |
523 | # Virtual hosts
524 | #Include conf/extra/httpd-vhosts.conf
525 |
526 | # Local access to the Apache HTTP Server Manual
527 | #Include conf/extra/httpd-manual.conf
528 |
529 | # Distributed authoring and versioning (WebDAV)
530 | #Include conf/extra/httpd-dav.conf
531 |
532 | # Various default settings
533 | #Include conf/extra/httpd-default.conf
534 |
535 | # Configure mod_proxy_html to understand HTML4/XHTML1
536 |
537 | Include conf/extra/proxy-html.conf
538 |
539 |
540 | # Secure (SSL/TLS) connections
541 | #Include conf/extra/httpd-ssl.conf
542 | #
543 | # Note: The following must must be present to support
544 | # starting without SSL on platforms with no /dev/random equivalent
545 | # but a statically compiled-in mod_ssl.
546 | #
547 |
548 | SSLRandomSeed startup builtin
549 | SSLRandomSeed connect builtin
550 |
551 |
552 |
--------------------------------------------------------------------------------
/Sources/SundialKit/Network/Extensions/NWInterface.swift:
--------------------------------------------------------------------------------
1 | #if canImport(Network)
2 |
3 | import Network
4 | @available(macOS 10.14, *)
5 | // swiftlint:disable:next file_types_order
6 | internal extension NWInterface.InterfaceType {
7 | // swiftlint:disable:next explicit_acl
8 | var value: Int {
9 | switch self {
10 | case .other:
11 | return PathStatus.Interface.other.rawValue
12 |
13 | case .wifi:
14 | return PathStatus.Interface.wifi.rawValue
15 |
16 | case .cellular:
17 | return PathStatus.Interface.cellular.rawValue
18 |
19 | case .wiredEthernet:
20 | return PathStatus.Interface.wiredEthernet.rawValue
21 |
22 | case .loopback:
23 | return PathStatus.Interface.loopback.rawValue
24 | @unknown default:
25 | return 0
26 | }
27 | }
28 | }
29 |
30 | @available(macOS 10.14, *)
31 | extension NWInterface: Interfaceable {
32 | public var typeValue: Int {
33 | type.value
34 | }
35 | }
36 | #endif
37 |
--------------------------------------------------------------------------------
/Sources/SundialKit/Network/Extensions/NWPath.swift:
--------------------------------------------------------------------------------
1 | #if canImport(Network)
2 | import Network
3 |
4 | @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *)
5 | extension NWPath: NetworkPath {
6 | public var pathStatus: PathStatus {
7 | if #available(iOS 14.2, watchOS 7.1, macOS 11.0, tvOS 14.2, *) {
8 | return PathStatus(
9 | status,
10 | reason: unsatisfiedReason,
11 | interfaces: availableInterfaces.map {
12 | $0 as Interfaceable
13 | }
14 | )
15 | } else {
16 | return PathStatus(
17 | status,
18 | interfaces: availableInterfaces.map {
19 | $0 as Interfaceable
20 | }
21 | )
22 | }
23 | }
24 | }
25 |
26 | #endif
27 |
--------------------------------------------------------------------------------
/Sources/SundialKit/Network/Extensions/NWPathMonitor.swift:
--------------------------------------------------------------------------------
1 | #if canImport(Network)
2 | import Network
3 |
4 | @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *)
5 | extension NWPathMonitor: PathMonitor {
6 | public func onPathUpdate(_ handler: @escaping (NWPath) -> Void) {
7 | pathUpdateHandler = handler
8 | }
9 | }
10 |
11 | @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *)
12 | public extension NetworkObserver {
13 | /// Default implementation of `NetworkObserver`
14 | /// which does not use execute a perodic ``NetworkPing``.
15 | convenience init() where MonitorType == NWPathMonitor, PingType == NeverPing {
16 | let monitor = NWPathMonitor()
17 | self.init(monitor: monitor)
18 | }
19 |
20 | /// Default implementation of `NetworkObserver`
21 | /// which does use execute a perodic ``NetworkPing``.
22 | convenience init(ping: PingType) where MonitorType == NWPathMonitor {
23 | let monitor = NWPathMonitor()
24 | self.init(monitor: monitor, ping: ping)
25 | }
26 | }
27 |
28 | #endif
29 |
--------------------------------------------------------------------------------
/Sources/SundialKit/Network/Extensions/PathStatus.Network.swift:
--------------------------------------------------------------------------------
1 | // swiftlint:disable function_body_length cyclomatic_complexity
2 | #if canImport(Network)
3 | import Network
4 |
5 | @available(macOS 10.14, *)
6 | extension PathStatus {
7 | /// Creates a `PathStatus`.
8 | /// - Parameters:
9 | /// - status: The `NWPath.Status`
10 | /// - interfaces: The `Interfacable` objects.
11 | @available(macOS, obsoleted: 11.0)
12 | @available(iOS, obsoleted: 14.2)
13 | @available(watchOS, obsoleted: 7.0)
14 | @available(tvOS, obsoleted: 14.2)
15 | // swiftlint:disable:next explicit_acl
16 | init(
17 | _ status: NWPath.Status,
18 | interfaces: [Interfaceable]
19 | ) {
20 | self.init(status, reason: .unsupported, interfaces: interfaces)
21 | }
22 |
23 | /// Creates a `PathStatus`.
24 | /// - Parameters:
25 | /// - status: The `NWPath.Status`
26 | /// - reason: The `NWPath.UnsatisfiedReason`
27 | /// - interfaces: The `Interfacable` objects.
28 | @available(iOS 14.2, watchOS 7.1, macOS 11.0, tvOS 14.2, *)
29 | // swiftlint:disable:next explicit_acl
30 | init(
31 | _ status: NWPath.Status,
32 | reason: NWPath.UnsatisfiedReason,
33 | interfaces: [Interfaceable]
34 | ) {
35 | self.init(status, reason: UnsatisfiedReason(reason), interfaces: interfaces)
36 | }
37 |
38 | private init(
39 | _ status: NWPath.Status,
40 | reason: UnsatisfiedReason,
41 | interfaces: [Interfaceable]
42 | ) {
43 | switch (status, reason) {
44 | case (.satisfied, _):
45 | self = .satisfied(PathStatus.Interface(interfaces: interfaces))
46 |
47 | case (.unsatisfied, .cellularDenied):
48 | self = .unsatisfied(.cellularDenied)
49 |
50 | case (.requiresConnection, _):
51 | self = .requiresConnection
52 |
53 | case (.unsatisfied, .notAvailable):
54 | self = .unsatisfied(.notAvailable)
55 |
56 | case (.unsatisfied, .wifiDenied):
57 | self = .unsatisfied(.wifiDenied)
58 |
59 | case (.unsatisfied, .localNetworkDenied):
60 | self = .unsatisfied(.localNetworkDenied)
61 |
62 | case (.unsatisfied, _):
63 | self = .unsatisfied(.unknown)
64 |
65 | case (_, _):
66 | self = .unknown
67 | }
68 | }
69 | }
70 |
71 | #endif
72 |
--------------------------------------------------------------------------------
/Sources/SundialKit/Network/Extensions/PathStatus.UnsatisfiedReason.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | #if canImport(Network)
3 | import Network
4 |
5 | @available(macOS 11.0, iOS 14.2, watchOS 7.1, tvOS 14.2, *)
6 | public extension PathStatus.UnsatisfiedReason {
7 | /// Creates `UnsatisfiedReason` from a `Network` one.
8 | /// - Parameter reason: The `UnsatisfiedReason` from the `Network` API.
9 | init(_ reason: NWPath.UnsatisfiedReason) {
10 | switch reason {
11 | case .notAvailable:
12 | self = .notAvailable
13 |
14 | case .cellularDenied:
15 | self = .cellularDenied
16 |
17 | case .wifiDenied:
18 | self = .wifiDenied
19 |
20 | case .localNetworkDenied:
21 | self = .localNetworkDenied
22 | @unknown default:
23 | self = .unknown
24 | }
25 | }
26 | }
27 | #endif
28 |
--------------------------------------------------------------------------------
/Sources/SundialKit/Network/Interfaceable.swift:
--------------------------------------------------------------------------------
1 | /// Defines an object which can be used as a path interface.
2 | internal protocol Interfaceable {
3 | /// Integer value which matches the `PathStatus.Interface` values.
4 | var typeValue: Int { get }
5 | }
6 |
--------------------------------------------------------------------------------
/Sources/SundialKit/Network/NetworkObserver.swift:
--------------------------------------------------------------------------------
1 | #if canImport(Combine)
2 | import Combine
3 | import Foundation
4 |
5 | /// Observes the status of network connectivity
6 | ///
7 | /// `NetworkObserver` allows you the listen to variety of publishers related to the network.
8 | /// This is especially useful if you are using `SwiftUI` in particular.
9 | /// With `SwiftUI`, you can create an `ObservableObject` which contains an `NetworkObserver`:
10 | ///
11 | /// ```swift
12 | /// import SwiftUI
13 | /// import SundialKit
14 | ///
15 | /// class NetworkConnectivityObject : ObservableObject {
16 | /// // our NetworkObserver
17 | /// let connectivityObserver = NetworkObserver()
18 | ///
19 | /// // our published property for pathStatus initially set to `.unknown`
20 | /// @Published var pathStatus : PathStatus = .unknown
21 | ///
22 | /// init () {
23 | /// // set the pathStatus changes to our published property
24 | /// connectivityObserver
25 | /// .pathStatusPublisher
26 | /// .receive(on: DispatchQueue.main)
27 | /// .assign(to: &self.$pathStatus)
28 | /// }
29 | ///
30 | /// // need to start listening
31 | /// func start () {
32 | /// self.connectivityObserver.start(queue: .global())
33 | /// }
34 | /// }
35 | /// ```
36 | ///
37 | @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *)
38 | public class NetworkObserver {
39 | private let ping: PingType?
40 | private let monitor: MonitorType
41 |
42 | private let pathSubject = PassthroughSubject()
43 | private var pingCancellable: AnyCancellable?
44 | private var timerCancellable: Cancellable?
45 | private var otherCancellables = [AnyCancellable]()
46 |
47 | private let pathStatusSubject = PassthroughSubject()
48 | private let isExpensiveSubject = PassthroughSubject()
49 | private let isConstrainedSubject = PassthroughSubject()
50 | private let pingStatusSubject = PassthroughSubject()
51 |
52 | internal var isPingActive: Bool {
53 | timerCancellable != nil
54 | }
55 |
56 | internal var hasNetworkPing: Bool {
57 | ping != nil
58 | }
59 |
60 | /// Publishes updates to the `PathStatus`
61 | public var pathStatusPublisher: AnyPublisher {
62 | pathStatusSubject.eraseToAnyPublisher()
63 | }
64 |
65 | /// Publishes updates to whether the network connection is expensive.
66 | public var isExpensivePublisher: AnyPublisher {
67 | isExpensiveSubject.eraseToAnyPublisher()
68 | }
69 |
70 | /// Publishes updates to whether the network connection is constrained.
71 | public var isConstrainedPublisher: AnyPublisher {
72 | isConstrainedSubject.eraseToAnyPublisher()
73 | }
74 |
75 | /// Publishes updates to the `PingType.StatusType`
76 | public var pingStatusPublisher: AnyPublisher {
77 | pingStatusSubject.eraseToAnyPublisher()
78 | }
79 |
80 | internal init(monitor: MonitorType, pingOrNil: PingType?) {
81 | self.monitor = monitor
82 | ping = pingOrNil
83 |
84 | pathSubject.map(\.pathStatus).subscribe(pathStatusSubject).store(in: &otherCancellables)
85 | pathSubject.map(\.isExpensive).subscribe(isExpensiveSubject).store(in: &otherCancellables)
86 | pathSubject.map(\.isConstrained).subscribe(isConstrainedSubject).store(in: &otherCancellables)
87 |
88 | monitor.onPathUpdate(onUpdate(path:))
89 | }
90 |
91 | /// Starts the monitor.
92 | /// - Parameter queue: The `DispatchQueue` to start the `PathMonitor` on.
93 | public func start(queue: DispatchQueue) {
94 | timerCancellable = ping.map {
95 | $0.publish(with: self.pathStatusSubject)
96 | .subscribe(self.pingStatusSubject)
97 | }
98 | monitor.start(queue: queue)
99 | }
100 |
101 | /// Cancels the montor.
102 | public func cancel() {
103 | if let timerCancellable = timerCancellable {
104 | timerCancellable.cancel()
105 | self.timerCancellable = nil
106 | }
107 |
108 | monitor.cancel()
109 | }
110 |
111 | internal func onUpdate(path: MonitorType.PathType) {
112 | pathSubject.send(path)
113 | }
114 | }
115 |
116 | @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *)
117 | public extension NetworkObserver {
118 | /// Creates `NetworkObserver` without a `NetworkPing` object.
119 | /// - Parameter monitor: The `PathMonitor` to monitor the network .
120 | convenience init(monitor: MonitorType) where PingType == NeverPing {
121 | self.init(monitor: monitor, pingOrNil: nil)
122 | }
123 |
124 | /// Creates `NetworkObserver` with a `NetworkPing` object.
125 | /// - Parameters:
126 | /// - monitor: The `PathMonitor` to monitor the network .
127 | /// - ping: The `NetworkPing` to ping periodically.
128 | convenience init(monitor: MonitorType, ping: PingType) {
129 | self.init(monitor: monitor, pingOrNil: ping)
130 | }
131 | }
132 | #endif
133 |
--------------------------------------------------------------------------------
/Sources/SundialKit/Network/NetworkPath.swift:
--------------------------------------------------------------------------------
1 | /// A path which contains information about the network connections.
2 | public protocol NetworkPath {
3 | /// Whether the network path is constrained.
4 | var isConstrained: Bool { get }
5 | /// Whether the network path is expensive.
6 | var isExpensive: Bool { get }
7 | /// The status of the network connection.
8 | var pathStatus: PathStatus { get }
9 | }
10 |
--------------------------------------------------------------------------------
/Sources/SundialKit/Network/NetworkPing.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | #if canImport(Combine)
4 | import Combine
5 | #endif
6 |
7 | /// Called periodically to verify network connectivity.
8 | ///
9 | /// Here's an example which calls the _ipify_ API to verify there's a ip address:
10 | /// ```swift
11 | /// struct IpifyPing : NetworkPing {
12 | /// typealias StatusType = String?
13 | ///
14 | /// let session: URLSession
15 | /// let timeInterval: TimeInterval
16 | ///
17 | /// public func shouldPing(onStatus status: PathStatus) -> Bool {
18 | /// switch status {
19 | /// case .unknown, .unsatisfied:
20 | /// return false
21 | /// case .requiresConnection, .satisfied:
22 | /// return true
23 | /// }
24 | /// }
25 | ///
26 | /// static let url : URL = .init(string: "https://api.ipify.org")!
27 | ///
28 | /// func onPing(_ closure: @escaping (String?) -> Void) {
29 | /// session.dataTask(with: IpifyPing.url) { data, _, _ in
30 | /// closure(data.flatMap{String(data: $0, encoding: .utf8)})
31 | /// }.resume()
32 | /// }
33 | /// }
34 | /// ```
35 | ///
36 | /// In our `ObservableObject`, we can create a ``NetworkObserver`` to use this with:
37 | ///
38 | /// ```swift
39 | /// @Published var nwObject = NetworkObserver(ping:
40 | /// // use the shared `URLSession` and check every 10.0 seconds
41 | /// IpifyPing(session: .shared, timeInterval: 10.0)
42 | /// )
43 | /// ```
44 | public protocol NetworkPing {
45 | /// The resulting status of the ping.
46 | associatedtype StatusType
47 | /// The amount of time between each verification
48 | var timeInterval: TimeInterval { get }
49 | /// Based on the `PathStatus` should it verify network connectivity.
50 | func shouldPing(onStatus status: PathStatus) -> Bool
51 | /// Invokes the network verification.
52 | func onPing(_ closure: @escaping (StatusType) -> Void)
53 | }
54 |
55 | internal extension NetworkPing {
56 | // swiftlint:disable:next explicit_acl
57 | func onPingForFuture(_ closure: @escaping (Result) -> Void) {
58 | onPing {
59 | closure(.success($0))
60 | }
61 | }
62 | }
63 |
64 | #if canImport(Combine)
65 | @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *)
66 | internal extension NetworkPing {
67 | // swiftlint:disable:next explicit_acl
68 | func publish(
69 | with pathStatusPublisher: PathStatusPublisher
70 | ) -> AnyPublisher
71 | where
72 | PathStatusPublisher.Output == PathStatus,
73 | PathStatusPublisher.Failure == Never {
74 | let timerPublisher = Timer
75 | .publish(
76 | every: timeInterval,
77 | on: .current,
78 | in: .common
79 | )
80 | .autoconnect()
81 | .prepend(.init())
82 |
83 | return Publishers.CombineLatest(timerPublisher, pathStatusPublisher)
84 | .compactMap { _, status in
85 | self.shouldPing(onStatus: status) ? () : nil
86 | }
87 | .flatMap {
88 | Future(self.onPingForFuture(_:))
89 | }
90 | .eraseToAnyPublisher()
91 | }
92 | }
93 | #endif
94 |
--------------------------------------------------------------------------------
/Sources/SundialKit/Network/NeverPing.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// `NetworkPing` which is never called.
4 | /// This used for a `NetworkObserver` that doesn't need a continuous ping.
5 | public struct NeverPing: NetworkPing {
6 | public typealias StatusType = Never
7 |
8 | public var timeInterval: TimeInterval {
9 | .nan
10 | }
11 |
12 | private init() {}
13 |
14 | public func shouldPing(onStatus _: PathStatus) -> Bool {
15 | false
16 | }
17 |
18 | public func onPing(_: @escaping (Never) -> Void) {}
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/SundialKit/Network/PathMonitor.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// Monitors the network for connectivity
4 | ///
5 | /// Typically you don't need to implement this and
6 | /// can use [`NWPathMonitor`](../network/nwpathmonitor)
7 | public protocol PathMonitor {
8 | /// The type of path accepted by the `PathMonitor`.
9 | associatedtype PathType: NetworkPath
10 | /// Sets the handler for when the `PathType` updates.
11 | func onPathUpdate(_ handler: @escaping (PathType) -> Void)
12 | /// Starts the monitor.
13 | func start(queue: DispatchQueue)
14 | /// Stops the montor.
15 | func cancel()
16 | }
17 |
--------------------------------------------------------------------------------
/Sources/SundialKit/Network/PathStatus.swift:
--------------------------------------------------------------------------------
1 | /// Status of a particular network path
2 | public enum PathStatus: Equatable {
3 | /// Unable to connnect
4 | case unsatisfied(UnsatisfiedReason?)
5 | /// Able to connect with interface
6 | case satisfied(Interface)
7 | /// The path is not currently available, but establishing a new connection may activate the path.
8 | case requiresConnection
9 | /// Unknown status
10 | case unknown
11 |
12 | public enum UnsatisfiedReason: Equatable {
13 | case cellularDenied
14 | case localNetworkDenied
15 | case notAvailable
16 | case wifiDenied
17 | case unknown
18 | case unsupported
19 | }
20 |
21 | /// Types of network interfaces, based on their link layer media types.
22 | public struct Interface: OptionSet, Interfaceable {
23 | public var typeValue: Int {
24 | rawValue
25 | }
26 |
27 | public var rawValue: Int
28 |
29 | public init(rawValue: Int) {
30 | self.rawValue = rawValue
31 | }
32 |
33 | /// Converts a group of `Interfaceable` objects into a `PathStatus.Interface`
34 | /// - Parameter interfaces: A list of `PathStatus.Interface` object.
35 | internal init(interfaces: [Interfaceable]) {
36 | let rawValue = Set(interfaces.map(\.typeValue)).reduce(0, +)
37 | self.init(rawValue: rawValue)
38 | }
39 |
40 | /// The network interface type used for communication over cellular networks.
41 | public static let cellular: Self = .init(rawValue: 1)
42 | /// The network interface type used for communication over Wi-Fi networks.
43 | public static let wifi: Self = .init(rawValue: 2)
44 | /// The network interface type used for communication over wired Ethernet networks.
45 | public static let wiredEthernet: Self = .init(rawValue: 4)
46 | /// The network interface type used for communication
47 | /// over virtual networks or networks of unknown types.
48 | public static let other: Self = .init(rawValue: 8)
49 | /// The network interface type used for communication over local loopback networks.
50 | public static let loopback: Self = .init(rawValue: 16)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Sources/SundialKit/PassthroughSubject.swift:
--------------------------------------------------------------------------------
1 | #if canImport(Combine)
2 | import Combine
3 |
4 | @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
5 | extension PassthroughSubject {
6 | // swiftlint:disable:next explicit_acl
7 | func anyPublisher(
8 | for keyPath: KeyPath