-
Notifications
You must be signed in to change notification settings - Fork 86
/
PathConfigurationLoader.swift
138 lines (109 loc) · 4.44 KB
/
PathConfigurationLoader.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import Foundation
typealias PathConfigurationLoaderCompletionHandler = (PathConfigurationDecoder) -> Void
final class PathConfigurationLoader {
private let cacheDirectory = "Turbo"
private let configurationCacheFilename = "path-configuration.json"
private let sources: [PathConfiguration.Source]
private var completionHandler: PathConfigurationLoaderCompletionHandler?
init(sources: [PathConfiguration.Source]) {
self.sources = sources
}
func load(then completion: @escaping PathConfigurationLoaderCompletionHandler) {
completionHandler = completion
for source in sources {
switch source {
case .data(let data):
loadData(data)
case .file(let url):
loadFile(url)
case .server(let url):
download(from: url)
}
}
}
// MARK: - Server
private func download(from url: URL) {
precondition(!url.isFileURL, "URL provided for server is a file url")
// Immediately load most recent cached version if available
if let data = cachedData() {
loadData(data)
}
URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
guard let data = data,
let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200
else {
debugPrint("[path-configuration] *** error - invalid response or data: \(String(describing: response)), error: \(String(describing: error))")
return
}
self?.loadData(data, cache: true)
}.resume()
}
// MARK: - Caching
private func cacheRemoteData(_ data: Data) {
createCacheDirectoryIfNeeded()
do {
try data.write(to: configurationCacheURL)
} catch {
debugPrint("[path-configuration-loader] error caching file error: \(error)")
}
}
private func cachedData() -> Data? {
guard FileManager.default.fileExists(atPath: configurationCacheURL.path) else {
return nil
}
do {
return try Data(contentsOf: configurationCacheURL)
} catch {
debugPrint("[path-configuration-loader] *** error loading cached data: \(error)")
return nil
}
}
private func createCacheDirectoryIfNeeded() {
guard !FileManager.default.fileExists(atPath: turboCacheDirectoryURL.path) else { return }
do {
try FileManager.default.createDirectory(at: turboCacheDirectoryURL, withIntermediateDirectories: false, attributes: nil)
} catch {
debugPrint("[path-configuration-loader] *** error creating cache directory: \(error)")
}
}
private var turboCacheDirectoryURL: URL {
let directory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
return directory.appendingPathComponent(cacheDirectory)
}
var configurationCacheURL: URL {
turboCacheDirectoryURL.appendingPathComponent(configurationCacheFilename)
}
// MARK: - File
private func loadFile(_ url: URL) {
precondition(url.isFileURL, "URL provided for file is not a file url")
do {
let data = try Data(contentsOf: url)
loadData(data)
} catch {
debugPrint("[path-configuration] *** error loading configuration from file: \(url), error: \(error)")
}
}
// MARK: - Data
private func loadData(_ data: Data, cache: Bool = false) {
do {
guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
throw JSONDecodingError.invalidJSON
}
let config = try PathConfigurationDecoder(json: json)
if cache {
// Only cache once we ensure we have valid data
cacheRemoteData(data)
}
updateHandler(with: config)
} catch {
debugPrint("[path-configuration] *** error decoding path configuration: \(error)")
}
}
// MARK: - Delegate
private func updateHandler(with config: PathConfigurationDecoder) {
DispatchQueue.main.async {
self.completionHandler?(config)
}
}
}