@capgo/capacitor-updater 8.0.0 → 8.1.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 +7 -5
- package/Package.swift +37 -0
- package/README.md +1461 -231
- package/android/build.gradle +29 -12
- package/android/proguard-rules.pro +45 -0
- package/android/src/main/AndroidManifest.xml +0 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleInfo.java +223 -195
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleStatus.java +23 -23
- package/android/src/main/java/ee/forgr/capacitor_updater/Callback.java +13 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +2159 -1234
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +1507 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipher.java +330 -121
- package/android/src/main/java/ee/forgr/capacitor_updater/DataManager.java +28 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayCondition.java +43 -49
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayUntilNext.java +4 -4
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayUpdateUtils.java +260 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DeviceIdHelper.java +221 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +808 -117
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +156 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/InternalUtils.java +32 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/Logger.java +338 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeDetector.java +72 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +169 -0
- package/dist/docs.json +2187 -625
- package/dist/esm/definitions.d.ts +1286 -249
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/history.d.ts +1 -0
- package/dist/esm/history.js +283 -0
- package/dist/esm/history.js.map +1 -0
- package/dist/esm/index.d.ts +3 -2
- package/dist/esm/index.js +5 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +36 -41
- package/dist/esm/web.js +94 -35
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +376 -35
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +376 -35
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/AES.swift +69 -0
- package/ios/Sources/CapacitorUpdaterPlugin/BigInt.swift +55 -0
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/BundleInfo.swift +37 -10
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/BundleStatus.swift +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +1605 -0
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +1526 -0
- package/ios/Sources/CapacitorUpdaterPlugin/CryptoCipher.swift +267 -0
- package/ios/Sources/CapacitorUpdaterPlugin/DelayUpdateUtils.swift +220 -0
- package/ios/Sources/CapacitorUpdaterPlugin/DeviceIdHelper.swift +120 -0
- package/ios/Sources/CapacitorUpdaterPlugin/InternalUtils.swift +311 -0
- package/ios/Sources/CapacitorUpdaterPlugin/Logger.swift +310 -0
- package/ios/Sources/CapacitorUpdaterPlugin/RSA.swift +274 -0
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +112 -0
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/UserDefaultsExtension.swift +0 -2
- package/package.json +41 -35
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdater.java +0 -1130
- package/ios/Plugin/CapacitorUpdater.swift +0 -858
- package/ios/Plugin/CapacitorUpdaterPlugin.h +0 -10
- package/ios/Plugin/CapacitorUpdaterPlugin.m +0 -27
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +0 -675
- package/ios/Plugin/CryptoCipher.swift +0 -240
- /package/{LICENCE → LICENSE} +0 -0
- /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/DelayCondition.swift +0 -0
- /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/DelayUntilNext.swift +0 -0
- /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/Info.plist +0 -0
|
@@ -1,858 +0,0 @@
|
|
|
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
|
-
|
|
7
|
-
import Foundation
|
|
8
|
-
import SSZipArchive
|
|
9
|
-
import Alamofire
|
|
10
|
-
import zlib
|
|
11
|
-
|
|
12
|
-
extension URL {
|
|
13
|
-
var isDirectory: Bool {
|
|
14
|
-
(try? resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == true
|
|
15
|
-
}
|
|
16
|
-
var exist: Bool {
|
|
17
|
-
return FileManager().fileExists(atPath: self.path)
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
extension Date {
|
|
21
|
-
func adding(minutes: Int) -> Date {
|
|
22
|
-
return Calendar.current.date(byAdding: .minute, value: minutes, to: self)!
|
|
23
|
-
}
|
|
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
|
-
}
|
|
88
|
-
struct AppVersionDec: Decodable {
|
|
89
|
-
let version: String?
|
|
90
|
-
let checksum: String?
|
|
91
|
-
let url: String?
|
|
92
|
-
let message: String?
|
|
93
|
-
let error: String?
|
|
94
|
-
let session_key: String?
|
|
95
|
-
let major: Bool?
|
|
96
|
-
}
|
|
97
|
-
public class AppVersion: NSObject {
|
|
98
|
-
var version: String = ""
|
|
99
|
-
var checksum: String = ""
|
|
100
|
-
var url: String = ""
|
|
101
|
-
var message: String?
|
|
102
|
-
var error: String?
|
|
103
|
-
var sessionKey: String?
|
|
104
|
-
var major: Bool?
|
|
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
|
-
|
|
120
|
-
extension OperatingSystemVersion {
|
|
121
|
-
func getFullVersion(separator: String = ".") -> String {
|
|
122
|
-
return "\(majorVersion)\(separator)\(minorVersion)\(separator)\(patchVersion)"
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
extension Bundle {
|
|
126
|
-
var versionName: String? {
|
|
127
|
-
return infoDictionary?["CFBundleShortVersionString"] as? String
|
|
128
|
-
}
|
|
129
|
-
var versionCode: String? {
|
|
130
|
-
return infoDictionary?["CFBundleVersion"] as? String
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
extension ISO8601DateFormatter {
|
|
135
|
-
convenience init(_ formatOptions: Options) {
|
|
136
|
-
self.init()
|
|
137
|
-
self.formatOptions = formatOptions
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
extension Formatter {
|
|
141
|
-
static let iso8601withFractionalSeconds: ISO8601DateFormatter = ISO8601DateFormatter([.withInternetDateTime, .withFractionalSeconds])
|
|
142
|
-
}
|
|
143
|
-
extension Date {
|
|
144
|
-
var iso8601withFractionalSeconds: String { return Formatter.iso8601withFractionalSeconds.string(from: self) }
|
|
145
|
-
}
|
|
146
|
-
extension String {
|
|
147
|
-
|
|
148
|
-
var fileURL: URL {
|
|
149
|
-
return URL(fileURLWithPath: self)
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
var lastPathComponent: String {
|
|
153
|
-
get {
|
|
154
|
-
return fileURL.lastPathComponent
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
var iso8601withFractionalSeconds: Date? {
|
|
158
|
-
return Formatter.iso8601withFractionalSeconds.date(from: self)
|
|
159
|
-
}
|
|
160
|
-
func trim(using characterSet: CharacterSet = .whitespacesAndNewlines) -> String {
|
|
161
|
-
return trimmingCharacters(in: characterSet)
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
enum CustomError: Error {
|
|
166
|
-
// Throw when an unzip fail
|
|
167
|
-
case cannotUnzip
|
|
168
|
-
case cannotWrite
|
|
169
|
-
case cannotDecode
|
|
170
|
-
case cannotUnflat
|
|
171
|
-
case cannotCreateDirectory
|
|
172
|
-
case cannotDeleteDirectory
|
|
173
|
-
|
|
174
|
-
// Throw in all other cases
|
|
175
|
-
case unexpected(code: Int)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
extension CustomError: LocalizedError {
|
|
179
|
-
public var errorDescription: String? {
|
|
180
|
-
switch self {
|
|
181
|
-
case .cannotUnzip:
|
|
182
|
-
return NSLocalizedString(
|
|
183
|
-
"The file cannot be unzip",
|
|
184
|
-
comment: "Invalid zip"
|
|
185
|
-
)
|
|
186
|
-
case .cannotCreateDirectory:
|
|
187
|
-
return NSLocalizedString(
|
|
188
|
-
"The folder cannot be created",
|
|
189
|
-
comment: "Invalid folder"
|
|
190
|
-
)
|
|
191
|
-
case .cannotDeleteDirectory:
|
|
192
|
-
return NSLocalizedString(
|
|
193
|
-
"The folder cannot be deleted",
|
|
194
|
-
comment: "Invalid folder"
|
|
195
|
-
)
|
|
196
|
-
case .cannotUnflat:
|
|
197
|
-
return NSLocalizedString(
|
|
198
|
-
"The file cannot be unflat",
|
|
199
|
-
comment: "Invalid folder"
|
|
200
|
-
)
|
|
201
|
-
case .unexpected:
|
|
202
|
-
return NSLocalizedString(
|
|
203
|
-
"An unexpected error occurred.",
|
|
204
|
-
comment: "Unexpected Error"
|
|
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
|
-
)
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
@objc public class CapacitorUpdater: NSObject {
|
|
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 = ""
|
|
242
|
-
public var deviceID = UIDevice.current.identifierForVendor?.uuidString ?? ""
|
|
243
|
-
public var privateKey: String = ""
|
|
244
|
-
|
|
245
|
-
public var notifyDownload: (String, Int) -> Void = { _, _ in }
|
|
246
|
-
|
|
247
|
-
private func calcTotalPercent(percent: Int, min: Int, max: Int) -> Int {
|
|
248
|
-
return (percent * (max - min)) / 100 + min
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
private func randomString(length: Int) -> String {
|
|
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
|
|
297
|
-
}
|
|
298
|
-
// Persistent path /var/mobile/Containers/Data/Application/8C0C07BE-0FD3-4FD4-B7DF-90A88E12B8C3/Library/NoCloud/ionic_built_snapshots/FOLDER
|
|
299
|
-
// Hot Reload path /var/mobile/Containers/Data/Application/8C0C07BE-0FD3-4FD4-B7DF-90A88E12B8C3/Documents/FOLDER
|
|
300
|
-
// Normal /private/var/containers/Bundle/Application/8C0C07BE-0FD3-4FD4-B7DF-90A88E12B8C3/App.app/public
|
|
301
|
-
|
|
302
|
-
private func prepareFolder(source: URL) throws {
|
|
303
|
-
if !FileManager.default.fileExists(atPath: source.path) {
|
|
304
|
-
do {
|
|
305
|
-
try FileManager.default.createDirectory(atPath: source.path, withIntermediateDirectories: true, attributes: nil)
|
|
306
|
-
} catch {
|
|
307
|
-
print("\(self.TAG) Cannot createDirectory \(source.path)")
|
|
308
|
-
throw CustomError.cannotCreateDirectory
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
private func deleteFolder(source: URL) throws {
|
|
314
|
-
do {
|
|
315
|
-
try FileManager.default.removeItem(atPath: source.path)
|
|
316
|
-
} catch {
|
|
317
|
-
print("\(self.TAG) File not removed. \(source.path)")
|
|
318
|
-
throw CustomError.cannotDeleteDirectory
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
private func unflatFolder(source: URL, dest: URL) throws -> Bool {
|
|
323
|
-
let index: URL = source.appendingPathComponent("index.html")
|
|
324
|
-
do {
|
|
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) {
|
|
327
|
-
try FileManager.default.moveItem(at: source.appendingPathComponent(files[0]), to: dest)
|
|
328
|
-
return true
|
|
329
|
-
} else {
|
|
330
|
-
try FileManager.default.moveItem(at: source, to: dest)
|
|
331
|
-
return false
|
|
332
|
-
}
|
|
333
|
-
} catch {
|
|
334
|
-
print("\(self.TAG) File not moved. source: \(source.path) dest: \(dest.path)")
|
|
335
|
-
throw CustomError.cannotUnflat
|
|
336
|
-
}
|
|
337
|
-
}
|
|
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
|
-
|
|
380
|
-
private func saveDownloaded(sourceZip: URL, id: String, base: URL) throws {
|
|
381
|
-
try prepareFolder(source: base)
|
|
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) {
|
|
385
|
-
throw CustomError.cannotUnzip
|
|
386
|
-
}
|
|
387
|
-
if try unflatFolder(source: destUnZip, dest: destHot) {
|
|
388
|
-
try deleteFolder(source: destUnZip)
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
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 })
|
|
416
|
-
|
|
417
|
-
request.validate().responseDecodable(of: AppVersionDec.self) { response in
|
|
418
|
-
switch response.result {
|
|
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"
|
|
445
|
-
}
|
|
446
|
-
semaphore.signal()
|
|
447
|
-
}
|
|
448
|
-
semaphore.wait()
|
|
449
|
-
return latest
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
private func setCurrentBundle(bundle: String) {
|
|
453
|
-
UserDefaults.standard.set(bundle, forKey: self.CAP_SERVER_PATH)
|
|
454
|
-
UserDefaults.standard.synchronize()
|
|
455
|
-
print("\(self.TAG) Current bundle set to: \((bundle ).isEmpty ? BundleInfo.ID_BUILTIN : bundle)")
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
public func download(url: URL, version: String, sessionKey: String) throws -> BundleInfo {
|
|
459
|
-
let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
|
|
460
|
-
let id: String = self.randomString(length: 10)
|
|
461
|
-
var checksum: String = ""
|
|
462
|
-
|
|
463
|
-
var mainError: NSError?
|
|
464
|
-
let destination: DownloadRequest.Destination = { _, _ in
|
|
465
|
-
let documentsURL: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
|
466
|
-
let fileURL: URL = documentsURL.appendingPathComponent(self.randomString(length: 10))
|
|
467
|
-
|
|
468
|
-
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
|
|
469
|
-
}
|
|
470
|
-
let request = AF.download(url, to: destination)
|
|
471
|
-
|
|
472
|
-
request.downloadProgress { progress in
|
|
473
|
-
let percent = self.calcTotalPercent(percent: Int(progress.fractionCompleted * 100), min: 10, max: 70)
|
|
474
|
-
self.notifyDownload(id, percent)
|
|
475
|
-
}
|
|
476
|
-
request.responseURL { (response) in
|
|
477
|
-
if let fileURL = response.fileURL {
|
|
478
|
-
switch response.result {
|
|
479
|
-
case .success:
|
|
480
|
-
self.notifyDownload(id, 71)
|
|
481
|
-
do {
|
|
482
|
-
try self.decryptFile(filePath: fileURL, sessionKey: sessionKey, version: version)
|
|
483
|
-
checksum = self.getChecksum(filePath: fileURL)
|
|
484
|
-
try self.saveDownloaded(sourceZip: fileURL, id: id, base: self.documentsDir.appendingPathComponent(self.bundleDirectoryHot))
|
|
485
|
-
self.notifyDownload(id, 85)
|
|
486
|
-
try self.saveDownloaded(sourceZip: fileURL, id: id, base: self.libraryDir.appendingPathComponent(self.bundleDirectory))
|
|
487
|
-
self.notifyDownload(id, 100)
|
|
488
|
-
try self.deleteFolder(source: fileURL)
|
|
489
|
-
} catch {
|
|
490
|
-
print("\(self.TAG) download unzip error", error)
|
|
491
|
-
mainError = error as NSError
|
|
492
|
-
}
|
|
493
|
-
case let .failure(error):
|
|
494
|
-
print("\(self.TAG) download error", response.value ?? "", error)
|
|
495
|
-
mainError = error as NSError
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
semaphore.signal()
|
|
499
|
-
}
|
|
500
|
-
self.saveBundleInfo(id: id, bundle: BundleInfo(id: id, version: version, status: BundleStatus.DOWNLOADING, downloaded: Date(), checksum: checksum))
|
|
501
|
-
self.notifyDownload(id, 0)
|
|
502
|
-
semaphore.wait()
|
|
503
|
-
if mainError != nil {
|
|
504
|
-
throw mainError!
|
|
505
|
-
}
|
|
506
|
-
let info: BundleInfo = BundleInfo(id: id, version: version, status: BundleStatus.PENDING, downloaded: Date(), checksum: checksum)
|
|
507
|
-
self.saveBundleInfo(id: id, bundle: info)
|
|
508
|
-
return info
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
public func list() -> [BundleInfo] {
|
|
512
|
-
let dest: URL = documentsDir.appendingPathComponent(bundleDirectoryHot)
|
|
513
|
-
do {
|
|
514
|
-
let files: [String] = try FileManager.default.contentsOfDirectory(atPath: dest.path)
|
|
515
|
-
var res: [BundleInfo] = []
|
|
516
|
-
print("\(self.TAG) list File : \(dest.path)")
|
|
517
|
-
if dest.exist {
|
|
518
|
-
for id: String in files {
|
|
519
|
-
res.append(self.getBundleInfo(id: id))
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
return res
|
|
523
|
-
} catch {
|
|
524
|
-
print("\(self.TAG) No version available \(dest.path)")
|
|
525
|
-
return []
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
public func delete(id: String, removeInfo: Bool) -> Bool {
|
|
530
|
-
let deleted: BundleInfo = self.getBundleInfo(id: id)
|
|
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)
|
|
537
|
-
do {
|
|
538
|
-
try FileManager.default.removeItem(atPath: destHot.path)
|
|
539
|
-
} catch {
|
|
540
|
-
print("\(self.TAG) Hot Folder \(destHot.path), not removed.")
|
|
541
|
-
}
|
|
542
|
-
do {
|
|
543
|
-
try FileManager.default.removeItem(atPath: destPersist.path)
|
|
544
|
-
} catch {
|
|
545
|
-
print("\(self.TAG) Folder \(destPersist.path), not removed.")
|
|
546
|
-
return false
|
|
547
|
-
}
|
|
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())
|
|
555
|
-
return true
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
public func delete(id: String) -> Bool {
|
|
559
|
-
return self.delete(id: id, removeInfo: true)
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
public func getBundleDirectory(id: String) -> URL {
|
|
563
|
-
return libraryDir.appendingPathComponent(self.bundleDirectory).appendingPathComponent(id)
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
public func set(bundle: BundleInfo) -> Bool {
|
|
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
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
public func set(id: String) -> Bool {
|
|
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)
|
|
591
|
-
self.setBundleStatus(id: id, status: BundleStatus.PENDING)
|
|
592
|
-
self.sendStats(action: "set", versionName: newBundle.getVersionName())
|
|
593
|
-
return true
|
|
594
|
-
}
|
|
595
|
-
self.setBundleStatus(id: id, status: BundleStatus.ERROR)
|
|
596
|
-
self.sendStats(action: "set_fail", versionName: newBundle.getVersionName())
|
|
597
|
-
return false
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
public func getPathHot(id: String) -> URL {
|
|
601
|
-
return documentsDir.appendingPathComponent(self.bundleDirectoryHot).appendingPathComponent(id)
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
public func getPathPersist(id: String) -> URL {
|
|
605
|
-
return libraryDir.appendingPathComponent(self.bundleDirectory).appendingPathComponent(id)
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
public func reset() {
|
|
609
|
-
self.reset(isInternal: false)
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
public func reset(isInternal: Bool) {
|
|
613
|
-
print("\(self.TAG) reset: \(isInternal)")
|
|
614
|
-
self.setCurrentBundle(bundle: "")
|
|
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())
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
public func setSuccess(bundle: BundleInfo, autoDeletePrevious: Bool) {
|
|
623
|
-
self.setBundleStatus(id: bundle.getId(), status: BundleStatus.SUCCESS)
|
|
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 })
|
|
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
|
|
726
|
-
DispatchQueue.global(qos: .background).async {
|
|
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
|
-
}
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
public func getBundleInfo(id: String?) -> BundleInfo {
|
|
740
|
-
var trueId = BundleInfo.VERSION_UNKNOWN
|
|
741
|
-
if id != nil {
|
|
742
|
-
trueId = id!
|
|
743
|
-
}
|
|
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
|
-
}
|
|
757
|
-
}
|
|
758
|
-
// print("\(self.TAG) Returning info bundle [\(result.toString())]")
|
|
759
|
-
return result
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
public func getBundleInfoByVersionName(version: String) -> BundleInfo? {
|
|
763
|
-
let installed: [BundleInfo] = self.list()
|
|
764
|
-
for i in installed {
|
|
765
|
-
if i.getVersionName() == version {
|
|
766
|
-
return i
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
return nil
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
private func removeBundleInfo(id: String) {
|
|
773
|
-
self.saveBundleInfo(id: id, bundle: nil)
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
private func saveBundleInfo(id: String, bundle: BundleInfo?) {
|
|
777
|
-
if bundle != nil && (bundle!.isBuiltin() || bundle!.isUnknown()) {
|
|
778
|
-
print("\(self.TAG) Not saving info for bundle [\(id)]", bundle?.toString() ?? "")
|
|
779
|
-
return
|
|
780
|
-
}
|
|
781
|
-
if bundle == nil {
|
|
782
|
-
print("\(self.TAG) Removing info for bundle [\(id)]")
|
|
783
|
-
UserDefaults.standard.removeObject(forKey: "\(id)\(self.INFO_SUFFIX)")
|
|
784
|
-
} else {
|
|
785
|
-
let update = bundle!.setId(id: id)
|
|
786
|
-
print("\(self.TAG) Storing info for bundle [\(id)]", update.toString())
|
|
787
|
-
do {
|
|
788
|
-
try UserDefaults.standard.setObj(update, forKey: "\(id)\(self.INFO_SUFFIX)")
|
|
789
|
-
} catch {
|
|
790
|
-
print("\(self.TAG) Failed to save info for bundle [\(id)]", error.localizedDescription)
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
UserDefaults.standard.synchronize()
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
public func setVersionName(id: String, version: String) {
|
|
797
|
-
print("\(self.TAG) Setting version for folder [\(id)] to \(version)")
|
|
798
|
-
let info = self.getBundleInfo(id: id)
|
|
799
|
-
self.saveBundleInfo(id: id, bundle: info.setVersionName(version: version))
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
private func setBundleStatus(id: String, status: BundleStatus) {
|
|
803
|
-
print("\(self.TAG) Setting status for bundle [\(id)] to \(status)")
|
|
804
|
-
let info = self.getBundleInfo(id: id)
|
|
805
|
-
self.saveBundleInfo(id: id, bundle: info.setStatus(status: status.localizedString))
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
public func getCurrentBundle() -> BundleInfo {
|
|
809
|
-
return self.getBundleInfo(id: self.getCurrentBundleId())
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
public func getCurrentBundleId() -> String {
|
|
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
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
public func isUsingBuiltin() -> Bool {
|
|
824
|
-
return (UserDefaults.standard.string(forKey: self.CAP_SERVER_PATH) ?? "") == self.DEFAULT_FOLDER
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
public func getFallbackBundle() -> BundleInfo {
|
|
828
|
-
let id: String = UserDefaults.standard.string(forKey: self.FALLBACK_VERSION) ?? BundleInfo.ID_BUILTIN
|
|
829
|
-
return self.getBundleInfo(id: id)
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
private func setFallbackBundle(fallback: BundleInfo?) {
|
|
833
|
-
UserDefaults.standard.set(fallback == nil ? BundleInfo.ID_BUILTIN : fallback!.getId(), forKey: self.FALLBACK_VERSION)
|
|
834
|
-
UserDefaults.standard.synchronize()
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
public func getNextBundle() -> BundleInfo? {
|
|
838
|
-
let id: String? = UserDefaults.standard.string(forKey: self.NEXT_VERSION)
|
|
839
|
-
return self.getBundleInfo(id: id)
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
public func setNextBundle(next: String?) -> Bool {
|
|
843
|
-
guard let nextId: String = next else {
|
|
844
|
-
UserDefaults.standard.removeObject(forKey: self.NEXT_VERSION)
|
|
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
|
|
852
|
-
}
|
|
853
|
-
UserDefaults.standard.set(nextId, forKey: self.NEXT_VERSION)
|
|
854
|
-
UserDefaults.standard.synchronize()
|
|
855
|
-
self.setBundleStatus(id: nextId, status: BundleStatus.PENDING)
|
|
856
|
-
return true
|
|
857
|
-
}
|
|
858
|
-
}
|