@capgo/capacitor-updater 6.1.17 → 6.1.19
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/README.md +1 -5
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdater.java +130 -82
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +11 -30
- package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipherV2.java +4 -9
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +2 -15
- package/dist/docs.json +0 -32
- package/dist/esm/definitions.d.ts +0 -15
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Plugin/CapacitorUpdater.swift +222 -173
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +23 -32
- package/ios/Plugin/CryptoCipher.swift +1 -36
- package/ios/Plugin/CryptoCipherV2.swift +4 -62
- package/package.json +1 -1
|
@@ -9,11 +9,12 @@ import SSZipArchive
|
|
|
9
9
|
import Alamofire
|
|
10
10
|
import zlib
|
|
11
11
|
import SwiftyRSA
|
|
12
|
+
import CryptoKit
|
|
12
13
|
|
|
13
14
|
extension Collection {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
subscript(safe index: Index) -> Element? {
|
|
16
|
+
return indices.contains(index) ? self[index] : nil
|
|
17
|
+
}
|
|
17
18
|
}
|
|
18
19
|
extension URL {
|
|
19
20
|
var isDirectory: Bool {
|
|
@@ -97,7 +98,6 @@ struct AppVersionDec: Decodable {
|
|
|
97
98
|
let session_key: String?
|
|
98
99
|
let major: Bool?
|
|
99
100
|
let data: [String: String]?
|
|
100
|
-
let signature: String?
|
|
101
101
|
}
|
|
102
102
|
public class AppVersion: NSObject {
|
|
103
103
|
var version: String = ""
|
|
@@ -108,7 +108,6 @@ public class AppVersion: NSObject {
|
|
|
108
108
|
var sessionKey: String?
|
|
109
109
|
var major: Bool?
|
|
110
110
|
var data: [String: String]?
|
|
111
|
-
var signature: String?
|
|
112
111
|
}
|
|
113
112
|
|
|
114
113
|
extension AppVersion {
|
|
@@ -177,8 +176,8 @@ enum CustomError: Error {
|
|
|
177
176
|
case cannotUnflat
|
|
178
177
|
case cannotCreateDirectory
|
|
179
178
|
case cannotDeleteDirectory
|
|
180
|
-
case
|
|
181
|
-
case
|
|
179
|
+
case cannotDecryptSessionKey
|
|
180
|
+
case invalidBase64
|
|
182
181
|
|
|
183
182
|
// Throw in all other cases
|
|
184
183
|
case unexpected(code: Int)
|
|
@@ -187,16 +186,6 @@ enum CustomError: Error {
|
|
|
187
186
|
extension CustomError: LocalizedError {
|
|
188
187
|
public var errorDescription: String? {
|
|
189
188
|
switch self {
|
|
190
|
-
case .signatureNotProvided:
|
|
191
|
-
return NSLocalizedString(
|
|
192
|
-
"Signature was required but none was provided",
|
|
193
|
-
comment: "Signature not provided"
|
|
194
|
-
)
|
|
195
|
-
case .invalidSignature:
|
|
196
|
-
return NSLocalizedString(
|
|
197
|
-
"Signature is not valid, cannot accept update",
|
|
198
|
-
comment: "Invalid signature"
|
|
199
|
-
)
|
|
200
189
|
case .cannotUnzip:
|
|
201
190
|
return NSLocalizedString(
|
|
202
191
|
"The file cannot be unzip",
|
|
@@ -225,19 +214,29 @@ extension CustomError: LocalizedError {
|
|
|
225
214
|
case .cannotDecode:
|
|
226
215
|
return NSLocalizedString(
|
|
227
216
|
"Decoding the zip failed with this key",
|
|
228
|
-
comment: "Invalid
|
|
217
|
+
comment: "Invalid public key"
|
|
229
218
|
)
|
|
230
219
|
case .cannotWrite:
|
|
231
220
|
return NSLocalizedString(
|
|
232
221
|
"Cannot write to the destination",
|
|
233
222
|
comment: "Invalid destination"
|
|
234
223
|
)
|
|
224
|
+
case .cannotDecryptSessionKey:
|
|
225
|
+
return NSLocalizedString(
|
|
226
|
+
"Decrypting the session key failed",
|
|
227
|
+
comment: "Invalid session key"
|
|
228
|
+
)
|
|
229
|
+
case .invalidBase64:
|
|
230
|
+
return NSLocalizedString(
|
|
231
|
+
"Decrypting the base64 failed",
|
|
232
|
+
comment: "Invalid checksum key"
|
|
233
|
+
)
|
|
235
234
|
}
|
|
236
235
|
}
|
|
237
236
|
}
|
|
238
237
|
|
|
239
238
|
@objc public class CapacitorUpdater: NSObject {
|
|
240
|
-
|
|
239
|
+
|
|
241
240
|
private let versionCode: String = Bundle.main.versionCode ?? ""
|
|
242
241
|
private let versionOs = UIDevice.current.systemVersion
|
|
243
242
|
private let libraryDir: URL = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!
|
|
@@ -247,7 +246,7 @@ extension CustomError: LocalizedError {
|
|
|
247
246
|
private let FALLBACK_VERSION: String = "pastVersion"
|
|
248
247
|
private let NEXT_VERSION: String = "nextVersion"
|
|
249
248
|
private var unzipPercent = 0
|
|
250
|
-
|
|
249
|
+
|
|
251
250
|
public let TAG: String = "✨ Capacitor-updater:"
|
|
252
251
|
public let CAP_SERVER_PATH: String = "serverBasePath"
|
|
253
252
|
public var versionBuild: String = ""
|
|
@@ -260,8 +259,9 @@ extension CustomError: LocalizedError {
|
|
|
260
259
|
public var appId: String = ""
|
|
261
260
|
public var deviceID = ""
|
|
262
261
|
public var privateKey: String = ""
|
|
263
|
-
public var
|
|
264
|
-
|
|
262
|
+
public var publicKey: String = ""
|
|
263
|
+
public var hasOldPrivateKeyPropertyInConfig: Bool = false
|
|
264
|
+
|
|
265
265
|
public var notifyDownloadRaw: (String, Int, Bool) -> Void = { _, _, _ in }
|
|
266
266
|
public func notifyDownload(id: String, percent: Int, ignoreMultipleOfTen: Bool = false) {
|
|
267
267
|
notifyDownloadRaw(id, percent, ignoreMultipleOfTen)
|
|
@@ -360,37 +360,56 @@ extension CustomError: LocalizedError {
|
|
|
360
360
|
}
|
|
361
361
|
}
|
|
362
362
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
print("\(self.TAG) Signing not configured")
|
|
368
|
-
return true
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
if (self.signKey != nil && (signature == nil || signature?.isEmpty == true)) {
|
|
372
|
-
print("\(self.TAG) Signature required but none provided")
|
|
373
|
-
self.sendStats(action: "signature_not_provided", versionName: version)
|
|
374
|
-
throw CustomError.signatureNotProvided
|
|
363
|
+
private func decryptFileV2(filePath: URL, sessionKey: String, version: String) throws {
|
|
364
|
+
if self.publicKey.isEmpty || sessionKey.isEmpty || sessionKey.components(separatedBy: ":").count != 2 {
|
|
365
|
+
print("\(self.TAG) Cannot find public key or sessionKey")
|
|
366
|
+
return
|
|
375
367
|
}
|
|
376
|
-
|
|
377
368
|
do {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
369
|
+
guard let rsaPublicKey: RSAPublicKey = .load(rsaPublicKey: self.publicKey) else {
|
|
370
|
+
print("cannot decode publicKey", self.publicKey)
|
|
371
|
+
throw CustomError.cannotDecode
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
let sessionKeyArray: [String] = sessionKey.components(separatedBy: ":")
|
|
375
|
+
guard let ivData: Data = Data(base64Encoded: sessionKeyArray[0]) else {
|
|
376
|
+
print("cannot decode sessionKey", sessionKey)
|
|
377
|
+
throw CustomError.cannotDecode
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
guard let sessionKeyDataEncrypted = Data(base64Encoded: sessionKeyArray[1]) else {
|
|
381
|
+
throw NSError(domain: "Invalid session key data", code: 1, userInfo: nil)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
guard let sessionKeyDataDecrypted = rsaPublicKey.decrypt(data: sessionKeyDataEncrypted) else {
|
|
385
|
+
throw NSError(domain: "Failed to decrypt session key data", code: 2, userInfo: nil)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
let aesPrivateKey = AES128Key(iv: ivData, aes128Key: sessionKeyDataDecrypted)
|
|
389
|
+
|
|
390
|
+
guard let encryptedData = try? Data(contentsOf: filePath) else {
|
|
391
|
+
throw NSError(domain: "Failed to read encrypted data", code: 3, userInfo: nil)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
guard let decryptedData = aesPrivateKey.decrypt(data: encryptedData) else {
|
|
395
|
+
throw NSError(domain: "Failed to decrypt data", code: 4, userInfo: nil)
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
try decryptedData.write(to: filePath)
|
|
399
|
+
|
|
384
400
|
} catch {
|
|
385
|
-
print("\(self.TAG)
|
|
386
|
-
self.sendStats(action: "
|
|
387
|
-
throw
|
|
401
|
+
print("\(self.TAG) Cannot decode: \(filePath.path)", error)
|
|
402
|
+
self.sendStats(action: "decrypt_fail", versionName: version)
|
|
403
|
+
throw CustomError.cannotDecode
|
|
388
404
|
}
|
|
389
405
|
}
|
|
390
406
|
|
|
391
407
|
private func decryptFile(filePath: URL, sessionKey: String, version: String) throws {
|
|
392
|
-
if self.privateKey.isEmpty
|
|
393
|
-
print("\(self.TAG) Cannot found privateKey
|
|
408
|
+
if self.privateKey.isEmpty {
|
|
409
|
+
print("\(self.TAG) Cannot found privateKey")
|
|
410
|
+
return
|
|
411
|
+
} else if sessionKey.isEmpty || sessionKey.components(separatedBy: ":").count != 2 {
|
|
412
|
+
print("\(self.TAG) Cannot found sessionKey")
|
|
394
413
|
return
|
|
395
414
|
}
|
|
396
415
|
do {
|
|
@@ -424,7 +443,7 @@ extension CustomError: LocalizedError {
|
|
|
424
443
|
}
|
|
425
444
|
|
|
426
445
|
try decryptedData.write(to: filePath)
|
|
427
|
-
|
|
446
|
+
|
|
428
447
|
} catch {
|
|
429
448
|
print("\(self.TAG) Cannot decode: \(filePath.path)", error)
|
|
430
449
|
self.sendStats(action: "decrypt_fail", versionName: version)
|
|
@@ -564,9 +583,6 @@ extension CustomError: LocalizedError {
|
|
|
564
583
|
if let data = response.value?.data {
|
|
565
584
|
latest.data = data
|
|
566
585
|
}
|
|
567
|
-
if let signature = response.value?.signature {
|
|
568
|
-
latest.signature = signature
|
|
569
|
-
}
|
|
570
586
|
case let .failure(error):
|
|
571
587
|
print("\(self.TAG) Error getting Latest", response.value ?? "", error )
|
|
572
588
|
latest.message = "Error getting Latest \(String(describing: response.value))"
|
|
@@ -611,7 +627,35 @@ extension CustomError: LocalizedError {
|
|
|
611
627
|
return ""
|
|
612
628
|
}
|
|
613
629
|
}
|
|
614
|
-
|
|
630
|
+
|
|
631
|
+
private func calcChecksumV2(filePath: URL) -> String {
|
|
632
|
+
let bufferSize = 1024 * 1024 * 5 // 5 MB
|
|
633
|
+
var sha256 = SHA256()
|
|
634
|
+
|
|
635
|
+
do {
|
|
636
|
+
let fileHandle = try FileHandle(forReadingFrom: filePath)
|
|
637
|
+
defer {
|
|
638
|
+
fileHandle.closeFile()
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
while autoreleasepool(invoking: {
|
|
642
|
+
let fileData = fileHandle.readData(ofLength: bufferSize)
|
|
643
|
+
if fileData.count > 0 {
|
|
644
|
+
sha256.update(data: fileData)
|
|
645
|
+
return true // Continue
|
|
646
|
+
} else {
|
|
647
|
+
return false // End of file
|
|
648
|
+
}
|
|
649
|
+
}) {}
|
|
650
|
+
|
|
651
|
+
let digest = sha256.finalize()
|
|
652
|
+
return digest.compactMap { String(format: "%02x", $0) }.joined()
|
|
653
|
+
} catch {
|
|
654
|
+
print("\(self.TAG) Cannot get checksum: \(filePath.path)", error)
|
|
655
|
+
return ""
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
615
659
|
private var tempDataPath: URL {
|
|
616
660
|
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("package.tmp")
|
|
617
661
|
}
|
|
@@ -620,26 +664,48 @@ extension CustomError: LocalizedError {
|
|
|
620
664
|
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("update.dat")
|
|
621
665
|
}
|
|
622
666
|
private var tempData = Data()
|
|
623
|
-
|
|
667
|
+
|
|
668
|
+
public func decryptChecksum(checksum: String, version: String) throws -> String {
|
|
669
|
+
if self.publicKey.isEmpty {
|
|
670
|
+
return checksum
|
|
671
|
+
}
|
|
672
|
+
do {
|
|
673
|
+
let checksumBytes: Data = Data(base64Encoded: checksum)!
|
|
674
|
+
guard let rsaPublicKey: RSAPublicKey = .load(rsaPublicKey: self.publicKey) else {
|
|
675
|
+
print("cannot decode publicKey", self.publicKey)
|
|
676
|
+
throw CustomError.cannotDecode
|
|
677
|
+
}
|
|
678
|
+
guard let decryptedChecksum = try? rsaPublicKey.decrypt(data: checksumBytes) else {
|
|
679
|
+
throw NSError(domain: "Failed to decrypt session key data", code: 2, userInfo: nil)
|
|
680
|
+
}
|
|
681
|
+
return decryptedChecksum.base64EncodedString()
|
|
682
|
+
} catch {
|
|
683
|
+
print("\(self.TAG) Cannot decrypt checksum: \(checksum)", error)
|
|
684
|
+
self.sendStats(action: "decrypt_fail", versionName: version)
|
|
685
|
+
throw CustomError.cannotDecode
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
public func download(url: URL, version: String, sessionKey: String) throws -> BundleInfo {
|
|
624
690
|
let id: String = self.randomString(length: 10)
|
|
625
691
|
let semaphore = DispatchSemaphore(value: 0)
|
|
626
|
-
if
|
|
627
|
-
|
|
692
|
+
if version != getLocalUpdateVersion() {
|
|
693
|
+
cleanDownloadData()
|
|
628
694
|
}
|
|
629
695
|
ensureResumableFilesExist()
|
|
630
696
|
saveDownloadInfo(version)
|
|
631
697
|
var checksum = ""
|
|
632
698
|
var targetSize = -1
|
|
633
699
|
var lastSentProgress = 0
|
|
634
|
-
var totalReceivedBytes: Int64 = loadDownloadProgress() //Retrieving the amount of already downloaded data if exist, defined at 0 otherwise
|
|
635
|
-
|
|
636
|
-
//Opening connection for streaming the bytes
|
|
637
|
-
if
|
|
700
|
+
var totalReceivedBytes: Int64 = loadDownloadProgress() // Retrieving the amount of already downloaded data if exist, defined at 0 otherwise
|
|
701
|
+
let requestHeaders: HTTPHeaders = ["Range": "bytes=\(totalReceivedBytes)-"]
|
|
702
|
+
// Opening connection for streaming the bytes
|
|
703
|
+
if totalReceivedBytes == 0 {
|
|
638
704
|
self.notifyDownload(id: id, percent: 0, ignoreMultipleOfTen: true)
|
|
639
705
|
}
|
|
640
706
|
var mainError: NSError?
|
|
641
707
|
let monitor = ClosureEventMonitor()
|
|
642
|
-
monitor.requestDidCompleteTaskWithError = { (
|
|
708
|
+
monitor.requestDidCompleteTaskWithError = { (_, _, error) in
|
|
643
709
|
if error != nil {
|
|
644
710
|
print("\(self.TAG) Downloading failed - ClosureEventMonitor activated")
|
|
645
711
|
mainError = error as NSError?
|
|
@@ -652,37 +718,34 @@ extension CustomError: LocalizedError {
|
|
|
652
718
|
targetSize = (Int(contentLength) ?? -1) + Int(totalReceivedBytes)
|
|
653
719
|
}
|
|
654
720
|
}).responseStream { [weak self] streamResponse in
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
case .complete(_):
|
|
721
|
+
guard let self = self else { return }
|
|
722
|
+
switch streamResponse.event {
|
|
723
|
+
case .stream(let result):
|
|
724
|
+
if case .success(let data) = result {
|
|
725
|
+
self.tempData.append(data)
|
|
726
|
+
|
|
727
|
+
self.savePartialData(startingAt: UInt64(totalReceivedBytes)) // Saving the received data in the package.tmp file
|
|
728
|
+
totalReceivedBytes += Int64(data.count)
|
|
729
|
+
|
|
730
|
+
let percent = max(10, Int((Double(totalReceivedBytes) / Double(targetSize)) * 70.0))
|
|
731
|
+
|
|
732
|
+
let currentMilestone = (percent / 10) * 10
|
|
733
|
+
if currentMilestone > lastSentProgress && currentMilestone <= 70 {
|
|
734
|
+
for milestone in stride(from: lastSentProgress + 10, through: currentMilestone, by: 10) {
|
|
735
|
+
self.notifyDownload(id: id, percent: milestone, ignoreMultipleOfTen: false)
|
|
736
|
+
}
|
|
737
|
+
lastSentProgress = currentMilestone
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
} else {
|
|
741
|
+
print("\(self.TAG) Download failed")
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
case .complete:
|
|
682
745
|
print("\(self.TAG) Download complete, total received bytes: \(totalReceivedBytes)")
|
|
683
|
-
|
|
746
|
+
self.notifyDownload(id: id, percent: 70, ignoreMultipleOfTen: true)
|
|
684
747
|
semaphore.signal()
|
|
685
|
-
|
|
748
|
+
}
|
|
686
749
|
}
|
|
687
750
|
self.saveBundleInfo(id: id, bundle: BundleInfo(id: id, version: version, status: BundleStatus.DOWNLOADING, downloaded: Date(), checksum: checksum))
|
|
688
751
|
let reachabilityManager = NetworkReachabilityManager()
|
|
@@ -700,50 +763,50 @@ extension CustomError: LocalizedError {
|
|
|
700
763
|
semaphore.wait()
|
|
701
764
|
reachabilityManager?.stopListening()
|
|
702
765
|
|
|
703
|
-
if
|
|
766
|
+
if mainError != nil {
|
|
704
767
|
print("\(self.TAG) Failed to download: \(String(describing: mainError))")
|
|
705
768
|
self.saveBundleInfo(id: id, bundle: BundleInfo(id: id, version: version, status: BundleStatus.ERROR, downloaded: Date(), checksum: checksum))
|
|
706
769
|
throw mainError!
|
|
707
770
|
}
|
|
708
|
-
|
|
771
|
+
|
|
709
772
|
let finalPath = tempDataPath.deletingLastPathComponent().appendingPathComponent("\(id)")
|
|
710
773
|
do {
|
|
711
|
-
|
|
712
|
-
if
|
|
713
|
-
|
|
714
|
-
self.sendStats(action: "invalid_signature", versionName: version)
|
|
715
|
-
throw CustomError.invalidSignature
|
|
774
|
+
var checksumDecrypted = checksum
|
|
775
|
+
if !self.hasOldPrivateKeyPropertyInConfig {
|
|
776
|
+
try self.decryptFileV2(filePath: tempDataPath, sessionKey: sessionKey, version: version)
|
|
716
777
|
} else {
|
|
717
|
-
|
|
778
|
+
try self.decryptFile(filePath: tempDataPath, sessionKey: sessionKey, version: version)
|
|
718
779
|
}
|
|
719
|
-
|
|
720
|
-
try self.decryptFile(filePath: tempDataPath, sessionKey: sessionKey, version: version)
|
|
721
780
|
try FileManager.default.moveItem(at: tempDataPath, to: finalPath)
|
|
722
781
|
} catch {
|
|
723
|
-
print("\(self.TAG) Failed decrypt file
|
|
782
|
+
print("\(self.TAG) Failed decrypt file : \(error)")
|
|
724
783
|
self.saveBundleInfo(id: id, bundle: BundleInfo(id: id, version: version, status: BundleStatus.ERROR, downloaded: Date(), checksum: checksum))
|
|
725
|
-
|
|
784
|
+
cleanDownloadData()
|
|
726
785
|
throw error
|
|
727
786
|
}
|
|
728
|
-
|
|
787
|
+
|
|
729
788
|
do {
|
|
730
|
-
|
|
789
|
+
if !self.hasOldPrivateKeyPropertyInConfig && !sessionKey.isEmpty {
|
|
790
|
+
checksum = self.calcChecksumV2(filePath: finalPath)
|
|
791
|
+
} else {
|
|
792
|
+
checksum = self.calcChecksum(filePath: finalPath)
|
|
793
|
+
}
|
|
731
794
|
print("\(self.TAG) Downloading: 80% (unzipping)")
|
|
732
795
|
try self.saveDownloaded(sourceZip: finalPath, id: id, base: self.libraryDir.appendingPathComponent(self.bundleDirectory), notify: true)
|
|
733
|
-
|
|
796
|
+
|
|
734
797
|
} catch {
|
|
735
798
|
print("\(self.TAG) Failed to unzip file: \(error)")
|
|
736
799
|
self.saveBundleInfo(id: id, bundle: BundleInfo(id: id, version: version, status: BundleStatus.ERROR, downloaded: Date(), checksum: checksum))
|
|
737
|
-
|
|
800
|
+
cleanDownloadData()
|
|
738
801
|
// todo: cleanup zip attempts
|
|
739
802
|
throw error
|
|
740
803
|
}
|
|
741
|
-
|
|
804
|
+
|
|
742
805
|
self.notifyDownload(id: id, percent: 90)
|
|
743
806
|
print("\(self.TAG) Downloading: 90% (wrapping up)")
|
|
744
807
|
let info = BundleInfo(id: id, version: version, status: BundleStatus.PENDING, downloaded: Date(), checksum: checksum)
|
|
745
808
|
self.saveBundleInfo(id: id, bundle: info)
|
|
746
|
-
self.
|
|
809
|
+
self.cleanDownloadData()
|
|
747
810
|
self.notifyDownload(id: id, percent: 100)
|
|
748
811
|
print("\(self.TAG) Downloading: 100% (complete)")
|
|
749
812
|
return info
|
|
@@ -755,39 +818,34 @@ extension CustomError: LocalizedError {
|
|
|
755
818
|
print("\(self.TAG) Cannot ensure that a file at \(tempDataPath.path) exists")
|
|
756
819
|
}
|
|
757
820
|
}
|
|
758
|
-
|
|
821
|
+
|
|
759
822
|
if !fileManager.fileExists(atPath: updateInfo.path) {
|
|
760
823
|
if !fileManager.createFile(atPath: updateInfo.path, contents: Data()) {
|
|
761
824
|
print("\(self.TAG) Cannot ensure that a file at \(updateInfo.path) exists")
|
|
762
825
|
}
|
|
763
826
|
}
|
|
764
827
|
}
|
|
765
|
-
|
|
766
|
-
private func
|
|
828
|
+
|
|
829
|
+
private func cleanDownloadData() {
|
|
767
830
|
// Deleting package.tmp
|
|
768
831
|
let fileManager = FileManager.default
|
|
769
832
|
if fileManager.fileExists(atPath: tempDataPath.path) {
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
} else {
|
|
787
|
-
print("\(self.TAG) \(updateInfo.lastPathComponent) does not exist")
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
|
|
833
|
+
do {
|
|
834
|
+
try fileManager.removeItem(at: tempDataPath)
|
|
835
|
+
} catch {
|
|
836
|
+
print("\(self.TAG) Could not delete file at \(tempDataPath): \(error)")
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
// Deleting update.dat
|
|
840
|
+
if fileManager.fileExists(atPath: updateInfo.path) {
|
|
841
|
+
do {
|
|
842
|
+
try fileManager.removeItem(at: updateInfo)
|
|
843
|
+
} catch {
|
|
844
|
+
print("\(self.TAG) Could not delete file at \(updateInfo): \(error)")
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
791
849
|
private func savePartialData(startingAt byteOffset: UInt64) {
|
|
792
850
|
let fileManager = FileManager.default
|
|
793
851
|
do {
|
|
@@ -807,7 +865,6 @@ extension CustomError: LocalizedError {
|
|
|
807
865
|
self.tempData.removeAll() // Clearing tempData to avoid writing the same data multiple times
|
|
808
866
|
}
|
|
809
867
|
|
|
810
|
-
|
|
811
868
|
private func saveDownloadInfo(_ version: String) {
|
|
812
869
|
do {
|
|
813
870
|
try "\(version)".write(to: updateInfo, atomically: true, encoding: .utf8)
|
|
@@ -815,34 +872,30 @@ extension CustomError: LocalizedError {
|
|
|
815
872
|
print("\(self.TAG) Failed to save progress: \(error)")
|
|
816
873
|
}
|
|
817
874
|
}
|
|
818
|
-
private func getLocalUpdateVersion() -> String { //Return the version that was tried to be downloaded on last download attempt
|
|
875
|
+
private func getLocalUpdateVersion() -> String { // Return the version that was tried to be downloaded on last download attempt
|
|
819
876
|
if !FileManager.default.fileExists(atPath: updateInfo.path) {
|
|
820
877
|
return "nil"
|
|
821
878
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
879
|
+
guard let versionString = try? String(contentsOf: updateInfo),
|
|
880
|
+
let version = Optional(versionString) else {
|
|
881
|
+
return "nil"
|
|
882
|
+
}
|
|
883
|
+
return version
|
|
884
|
+
}
|
|
828
885
|
private func loadDownloadProgress() -> Int64 {
|
|
829
|
-
|
|
886
|
+
|
|
830
887
|
let fileManager = FileManager.default
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
888
|
+
do {
|
|
889
|
+
let attributes = try fileManager.attributesOfItem(atPath: tempDataPath.path)
|
|
890
|
+
if let fileSize = attributes[.size] as? NSNumber {
|
|
891
|
+
return fileSize.int64Value
|
|
892
|
+
}
|
|
893
|
+
} catch {
|
|
894
|
+
print("\(self.TAG) Could not retrieve already downloaded data size : \(error)")
|
|
895
|
+
}
|
|
896
|
+
return 0
|
|
840
897
|
}
|
|
841
898
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
899
|
public func list() -> [BundleInfo] {
|
|
847
900
|
let dest: URL = libraryDir.appendingPathComponent(bundleDirectory)
|
|
848
901
|
do {
|
|
@@ -1100,34 +1153,30 @@ extension CustomError: LocalizedError {
|
|
|
1100
1153
|
parameters.action = action
|
|
1101
1154
|
parameters.version_name = versionName
|
|
1102
1155
|
parameters.old_version_name = oldVersionName ?? ""
|
|
1103
|
-
|
|
1104
|
-
|
|
1156
|
+
|
|
1105
1157
|
let operation = BlockOperation {
|
|
1106
1158
|
let semaphore = DispatchSemaphore(value: 0)
|
|
1107
1159
|
AF.request(
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1160
|
+
self.statsUrl,
|
|
1161
|
+
method: .post,
|
|
1162
|
+
parameters: parameters,
|
|
1163
|
+
encoder: JSONParameterEncoder.default,
|
|
1164
|
+
requestModifier: { $0.timeoutInterval = self.timeout }
|
|
1165
|
+
).responseData { response in
|
|
1166
|
+
switch response.result {
|
|
1167
|
+
case .success:
|
|
1168
|
+
print("\(self.TAG) Stats sent for \(action), version \(versionName)")
|
|
1169
|
+
case let .failure(error):
|
|
1170
|
+
print("\(self.TAG) Error sending stats: ", response.value ?? "", error.localizedDescription)
|
|
1171
|
+
}
|
|
1172
|
+
semaphore.signal()
|
|
1173
|
+
}
|
|
1174
|
+
semaphore.wait()
|
|
1175
|
+
}
|
|
1124
1176
|
operationQueue.addOperation(operation)
|
|
1125
|
-
|
|
1126
1177
|
|
|
1127
|
-
|
|
1128
1178
|
}
|
|
1129
1179
|
|
|
1130
|
-
|
|
1131
1180
|
public func getBundleInfo(id: String?) -> BundleInfo {
|
|
1132
1181
|
var trueId = BundleInfo.VERSION_UNKNOWN
|
|
1133
1182
|
if id != nil {
|