@capgo/capacitor-updater 5.0.0-alpha.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapgoCapacitorUpdater.podspec +1 -1
- package/LICENCE +373 -661
- package/README.md +339 -75
- package/android/build.gradle +13 -12
- package/android/src/main/AndroidManifest.xml +4 -2
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleInfo.java +205 -121
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleStatus.java +32 -24
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdater.java +1041 -441
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +1217 -536
- package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipher.java +153 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayCondition.java +62 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayUntilNext.java +14 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +126 -0
- package/dist/docs.json +727 -171
- package/dist/esm/definitions.d.ts +234 -45
- package/dist/esm/definitions.js +5 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +9 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +12 -6
- package/dist/esm/web.js +64 -20
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +70 -23
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +70 -23
- package/dist/plugin.js.map +1 -1
- package/ios/Plugin/BundleInfo.swift +38 -19
- package/ios/Plugin/BundleStatus.swift +11 -4
- package/ios/Plugin/CapacitorUpdater.swift +520 -192
- package/ios/Plugin/CapacitorUpdaterPlugin.m +8 -1
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +447 -190
- package/ios/Plugin/CryptoCipher.swift +240 -0
- package/ios/Plugin/DelayCondition.swift +74 -0
- package/ios/Plugin/DelayUntilNext.swift +30 -0
- package/ios/Plugin/UserDefaultsExtension.swift +48 -0
- package/package.json +26 -20
- package/ios/Plugin/ObjectPreferences.swift +0 -97
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
import Foundation
|
|
2
8
|
import SSZipArchive
|
|
3
9
|
import Alamofire
|
|
10
|
+
import zlib
|
|
4
11
|
|
|
5
12
|
extension URL {
|
|
6
13
|
var isDirectory: Bool {
|
|
@@ -15,28 +22,111 @@ extension Date {
|
|
|
15
22
|
return Calendar.current.date(byAdding: .minute, value: minutes, to: self)!
|
|
16
23
|
}
|
|
17
24
|
}
|
|
25
|
+
struct SetChannelDec: Decodable {
|
|
26
|
+
let status: String?
|
|
27
|
+
let error: String?
|
|
28
|
+
let message: String?
|
|
29
|
+
}
|
|
30
|
+
public class SetChannel: NSObject {
|
|
31
|
+
var status: String = ""
|
|
32
|
+
var error: String = ""
|
|
33
|
+
var message: String = ""
|
|
34
|
+
}
|
|
35
|
+
extension SetChannel {
|
|
36
|
+
func toDict() -> [String: Any] {
|
|
37
|
+
var dict: [String: Any] = [String: Any]()
|
|
38
|
+
let otherSelf: Mirror = Mirror(reflecting: self)
|
|
39
|
+
for child: Mirror.Child in otherSelf.children {
|
|
40
|
+
if let key: String = child.label {
|
|
41
|
+
dict[key] = child.value
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return dict
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
struct GetChannelDec: Decodable {
|
|
48
|
+
let channel: String?
|
|
49
|
+
let status: String?
|
|
50
|
+
let error: String?
|
|
51
|
+
let message: String?
|
|
52
|
+
let allowSet: Bool?
|
|
53
|
+
}
|
|
54
|
+
public class GetChannel: NSObject {
|
|
55
|
+
var channel: String = ""
|
|
56
|
+
var status: String = ""
|
|
57
|
+
var error: String = ""
|
|
58
|
+
var message: String = ""
|
|
59
|
+
var allowSet: Bool = true
|
|
60
|
+
}
|
|
61
|
+
extension GetChannel {
|
|
62
|
+
func toDict() -> [String: Any] {
|
|
63
|
+
var dict: [String: Any] = [String: Any]()
|
|
64
|
+
let otherSelf: Mirror = Mirror(reflecting: self)
|
|
65
|
+
for child: Mirror.Child in otherSelf.children {
|
|
66
|
+
if let key: String = child.label {
|
|
67
|
+
dict[key] = child.value
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return dict
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
struct InfoObject: Codable {
|
|
74
|
+
let platform: String?
|
|
75
|
+
let device_id: String?
|
|
76
|
+
let app_id: String?
|
|
77
|
+
let custom_id: String?
|
|
78
|
+
let version_build: String?
|
|
79
|
+
let version_code: String?
|
|
80
|
+
let version_os: String?
|
|
81
|
+
let version_name: String?
|
|
82
|
+
let plugin_version: String?
|
|
83
|
+
let is_emulator: Bool?
|
|
84
|
+
let is_prod: Bool?
|
|
85
|
+
var action: String?
|
|
86
|
+
var channel: String?
|
|
87
|
+
}
|
|
18
88
|
struct AppVersionDec: Decodable {
|
|
19
89
|
let version: String?
|
|
90
|
+
let checksum: String?
|
|
20
91
|
let url: String?
|
|
21
92
|
let message: String?
|
|
93
|
+
let error: String?
|
|
94
|
+
let session_key: String?
|
|
22
95
|
let major: Bool?
|
|
23
96
|
}
|
|
24
97
|
public class AppVersion: NSObject {
|
|
25
98
|
var version: String = ""
|
|
99
|
+
var checksum: String = ""
|
|
26
100
|
var url: String = ""
|
|
27
101
|
var message: String?
|
|
102
|
+
var error: String?
|
|
103
|
+
var sessionKey: String?
|
|
28
104
|
var major: Bool?
|
|
29
105
|
}
|
|
106
|
+
|
|
107
|
+
extension AppVersion {
|
|
108
|
+
func toDict() -> [String: Any] {
|
|
109
|
+
var dict: [String: Any] = [String: Any]()
|
|
110
|
+
let otherSelf: Mirror = Mirror(reflecting: self)
|
|
111
|
+
for child: Mirror.Child in otherSelf.children {
|
|
112
|
+
if let key: String = child.label {
|
|
113
|
+
dict[key] = child.value
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return dict
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
30
120
|
extension OperatingSystemVersion {
|
|
31
121
|
func getFullVersion(separator: String = ".") -> String {
|
|
32
122
|
return "\(majorVersion)\(separator)\(minorVersion)\(separator)\(patchVersion)"
|
|
33
123
|
}
|
|
34
124
|
}
|
|
35
125
|
extension Bundle {
|
|
36
|
-
var
|
|
126
|
+
var versionName: String? {
|
|
37
127
|
return infoDictionary?["CFBundleShortVersionString"] as? String
|
|
38
128
|
}
|
|
39
|
-
var
|
|
129
|
+
var versionCode: String? {
|
|
40
130
|
return infoDictionary?["CFBundleVersion"] as? String
|
|
41
131
|
}
|
|
42
132
|
}
|
|
@@ -48,18 +138,18 @@ extension ISO8601DateFormatter {
|
|
|
48
138
|
}
|
|
49
139
|
}
|
|
50
140
|
extension Formatter {
|
|
51
|
-
static let iso8601withFractionalSeconds = ISO8601DateFormatter([.withInternetDateTime, .withFractionalSeconds])
|
|
141
|
+
static let iso8601withFractionalSeconds: ISO8601DateFormatter = ISO8601DateFormatter([.withInternetDateTime, .withFractionalSeconds])
|
|
52
142
|
}
|
|
53
143
|
extension Date {
|
|
54
144
|
var iso8601withFractionalSeconds: String { return Formatter.iso8601withFractionalSeconds.string(from: self) }
|
|
55
145
|
}
|
|
56
146
|
extension String {
|
|
57
|
-
|
|
147
|
+
|
|
58
148
|
var fileURL: URL {
|
|
59
149
|
return URL(fileURLWithPath: self)
|
|
60
150
|
}
|
|
61
|
-
|
|
62
|
-
var lastPathComponent:String {
|
|
151
|
+
|
|
152
|
+
var lastPathComponent: String {
|
|
63
153
|
get {
|
|
64
154
|
return fileURL.lastPathComponent
|
|
65
155
|
}
|
|
@@ -75,6 +165,8 @@ extension String {
|
|
|
75
165
|
enum CustomError: Error {
|
|
76
166
|
// Throw when an unzip fail
|
|
77
167
|
case cannotUnzip
|
|
168
|
+
case cannotWrite
|
|
169
|
+
case cannotDecode
|
|
78
170
|
case cannotUnflat
|
|
79
171
|
case cannotCreateDirectory
|
|
80
172
|
case cannotDeleteDirectory
|
|
@@ -106,56 +198,109 @@ extension CustomError: LocalizedError {
|
|
|
106
198
|
"The file cannot be unflat",
|
|
107
199
|
comment: "Invalid folder"
|
|
108
200
|
)
|
|
109
|
-
case .unexpected
|
|
201
|
+
case .unexpected:
|
|
110
202
|
return NSLocalizedString(
|
|
111
203
|
"An unexpected error occurred.",
|
|
112
204
|
comment: "Unexpected Error"
|
|
113
205
|
)
|
|
206
|
+
case .cannotDecode:
|
|
207
|
+
return NSLocalizedString(
|
|
208
|
+
"Decoding the zip failed with this key",
|
|
209
|
+
comment: "Invalid private key"
|
|
210
|
+
)
|
|
211
|
+
case .cannotWrite:
|
|
212
|
+
return NSLocalizedString(
|
|
213
|
+
"Cannot write to the destination",
|
|
214
|
+
comment: "Invalid destination"
|
|
215
|
+
)
|
|
114
216
|
}
|
|
115
217
|
}
|
|
116
218
|
}
|
|
117
219
|
|
|
118
220
|
@objc public class CapacitorUpdater: NSObject {
|
|
119
|
-
|
|
120
|
-
private let
|
|
121
|
-
private let
|
|
122
|
-
private let
|
|
123
|
-
private let
|
|
124
|
-
private let
|
|
125
|
-
private let
|
|
126
|
-
private let
|
|
127
|
-
private let
|
|
128
|
-
private let
|
|
129
|
-
private let
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
public
|
|
136
|
-
public let
|
|
137
|
-
public
|
|
138
|
-
public var
|
|
139
|
-
public var appId = ""
|
|
221
|
+
|
|
222
|
+
private let versionCode: String = Bundle.main.versionCode ?? ""
|
|
223
|
+
private let versionOs = UIDevice.current.systemVersion
|
|
224
|
+
private let documentsDir: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
|
225
|
+
private let libraryDir: URL = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!
|
|
226
|
+
private let bundleDirectoryHot: String = "versions"
|
|
227
|
+
private let DEFAULT_FOLDER: String = ""
|
|
228
|
+
private let bundleDirectory: String = "NoCloud/ionic_built_snapshots"
|
|
229
|
+
private let INFO_SUFFIX: String = "_info"
|
|
230
|
+
private let FALLBACK_VERSION: String = "pastVersion"
|
|
231
|
+
private let NEXT_VERSION: String = "nextVersion"
|
|
232
|
+
|
|
233
|
+
public let TAG: String = "✨ Capacitor-updater:"
|
|
234
|
+
public let CAP_SERVER_PATH: String = "serverBasePath"
|
|
235
|
+
public var versionName: String = ""
|
|
236
|
+
public var customId: String = ""
|
|
237
|
+
public var PLUGIN_VERSION: String = ""
|
|
238
|
+
public let timeout: Double = 20
|
|
239
|
+
public var statsUrl: String = ""
|
|
240
|
+
public var channelUrl: String = ""
|
|
241
|
+
public var appId: String = ""
|
|
140
242
|
public var deviceID = UIDevice.current.identifierForVendor?.uuidString ?? ""
|
|
141
|
-
|
|
142
|
-
|
|
243
|
+
public var privateKey: String = ""
|
|
244
|
+
|
|
245
|
+
public var notifyDownload: (String, Int) -> Void = { _, _ in }
|
|
143
246
|
|
|
144
247
|
private func calcTotalPercent(percent: Int, min: Int, max: Int) -> Int {
|
|
145
|
-
return (percent * (max - min)) / 100 + min
|
|
248
|
+
return (percent * (max - min)) / 100 + min
|
|
146
249
|
}
|
|
147
|
-
|
|
250
|
+
|
|
148
251
|
private func randomString(length: Int) -> String {
|
|
149
|
-
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
150
|
-
return String((0..<length).map{ _ in letters.randomElement()! })
|
|
252
|
+
let letters: String = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
253
|
+
return String((0..<length).map { _ in letters.randomElement()! })
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private var isDevEnvironment: Bool {
|
|
257
|
+
#if DEBUG
|
|
258
|
+
return true
|
|
259
|
+
#else
|
|
260
|
+
return false
|
|
261
|
+
#endif
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private func isProd() -> Bool {
|
|
265
|
+
return !self.isDevEnvironment && !self.isAppStoreReceiptSandbox() && !self.hasEmbeddedMobileProvision()
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// MARK: Private
|
|
269
|
+
private func hasEmbeddedMobileProvision() -> Bool {
|
|
270
|
+
guard Bundle.main.path(forResource: "embedded", ofType: "mobileprovision") == nil else {
|
|
271
|
+
return true
|
|
272
|
+
}
|
|
273
|
+
return false
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
private func isAppStoreReceiptSandbox() -> Bool {
|
|
277
|
+
|
|
278
|
+
if isEmulator() {
|
|
279
|
+
return false
|
|
280
|
+
} else {
|
|
281
|
+
guard let url: URL = Bundle.main.appStoreReceiptURL else {
|
|
282
|
+
return false
|
|
283
|
+
}
|
|
284
|
+
guard url.lastPathComponent == "sandboxReceipt" else {
|
|
285
|
+
return false
|
|
286
|
+
}
|
|
287
|
+
return true
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private func isEmulator() -> Bool {
|
|
292
|
+
#if targetEnvironment(simulator)
|
|
293
|
+
return true
|
|
294
|
+
#else
|
|
295
|
+
return false
|
|
296
|
+
#endif
|
|
151
297
|
}
|
|
152
|
-
|
|
153
298
|
// Persistent path /var/mobile/Containers/Data/Application/8C0C07BE-0FD3-4FD4-B7DF-90A88E12B8C3/Library/NoCloud/ionic_built_snapshots/FOLDER
|
|
154
299
|
// Hot Reload path /var/mobile/Containers/Data/Application/8C0C07BE-0FD3-4FD4-B7DF-90A88E12B8C3/Documents/FOLDER
|
|
155
300
|
// Normal /private/var/containers/Bundle/Application/8C0C07BE-0FD3-4FD4-B7DF-90A88E12B8C3/App.app/public
|
|
156
|
-
|
|
301
|
+
|
|
157
302
|
private func prepareFolder(source: URL) throws {
|
|
158
|
-
if
|
|
303
|
+
if !FileManager.default.fileExists(atPath: source.path) {
|
|
159
304
|
do {
|
|
160
305
|
try FileManager.default.createDirectory(atPath: source.path, withIntermediateDirectories: true, attributes: nil)
|
|
161
306
|
} catch {
|
|
@@ -164,7 +309,7 @@ extension CustomError: LocalizedError {
|
|
|
164
309
|
}
|
|
165
310
|
}
|
|
166
311
|
}
|
|
167
|
-
|
|
312
|
+
|
|
168
313
|
private func deleteFolder(source: URL) throws {
|
|
169
314
|
do {
|
|
170
315
|
try FileManager.default.removeItem(atPath: source.path)
|
|
@@ -173,12 +318,12 @@ extension CustomError: LocalizedError {
|
|
|
173
318
|
throw CustomError.cannotDeleteDirectory
|
|
174
319
|
}
|
|
175
320
|
}
|
|
176
|
-
|
|
321
|
+
|
|
177
322
|
private func unflatFolder(source: URL, dest: URL) throws -> Bool {
|
|
178
|
-
let index = source.appendingPathComponent("index.html")
|
|
323
|
+
let index: URL = source.appendingPathComponent("index.html")
|
|
179
324
|
do {
|
|
180
|
-
let files = try FileManager.default.contentsOfDirectory(atPath: source.path)
|
|
181
|
-
if
|
|
325
|
+
let files: [String] = try FileManager.default.contentsOfDirectory(atPath: source.path)
|
|
326
|
+
if files.count == 1 && source.appendingPathComponent(files[0]).isDirectory && !FileManager.default.fileExists(atPath: index.path) {
|
|
182
327
|
try FileManager.default.moveItem(at: source.appendingPathComponent(files[0]), to: dest)
|
|
183
328
|
return true
|
|
184
329
|
} else {
|
|
@@ -190,77 +335,140 @@ extension CustomError: LocalizedError {
|
|
|
190
335
|
throw CustomError.cannotUnflat
|
|
191
336
|
}
|
|
192
337
|
}
|
|
193
|
-
|
|
338
|
+
|
|
339
|
+
private func getChecksum(filePath: URL) -> String {
|
|
340
|
+
do {
|
|
341
|
+
let fileData: Data = try Data.init(contentsOf: filePath)
|
|
342
|
+
let checksum: uLong = fileData.withUnsafeBytes { crc32(0, $0.bindMemory(to: Bytef.self).baseAddress, uInt(fileData.count)) }
|
|
343
|
+
return String(format: "%08X", checksum).lowercased()
|
|
344
|
+
} catch {
|
|
345
|
+
print("\(self.TAG) Cannot get checksum: \(filePath.path)", error)
|
|
346
|
+
return ""
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
private func decryptFile(filePath: URL, sessionKey: String, version: String) throws {
|
|
351
|
+
if self.privateKey.isEmpty || sessionKey.isEmpty {
|
|
352
|
+
print("\(self.TAG) Cannot found privateKey or sessionKey")
|
|
353
|
+
return
|
|
354
|
+
}
|
|
355
|
+
do {
|
|
356
|
+
guard let rsaPrivateKey: RSAPrivateKey = .load(rsaPrivateKey: self.privateKey) else {
|
|
357
|
+
print("cannot decode privateKey", self.privateKey)
|
|
358
|
+
throw CustomError.cannotDecode
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
let sessionKeyArray: [String] = sessionKey.components(separatedBy: ":")
|
|
362
|
+
guard let ivData: Data = Data(base64Encoded: sessionKeyArray[0]) else {
|
|
363
|
+
print("cannot decode sessionKey", sessionKey)
|
|
364
|
+
throw CustomError.cannotDecode
|
|
365
|
+
}
|
|
366
|
+
let sessionKeyDataEncrypted: Data = Data(base64Encoded: sessionKeyArray[1])!
|
|
367
|
+
let sessionKeyDataDecrypted: Data = rsaPrivateKey.decrypt(data: sessionKeyDataEncrypted)!
|
|
368
|
+
let aesPrivateKey: AES128Key = AES128Key(iv: ivData, aes128Key: sessionKeyDataDecrypted)
|
|
369
|
+
let encryptedData: Data = try Data(contentsOf: filePath)
|
|
370
|
+
let decryptedData: Data = aesPrivateKey.decrypt(data: encryptedData)!
|
|
371
|
+
|
|
372
|
+
try decryptedData.write(to: filePath)
|
|
373
|
+
} catch {
|
|
374
|
+
print("\(self.TAG) Cannot decode: \(filePath.path)", error)
|
|
375
|
+
self.sendStats(action: "decrypt_fail", versionName: version)
|
|
376
|
+
throw CustomError.cannotDecode
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
194
380
|
private func saveDownloaded(sourceZip: URL, id: String, base: URL) throws {
|
|
195
381
|
try prepareFolder(source: base)
|
|
196
|
-
let destHot = base.appendingPathComponent(id)
|
|
197
|
-
let destUnZip = documentsDir.appendingPathComponent(randomString(length: 10))
|
|
198
|
-
if
|
|
382
|
+
let destHot: URL = base.appendingPathComponent(id)
|
|
383
|
+
let destUnZip: URL = documentsDir.appendingPathComponent(randomString(length: 10))
|
|
384
|
+
if !SSZipArchive.unzipFile(atPath: sourceZip.path, toDestination: destUnZip.path) {
|
|
199
385
|
throw CustomError.cannotUnzip
|
|
200
386
|
}
|
|
201
|
-
if
|
|
387
|
+
if try unflatFolder(source: destUnZip, dest: destHot) {
|
|
202
388
|
try deleteFolder(source: destUnZip)
|
|
203
389
|
}
|
|
204
390
|
}
|
|
205
391
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
392
|
+
private func createInfoObject() -> InfoObject {
|
|
393
|
+
return InfoObject(
|
|
394
|
+
platform: "ios",
|
|
395
|
+
device_id: self.deviceID,
|
|
396
|
+
app_id: self.appId,
|
|
397
|
+
custom_id: self.customId,
|
|
398
|
+
version_build: self.versionName,
|
|
399
|
+
version_code: self.versionCode,
|
|
400
|
+
version_os: self.versionOs,
|
|
401
|
+
version_name: self.getCurrentBundle().getVersionName(),
|
|
402
|
+
plugin_version: self.PLUGIN_VERSION,
|
|
403
|
+
is_emulator: self.isEmulator(),
|
|
404
|
+
is_prod: self.isProd(),
|
|
405
|
+
action: nil,
|
|
406
|
+
channel: nil
|
|
407
|
+
)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
public func getLatest(url: URL) -> AppVersion {
|
|
411
|
+
let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
|
|
412
|
+
let latest: AppVersion = AppVersion()
|
|
413
|
+
let parameters: InfoObject = self.createInfoObject()
|
|
414
|
+
print("\(self.TAG) Auto-update parameters: \(parameters)")
|
|
415
|
+
let request = AF.request(url, method: .post, parameters: parameters, encoder: JSONParameterEncoder.default, requestModifier: { $0.timeoutInterval = self.timeout })
|
|
220
416
|
|
|
221
417
|
request.validate().responseDecodable(of: AppVersionDec.self) { response in
|
|
222
418
|
switch response.result {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
419
|
+
case .success:
|
|
420
|
+
if let url = response.value?.url {
|
|
421
|
+
latest.url = url
|
|
422
|
+
}
|
|
423
|
+
if let checksum = response.value?.checksum {
|
|
424
|
+
latest.checksum = checksum
|
|
425
|
+
}
|
|
426
|
+
if let version = response.value?.version {
|
|
427
|
+
latest.version = version
|
|
428
|
+
}
|
|
429
|
+
if let major = response.value?.major {
|
|
430
|
+
latest.major = major
|
|
431
|
+
}
|
|
432
|
+
if let error = response.value?.error {
|
|
433
|
+
latest.error = error
|
|
434
|
+
}
|
|
435
|
+
if let message = response.value?.message {
|
|
436
|
+
latest.message = message
|
|
437
|
+
}
|
|
438
|
+
if let sessionKey = response.value?.session_key {
|
|
439
|
+
latest.sessionKey = sessionKey
|
|
440
|
+
}
|
|
441
|
+
case let .failure(error):
|
|
442
|
+
print("\(self.TAG) Error getting Latest", response.value ?? "", error )
|
|
443
|
+
latest.message = "Error getting Latest \(String(describing: response.value))"
|
|
444
|
+
latest.error = "response_error"
|
|
239
445
|
}
|
|
240
446
|
semaphore.signal()
|
|
241
447
|
}
|
|
242
448
|
semaphore.wait()
|
|
243
|
-
return latest
|
|
449
|
+
return latest
|
|
244
450
|
}
|
|
245
|
-
|
|
451
|
+
|
|
246
452
|
private func setCurrentBundle(bundle: String) {
|
|
247
453
|
UserDefaults.standard.set(bundle, forKey: self.CAP_SERVER_PATH)
|
|
248
|
-
print("\(self.TAG) Current bundle set to: \(bundle)")
|
|
249
454
|
UserDefaults.standard.synchronize()
|
|
455
|
+
print("\(self.TAG) Current bundle set to: \((bundle ).isEmpty ? BundleInfo.ID_BUILTIN : bundle)")
|
|
250
456
|
}
|
|
251
457
|
|
|
252
|
-
public func download(url: URL, version: String) throws -> BundleInfo {
|
|
253
|
-
let semaphore = DispatchSemaphore(value: 0)
|
|
458
|
+
public func download(url: URL, version: String, sessionKey: String) throws -> BundleInfo {
|
|
459
|
+
let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
|
|
254
460
|
let id: String = self.randomString(length: 10)
|
|
255
|
-
var
|
|
461
|
+
var checksum: String = ""
|
|
462
|
+
|
|
463
|
+
var mainError: NSError?
|
|
256
464
|
let destination: DownloadRequest.Destination = { _, _ in
|
|
257
|
-
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
|
258
|
-
let fileURL = documentsURL.appendingPathComponent(self.randomString(length: 10))
|
|
465
|
+
let documentsURL: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
|
466
|
+
let fileURL: URL = documentsURL.appendingPathComponent(self.randomString(length: 10))
|
|
259
467
|
|
|
260
468
|
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
|
|
261
469
|
}
|
|
262
470
|
let request = AF.download(url, to: destination)
|
|
263
|
-
|
|
471
|
+
|
|
264
472
|
request.downloadProgress { progress in
|
|
265
473
|
let percent = self.calcTotalPercent(percent: Int(progress.fractionCompleted * 100), min: 10, max: 70)
|
|
266
474
|
self.notifyDownload(id, percent)
|
|
@@ -271,6 +479,8 @@ extension CustomError: LocalizedError {
|
|
|
271
479
|
case .success:
|
|
272
480
|
self.notifyDownload(id, 71)
|
|
273
481
|
do {
|
|
482
|
+
try self.decryptFile(filePath: fileURL, sessionKey: sessionKey, version: version)
|
|
483
|
+
checksum = self.getChecksum(filePath: fileURL)
|
|
274
484
|
try self.saveDownloaded(sourceZip: fileURL, id: id, base: self.documentsDir.appendingPathComponent(self.bundleDirectoryHot))
|
|
275
485
|
self.notifyDownload(id, 85)
|
|
276
486
|
try self.saveDownloaded(sourceZip: fileURL, id: id, base: self.libraryDir.appendingPathComponent(self.bundleDirectory))
|
|
@@ -281,32 +491,32 @@ extension CustomError: LocalizedError {
|
|
|
281
491
|
mainError = error as NSError
|
|
282
492
|
}
|
|
283
493
|
case let .failure(error):
|
|
284
|
-
print("\(self.TAG) download error", error)
|
|
494
|
+
print("\(self.TAG) download error", response.value ?? "", error)
|
|
285
495
|
mainError = error as NSError
|
|
286
496
|
}
|
|
287
497
|
}
|
|
288
498
|
semaphore.signal()
|
|
289
499
|
}
|
|
290
|
-
self.saveBundleInfo(id: id, bundle: BundleInfo(id: id, version: version, status: BundleStatus.DOWNLOADING, downloaded: Date()))
|
|
500
|
+
self.saveBundleInfo(id: id, bundle: BundleInfo(id: id, version: version, status: BundleStatus.DOWNLOADING, downloaded: Date(), checksum: checksum))
|
|
291
501
|
self.notifyDownload(id, 0)
|
|
292
502
|
semaphore.wait()
|
|
293
|
-
if
|
|
503
|
+
if mainError != nil {
|
|
294
504
|
throw mainError!
|
|
295
505
|
}
|
|
296
|
-
let info: BundleInfo = BundleInfo(id: id, version: version, status: BundleStatus.PENDING, downloaded: Date())
|
|
506
|
+
let info: BundleInfo = BundleInfo(id: id, version: version, status: BundleStatus.PENDING, downloaded: Date(), checksum: checksum)
|
|
297
507
|
self.saveBundleInfo(id: id, bundle: info)
|
|
298
508
|
return info
|
|
299
509
|
}
|
|
300
510
|
|
|
301
511
|
public func list() -> [BundleInfo] {
|
|
302
|
-
let dest = documentsDir.appendingPathComponent(bundleDirectoryHot)
|
|
512
|
+
let dest: URL = documentsDir.appendingPathComponent(bundleDirectoryHot)
|
|
303
513
|
do {
|
|
304
|
-
let files = try FileManager.default.contentsOfDirectory(atPath: dest.path)
|
|
514
|
+
let files: [String] = try FileManager.default.contentsOfDirectory(atPath: dest.path)
|
|
305
515
|
var res: [BundleInfo] = []
|
|
306
516
|
print("\(self.TAG) list File : \(dest.path)")
|
|
307
|
-
if
|
|
308
|
-
for id in files {
|
|
309
|
-
res.append(self.getBundleInfo(id: id))
|
|
517
|
+
if dest.exist {
|
|
518
|
+
for id: String in files {
|
|
519
|
+
res.append(self.getBundleInfo(id: id))
|
|
310
520
|
}
|
|
311
521
|
}
|
|
312
522
|
return res
|
|
@@ -315,11 +525,15 @@ extension CustomError: LocalizedError {
|
|
|
315
525
|
return []
|
|
316
526
|
}
|
|
317
527
|
}
|
|
318
|
-
|
|
319
|
-
public func delete(id: String) -> Bool {
|
|
528
|
+
|
|
529
|
+
public func delete(id: String, removeInfo: Bool) -> Bool {
|
|
320
530
|
let deleted: BundleInfo = self.getBundleInfo(id: id)
|
|
321
|
-
|
|
322
|
-
|
|
531
|
+
if deleted.isBuiltin() || self.getCurrentBundleId() == id {
|
|
532
|
+
print("\(self.TAG) Cannot delete \(id)")
|
|
533
|
+
return false
|
|
534
|
+
}
|
|
535
|
+
let destHot: URL = documentsDir.appendingPathComponent(bundleDirectoryHot).appendingPathComponent(id)
|
|
536
|
+
let destPersist: URL = libraryDir.appendingPathComponent(bundleDirectory).appendingPathComponent(id)
|
|
323
537
|
do {
|
|
324
538
|
try FileManager.default.removeItem(atPath: destHot.path)
|
|
325
539
|
} catch {
|
|
@@ -331,107 +545,224 @@ extension CustomError: LocalizedError {
|
|
|
331
545
|
print("\(self.TAG) Folder \(destPersist.path), not removed.")
|
|
332
546
|
return false
|
|
333
547
|
}
|
|
334
|
-
|
|
335
|
-
|
|
548
|
+
if removeInfo {
|
|
549
|
+
self.removeBundleInfo(id: id)
|
|
550
|
+
} else {
|
|
551
|
+
self.saveBundleInfo(id: id, bundle: deleted.setStatus(status: BundleStatus.DELETED.localizedString))
|
|
552
|
+
}
|
|
553
|
+
print("\(self.TAG) bundle delete \(deleted.getVersionName())")
|
|
554
|
+
self.sendStats(action: "delete", versionName: deleted.getVersionName())
|
|
336
555
|
return true
|
|
337
556
|
}
|
|
338
557
|
|
|
558
|
+
public func delete(id: String) -> Bool {
|
|
559
|
+
return self.delete(id: id, removeInfo: true)
|
|
560
|
+
}
|
|
561
|
+
|
|
339
562
|
public func getBundleDirectory(id: String) -> URL {
|
|
340
563
|
return libraryDir.appendingPathComponent(self.bundleDirectory).appendingPathComponent(id)
|
|
341
564
|
}
|
|
342
565
|
|
|
343
566
|
public func set(bundle: BundleInfo) -> Bool {
|
|
344
|
-
return self.set(id: bundle.getId())
|
|
567
|
+
return self.set(id: bundle.getId())
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
private func bundleExists(id: String) -> Bool {
|
|
571
|
+
let destHot: URL = self.getPathHot(id: id)
|
|
572
|
+
let destHotPersist: URL = self.getPathPersist(id: id)
|
|
573
|
+
let indexHot: URL = destHot.appendingPathComponent("index.html")
|
|
574
|
+
let indexPersist: URL = destHotPersist.appendingPathComponent("index.html")
|
|
575
|
+
let url: URL = self.getBundleDirectory(id: id)
|
|
576
|
+
let bundleIndo: BundleInfo = self.getBundleInfo(id: id)
|
|
577
|
+
if url.isDirectory && destHotPersist.isDirectory && indexHot.exist && indexPersist.exist && !bundleIndo.isDeleted() {
|
|
578
|
+
return true
|
|
579
|
+
}
|
|
580
|
+
return false
|
|
345
581
|
}
|
|
346
582
|
|
|
347
583
|
public func set(id: String) -> Bool {
|
|
348
|
-
let
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
if (bundle.isDirectory && destHotPersist.isDirectory && indexHot.exist && indexPersist.exist) {
|
|
356
|
-
self.setCurrentBundle(bundle: String(bundle.path.suffix(10)))
|
|
584
|
+
let newBundle: BundleInfo = self.getBundleInfo(id: id)
|
|
585
|
+
if newBundle.isBuiltin() {
|
|
586
|
+
self.reset()
|
|
587
|
+
return true
|
|
588
|
+
}
|
|
589
|
+
if bundleExists(id: id) {
|
|
590
|
+
self.setCurrentBundle(bundle: self.getBundleDirectory(id: id).path)
|
|
357
591
|
self.setBundleStatus(id: id, status: BundleStatus.PENDING)
|
|
358
|
-
sendStats(action: "set",
|
|
592
|
+
self.sendStats(action: "set", versionName: newBundle.getVersionName())
|
|
359
593
|
return true
|
|
360
594
|
}
|
|
361
|
-
|
|
595
|
+
self.setBundleStatus(id: id, status: BundleStatus.ERROR)
|
|
596
|
+
self.sendStats(action: "set_fail", versionName: newBundle.getVersionName())
|
|
362
597
|
return false
|
|
363
598
|
}
|
|
364
|
-
|
|
599
|
+
|
|
365
600
|
public func getPathHot(id: String) -> URL {
|
|
366
601
|
return documentsDir.appendingPathComponent(self.bundleDirectoryHot).appendingPathComponent(id)
|
|
367
602
|
}
|
|
368
|
-
|
|
603
|
+
|
|
369
604
|
public func getPathPersist(id: String) -> URL {
|
|
370
605
|
return libraryDir.appendingPathComponent(self.bundleDirectory).appendingPathComponent(id)
|
|
371
606
|
}
|
|
372
|
-
|
|
607
|
+
|
|
373
608
|
public func reset() {
|
|
374
609
|
self.reset(isInternal: false)
|
|
375
610
|
}
|
|
376
|
-
|
|
611
|
+
|
|
377
612
|
public func reset(isInternal: Bool) {
|
|
613
|
+
print("\(self.TAG) reset: \(isInternal)")
|
|
378
614
|
self.setCurrentBundle(bundle: "")
|
|
379
|
-
self.
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
sendStats(action: "reset", bundle: self.getCurrentBundle())
|
|
615
|
+
self.setFallbackBundle(fallback: Optional<BundleInfo>.none)
|
|
616
|
+
_ = self.setNextBundle(next: Optional<String>.none)
|
|
617
|
+
if !isInternal {
|
|
618
|
+
self.sendStats(action: "reset", versionName: self.getCurrentBundle().getVersionName())
|
|
384
619
|
}
|
|
385
620
|
}
|
|
386
|
-
|
|
387
|
-
public func
|
|
621
|
+
|
|
622
|
+
public func setSuccess(bundle: BundleInfo, autoDeletePrevious: Bool) {
|
|
388
623
|
self.setBundleStatus(id: bundle.getId(), status: BundleStatus.SUCCESS)
|
|
389
|
-
self.
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
624
|
+
let fallback: BundleInfo = self.getFallbackBundle()
|
|
625
|
+
print("\(self.TAG) Fallback bundle is: \(fallback.toString())")
|
|
626
|
+
print("\(self.TAG) Version successfully loaded: \(bundle.toString())")
|
|
627
|
+
if autoDeletePrevious && !fallback.isBuiltin() {
|
|
628
|
+
let res = self.delete(id: fallback.getId())
|
|
629
|
+
if res {
|
|
630
|
+
print("\(self.TAG) Deleted previous bundle: \(fallback.toString())")
|
|
631
|
+
} else {
|
|
632
|
+
print("\(self.TAG) Failed to delete previous bundle: \(fallback.toString())")
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
self.setFallbackBundle(fallback: bundle)
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
public func setError(bundle: BundleInfo) {
|
|
639
|
+
self.setBundleStatus(id: bundle.getId(), status: BundleStatus.ERROR)
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
func setChannel(channel: String) -> SetChannel {
|
|
643
|
+
let setChannel: SetChannel = SetChannel()
|
|
644
|
+
if (self.channelUrl ).isEmpty {
|
|
645
|
+
print("\(self.TAG) Channel URL is not set")
|
|
646
|
+
setChannel.message = "Channel URL is not set"
|
|
647
|
+
setChannel.error = "missing_config"
|
|
648
|
+
return setChannel
|
|
649
|
+
}
|
|
650
|
+
let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
|
|
651
|
+
var parameters: InfoObject = self.createInfoObject()
|
|
652
|
+
parameters.channel = channel
|
|
653
|
+
|
|
654
|
+
let request = AF.request(self.channelUrl, method: .post, parameters: parameters, encoder: JSONParameterEncoder.default, requestModifier: { $0.timeoutInterval = self.timeout })
|
|
655
|
+
|
|
656
|
+
request.validate().responseDecodable(of: SetChannelDec.self) { response in
|
|
657
|
+
switch response.result {
|
|
658
|
+
case .success:
|
|
659
|
+
if let status = response.value?.status {
|
|
660
|
+
setChannel.status = status
|
|
661
|
+
}
|
|
662
|
+
if let error = response.value?.error {
|
|
663
|
+
setChannel.error = error
|
|
664
|
+
}
|
|
665
|
+
if let message = response.value?.message {
|
|
666
|
+
setChannel.message = message
|
|
667
|
+
}
|
|
668
|
+
case let .failure(error):
|
|
669
|
+
print("\(self.TAG) Error set Channel", response.value ?? "", error)
|
|
670
|
+
setChannel.message = "Error set Channel \(String(describing: response.value))"
|
|
671
|
+
setChannel.error = "response_error"
|
|
672
|
+
}
|
|
673
|
+
semaphore.signal()
|
|
674
|
+
}
|
|
675
|
+
semaphore.wait()
|
|
676
|
+
return setChannel
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
func getChannel() -> GetChannel {
|
|
680
|
+
let getChannel: GetChannel = GetChannel()
|
|
681
|
+
if (self.channelUrl ).isEmpty {
|
|
682
|
+
print("\(self.TAG) Channel URL is not set")
|
|
683
|
+
getChannel.message = "Channel URL is not set"
|
|
684
|
+
getChannel.error = "missing_config"
|
|
685
|
+
return getChannel
|
|
686
|
+
}
|
|
687
|
+
let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
|
|
688
|
+
let parameters: InfoObject = self.createInfoObject()
|
|
689
|
+
let request = AF.request(self.channelUrl, method: .put, parameters: parameters, encoder: JSONParameterEncoder.default, requestModifier: { $0.timeoutInterval = self.timeout })
|
|
409
690
|
|
|
691
|
+
request.validate().responseDecodable(of: GetChannelDec.self) { response in
|
|
692
|
+
switch response.result {
|
|
693
|
+
case .success:
|
|
694
|
+
if let status = response.value?.status {
|
|
695
|
+
getChannel.status = status
|
|
696
|
+
}
|
|
697
|
+
if let error = response.value?.error {
|
|
698
|
+
getChannel.error = error
|
|
699
|
+
}
|
|
700
|
+
if let message = response.value?.message {
|
|
701
|
+
getChannel.message = message
|
|
702
|
+
}
|
|
703
|
+
if let channel = response.value?.channel {
|
|
704
|
+
getChannel.channel = channel
|
|
705
|
+
}
|
|
706
|
+
if let allowSet = response.value?.allowSet {
|
|
707
|
+
getChannel.allowSet = allowSet
|
|
708
|
+
}
|
|
709
|
+
case let .failure(error):
|
|
710
|
+
print("\(self.TAG) Error get Channel", response.value ?? "", error)
|
|
711
|
+
getChannel.message = "Error get Channel \(String(describing: response.value)))"
|
|
712
|
+
getChannel.error = "response_error"
|
|
713
|
+
}
|
|
714
|
+
semaphore.signal()
|
|
715
|
+
}
|
|
716
|
+
semaphore.wait()
|
|
717
|
+
return getChannel
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
func sendStats(action: String, versionName: String) {
|
|
721
|
+
if (self.statsUrl ).isEmpty {
|
|
722
|
+
return
|
|
723
|
+
}
|
|
724
|
+
var parameters: InfoObject = self.createInfoObject()
|
|
725
|
+
parameters.action = action
|
|
410
726
|
DispatchQueue.global(qos: .background).async {
|
|
411
|
-
let
|
|
412
|
-
|
|
727
|
+
let request = AF.request(self.statsUrl, method: .post, parameters: parameters, encoder: JSONParameterEncoder.default, requestModifier: { $0.timeoutInterval = self.timeout })
|
|
728
|
+
request.responseData { response in
|
|
729
|
+
switch response.result {
|
|
730
|
+
case .success:
|
|
731
|
+
print("\(self.TAG) Stats send for \(action), version \(versionName)")
|
|
732
|
+
case let .failure(error):
|
|
733
|
+
print("\(self.TAG) Error sending stats: ", response.value ?? "", error)
|
|
734
|
+
}
|
|
735
|
+
}
|
|
413
736
|
}
|
|
414
737
|
}
|
|
415
738
|
|
|
416
|
-
public func getBundleInfo(id: String
|
|
417
|
-
|
|
418
|
-
if
|
|
419
|
-
|
|
739
|
+
public func getBundleInfo(id: String?) -> BundleInfo {
|
|
740
|
+
var trueId = BundleInfo.VERSION_UNKNOWN
|
|
741
|
+
if id != nil {
|
|
742
|
+
trueId = id!
|
|
420
743
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
|
|
744
|
+
print("\(self.TAG) Getting info for bundle [\(trueId)]")
|
|
745
|
+
let result: BundleInfo
|
|
746
|
+
if BundleInfo.ID_BUILTIN == trueId {
|
|
747
|
+
result = BundleInfo(id: trueId, version: "", status: BundleStatus.SUCCESS, checksum: "")
|
|
748
|
+
} else if BundleInfo.VERSION_UNKNOWN == trueId {
|
|
749
|
+
result = BundleInfo(id: trueId, version: "", status: BundleStatus.ERROR, checksum: "")
|
|
750
|
+
} else {
|
|
751
|
+
do {
|
|
752
|
+
result = try UserDefaults.standard.getObj(forKey: "\(trueId)\(self.INFO_SUFFIX)", castTo: BundleInfo.self)
|
|
753
|
+
} catch {
|
|
754
|
+
print("\(self.TAG) Failed to parse info for bundle [\(trueId)]", error.localizedDescription)
|
|
755
|
+
result = BundleInfo(id: trueId, version: "", status: BundleStatus.PENDING, checksum: "")
|
|
756
|
+
}
|
|
428
757
|
}
|
|
758
|
+
// print("\(self.TAG) Returning info bundle [\(result.toString())]")
|
|
759
|
+
return result
|
|
429
760
|
}
|
|
430
761
|
|
|
431
762
|
public func getBundleInfoByVersionName(version: String) -> BundleInfo? {
|
|
432
|
-
let installed
|
|
763
|
+
let installed: [BundleInfo] = self.list()
|
|
433
764
|
for i in installed {
|
|
434
|
-
if
|
|
765
|
+
if i.getVersionName() == version {
|
|
435
766
|
return i
|
|
436
767
|
}
|
|
437
768
|
}
|
|
@@ -443,11 +774,11 @@ extension CustomError: LocalizedError {
|
|
|
443
774
|
}
|
|
444
775
|
|
|
445
776
|
private func saveBundleInfo(id: String, bundle: BundleInfo?) {
|
|
446
|
-
if
|
|
447
|
-
print("\(self.TAG) Not saving info for bundle [\(id)]", bundle
|
|
777
|
+
if bundle != nil && (bundle!.isBuiltin() || bundle!.isUnknown()) {
|
|
778
|
+
print("\(self.TAG) Not saving info for bundle [\(id)]", bundle?.toString() ?? "")
|
|
448
779
|
return
|
|
449
780
|
}
|
|
450
|
-
if
|
|
781
|
+
if bundle == nil {
|
|
451
782
|
print("\(self.TAG) Removing info for bundle [\(id)]")
|
|
452
783
|
UserDefaults.standard.removeObject(forKey: "\(id)\(self.INFO_SUFFIX)")
|
|
453
784
|
} else {
|
|
@@ -474,57 +805,54 @@ extension CustomError: LocalizedError {
|
|
|
474
805
|
self.saveBundleInfo(id: id, bundle: info.setStatus(status: status.localizedString))
|
|
475
806
|
}
|
|
476
807
|
|
|
477
|
-
private func getCurrentBundleVersion() -> String {
|
|
478
|
-
if(self.isUsingBuiltin()) {
|
|
479
|
-
return BundleInfo.ID_BUILTIN
|
|
480
|
-
} else {
|
|
481
|
-
let path: String = self.getCurrentBundleId()
|
|
482
|
-
return path.lastPathComponent
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
808
|
public func getCurrentBundle() -> BundleInfo {
|
|
487
|
-
return self.getBundleInfo(id: self.getCurrentBundleId())
|
|
809
|
+
return self.getBundleInfo(id: self.getCurrentBundleId())
|
|
488
810
|
}
|
|
489
811
|
|
|
490
812
|
public func getCurrentBundleId() -> String {
|
|
491
|
-
|
|
813
|
+
guard let bundlePath: String = UserDefaults.standard.string(forKey: self.CAP_SERVER_PATH) else {
|
|
814
|
+
return BundleInfo.ID_BUILTIN
|
|
815
|
+
}
|
|
816
|
+
if (bundlePath ).isEmpty {
|
|
817
|
+
return BundleInfo.ID_BUILTIN
|
|
818
|
+
}
|
|
819
|
+
let bundleID: String = bundlePath.components(separatedBy: "/").last ?? bundlePath
|
|
820
|
+
return bundleID
|
|
492
821
|
}
|
|
493
822
|
|
|
494
823
|
public func isUsingBuiltin() -> Bool {
|
|
495
|
-
return self.
|
|
824
|
+
return (UserDefaults.standard.string(forKey: self.CAP_SERVER_PATH) ?? "") == self.DEFAULT_FOLDER
|
|
496
825
|
}
|
|
497
826
|
|
|
498
|
-
public func
|
|
827
|
+
public func getFallbackBundle() -> BundleInfo {
|
|
499
828
|
let id: String = UserDefaults.standard.string(forKey: self.FALLBACK_VERSION) ?? BundleInfo.ID_BUILTIN
|
|
500
829
|
return self.getBundleInfo(id: id)
|
|
501
830
|
}
|
|
502
831
|
|
|
503
|
-
private func
|
|
832
|
+
private func setFallbackBundle(fallback: BundleInfo?) {
|
|
504
833
|
UserDefaults.standard.set(fallback == nil ? BundleInfo.ID_BUILTIN : fallback!.getId(), forKey: self.FALLBACK_VERSION)
|
|
834
|
+
UserDefaults.standard.synchronize()
|
|
505
835
|
}
|
|
506
836
|
|
|
507
|
-
public func
|
|
508
|
-
let id: String = UserDefaults.standard.string(forKey: self.NEXT_VERSION)
|
|
509
|
-
|
|
510
|
-
return self.getBundleInfo(id: id)
|
|
511
|
-
} else {
|
|
512
|
-
return nil
|
|
513
|
-
}
|
|
837
|
+
public func getNextBundle() -> BundleInfo? {
|
|
838
|
+
let id: String? = UserDefaults.standard.string(forKey: self.NEXT_VERSION)
|
|
839
|
+
return self.getBundleInfo(id: id)
|
|
514
840
|
}
|
|
515
841
|
|
|
516
|
-
public func
|
|
517
|
-
|
|
842
|
+
public func setNextBundle(next: String?) -> Bool {
|
|
843
|
+
guard let nextId: String = next else {
|
|
518
844
|
UserDefaults.standard.removeObject(forKey: self.NEXT_VERSION)
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
845
|
+
UserDefaults.standard.synchronize()
|
|
846
|
+
return false
|
|
847
|
+
}
|
|
848
|
+
let newBundle: BundleInfo = self.getBundleInfo(id: nextId)
|
|
849
|
+
let bundle: URL = self.getBundleDirectory(id: nextId)
|
|
850
|
+
if !newBundle.isBuiltin() && !bundle.exist {
|
|
851
|
+
return false
|
|
526
852
|
}
|
|
853
|
+
UserDefaults.standard.set(nextId, forKey: self.NEXT_VERSION)
|
|
527
854
|
UserDefaults.standard.synchronize()
|
|
855
|
+
self.setBundleStatus(id: nextId, status: BundleStatus.PENDING)
|
|
528
856
|
return true
|
|
529
857
|
}
|
|
530
858
|
}
|