@hot-updater/react-native 0.18.2 → 0.18.3
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.
|
@@ -88,6 +88,7 @@ class BundleFileStorageService(
|
|
|
88
88
|
): Boolean {
|
|
89
89
|
Log.d("BundleStorage", "updateBundle bundleId $bundleId fileUrl $fileUrl")
|
|
90
90
|
|
|
91
|
+
// If no URL is provided, reset to fallback
|
|
91
92
|
if (fileUrl.isNullOrEmpty()) {
|
|
92
93
|
setBundleURL(null)
|
|
93
94
|
return true
|
|
@@ -104,11 +105,13 @@ class BundleFileStorageService(
|
|
|
104
105
|
Log.d("BundleStorage", "Bundle for bundleId $bundleId already exists. Using cached bundle.")
|
|
105
106
|
val existingIndexFile = finalBundleDir.walk().find { it.name == "index.android.bundle" }
|
|
106
107
|
if (existingIndexFile != null) {
|
|
108
|
+
// Update last modified time and set the cached bundle URL
|
|
107
109
|
finalBundleDir.setLastModified(System.currentTimeMillis())
|
|
108
110
|
setBundleURL(existingIndexFile.absolutePath)
|
|
109
111
|
cleanupOldBundles(bundleStoreDir)
|
|
110
112
|
return true
|
|
111
113
|
} else {
|
|
114
|
+
// If index.android.bundle is missing, delete and re-download
|
|
112
115
|
finalBundleDir.deleteRecursively()
|
|
113
116
|
}
|
|
114
117
|
}
|
|
@@ -120,8 +123,6 @@ class BundleFileStorageService(
|
|
|
120
123
|
tempDir.mkdirs()
|
|
121
124
|
|
|
122
125
|
val tempZipFile = File(tempDir, "bundle.zip")
|
|
123
|
-
val extractedDir = File(tempDir, "extracted")
|
|
124
|
-
extractedDir.mkdirs()
|
|
125
126
|
|
|
126
127
|
return withContext(Dispatchers.IO) {
|
|
127
128
|
val downloadUrl = URL(fileUrl)
|
|
@@ -141,60 +142,96 @@ class BundleFileStorageService(
|
|
|
141
142
|
return@withContext false
|
|
142
143
|
}
|
|
143
144
|
is DownloadResult.Success -> {
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
// 1) Create a .tmp directory under bundle-store (to avoid colliding with an existing bundleId folder)
|
|
146
|
+
val tmpDir = File(bundleStoreDir, "$bundleId.tmp")
|
|
147
|
+
if (tmpDir.exists()) {
|
|
148
|
+
tmpDir.deleteRecursively()
|
|
149
|
+
}
|
|
150
|
+
tmpDir.mkdirs()
|
|
151
|
+
|
|
152
|
+
// 2) Unzip into tmpDir
|
|
153
|
+
Log.d("BundleStorage", "Unzipping $tempZipFile → $tmpDir")
|
|
154
|
+
if (!unzipService.extractZipFile(tempZipFile.absolutePath, tmpDir.absolutePath)) {
|
|
155
|
+
Log.d("BundleStorage", "Failed to extract zip into tmpDir.")
|
|
147
156
|
tempDir.deleteRecursively()
|
|
157
|
+
tmpDir.deleteRecursively()
|
|
148
158
|
return@withContext false
|
|
149
159
|
}
|
|
150
160
|
|
|
151
|
-
// Find
|
|
152
|
-
val
|
|
153
|
-
if (
|
|
154
|
-
Log.d("BundleStorage", "index.android.bundle not found in
|
|
161
|
+
// 3) Find index.android.bundle inside tmpDir
|
|
162
|
+
val extractedIndex = tmpDir.walk().find { it.name == "index.android.bundle" }
|
|
163
|
+
if (extractedIndex == null) {
|
|
164
|
+
Log.d("BundleStorage", "index.android.bundle not found in tmpDir.")
|
|
155
165
|
tempDir.deleteRecursively()
|
|
166
|
+
tmpDir.deleteRecursively()
|
|
156
167
|
return@withContext false
|
|
157
168
|
}
|
|
158
169
|
|
|
159
|
-
//
|
|
170
|
+
// 4) If the realDir (bundle-store/<bundleId>) exists, delete it
|
|
160
171
|
if (finalBundleDir.exists()) {
|
|
161
172
|
finalBundleDir.deleteRecursively()
|
|
162
173
|
}
|
|
163
174
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
175
|
+
// 5) Attempt to rename tmpDir → finalBundleDir (atomic within the same parent folder)
|
|
176
|
+
val renamed = tmpDir.renameTo(finalBundleDir)
|
|
177
|
+
if (!renamed) {
|
|
178
|
+
// If rename fails, use moveItem or copyItem
|
|
179
|
+
if (!fileSystem.moveItem(tmpDir.absolutePath, finalBundleDir.absolutePath)) {
|
|
180
|
+
fileSystem.copyItem(tmpDir.absolutePath, finalBundleDir.absolutePath)
|
|
181
|
+
tmpDir.deleteRecursively()
|
|
182
|
+
}
|
|
167
183
|
}
|
|
168
184
|
|
|
185
|
+
// 6) Verify index.android.bundle exists inside finalBundleDir
|
|
169
186
|
val finalIndexFile = finalBundleDir.walk().find { it.name == "index.android.bundle" }
|
|
170
187
|
if (finalIndexFile == null) {
|
|
171
|
-
Log.d("BundleStorage", "index.android.bundle not found in
|
|
188
|
+
Log.d("BundleStorage", "index.android.bundle not found in realDir.")
|
|
172
189
|
tempDir.deleteRecursively()
|
|
190
|
+
finalBundleDir.deleteRecursively()
|
|
173
191
|
return@withContext false
|
|
174
192
|
}
|
|
175
193
|
|
|
194
|
+
// 7) Update finalBundleDir's last modified time
|
|
176
195
|
finalBundleDir.setLastModified(System.currentTimeMillis())
|
|
196
|
+
|
|
197
|
+
// 8) Save the new bundle path in Preferences
|
|
177
198
|
val bundlePath = finalIndexFile.absolutePath
|
|
178
199
|
Log.d("BundleStorage", "Setting bundle URL: $bundlePath")
|
|
179
200
|
setBundleURL(bundlePath)
|
|
180
|
-
|
|
201
|
+
|
|
202
|
+
// 9) Clean up temporary and download folders
|
|
181
203
|
tempDir.deleteRecursively()
|
|
182
204
|
|
|
183
|
-
|
|
205
|
+
// 10) Remove old bundles
|
|
206
|
+
cleanupOldBundles(bundleStoreDir)
|
|
207
|
+
|
|
208
|
+
Log.d("BundleStorage", "Downloaded and activated bundle successfully.")
|
|
184
209
|
return@withContext true
|
|
185
210
|
}
|
|
186
211
|
}
|
|
187
212
|
}
|
|
188
213
|
}
|
|
189
214
|
|
|
215
|
+
/**
|
|
216
|
+
* Removes older bundles and any leftover .tmp directories
|
|
217
|
+
*/
|
|
190
218
|
private fun cleanupOldBundles(bundleStoreDir: File) {
|
|
191
|
-
|
|
219
|
+
// List only directories that are not .tmp
|
|
220
|
+
val bundles = bundleStoreDir.listFiles { file -> file.isDirectory && !file.name.endsWith(".tmp") }?.toList() ?: return
|
|
221
|
+
// Sort bundles by last modified (newest first)
|
|
192
222
|
val sortedBundles = bundles.sortedByDescending { it.lastModified() }
|
|
193
223
|
if (sortedBundles.size > 1) {
|
|
224
|
+
// Keep the most recent bundle, delete the rest
|
|
194
225
|
sortedBundles.drop(1).forEach { oldBundle ->
|
|
195
226
|
Log.d("BundleStorage", "Removing old bundle: ${oldBundle.name}")
|
|
196
227
|
oldBundle.deleteRecursively()
|
|
197
228
|
}
|
|
198
229
|
}
|
|
230
|
+
|
|
231
|
+
// Remove any leftover .tmp directories
|
|
232
|
+
bundleStoreDir.listFiles { file -> file.isDirectory && file.name.endsWith(".tmp") }?.forEach { staleTmp ->
|
|
233
|
+
Log.d("BundleStorage", "Removing stale tmp directory: ${staleTmp.name}")
|
|
234
|
+
staleTmp.deleteRecursively()
|
|
235
|
+
}
|
|
199
236
|
}
|
|
200
237
|
}
|
|
@@ -196,7 +196,12 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
196
196
|
for item in contents {
|
|
197
197
|
let fullPath = (storeDir as NSString).appendingPathComponent(item)
|
|
198
198
|
|
|
199
|
-
|
|
199
|
+
// Skip .tmp directories
|
|
200
|
+
if item.hasSuffix(".tmp") {
|
|
201
|
+
continue
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if let currentPath = currentBundlePath, fullPath == currentPath {
|
|
200
205
|
continue
|
|
201
206
|
}
|
|
202
207
|
|
|
@@ -230,6 +235,18 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
230
235
|
var removedCount = 0
|
|
231
236
|
for item in contents {
|
|
232
237
|
let fullPath = (storeDir as NSString).appendingPathComponent(item)
|
|
238
|
+
// Skip .tmp directories as well
|
|
239
|
+
if item.hasSuffix(".tmp") {
|
|
240
|
+
// Clean up any stale .tmp directories
|
|
241
|
+
do {
|
|
242
|
+
try self.fileSystem.removeItem(atPath: fullPath)
|
|
243
|
+
NSLog("[BundleStorage] Removed stale tmp directory: \(fullPath)")
|
|
244
|
+
} catch {
|
|
245
|
+
NSLog("[BundleStorage] Failed to remove stale tmp directory \(fullPath): \(error)")
|
|
246
|
+
}
|
|
247
|
+
continue
|
|
248
|
+
}
|
|
249
|
+
|
|
233
250
|
if !bundlesToKeep.contains(fullPath) {
|
|
234
251
|
do {
|
|
235
252
|
try self.fileSystem.removeItem(atPath: fullPath)
|
|
@@ -237,7 +254,6 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
237
254
|
NSLog("[BundleStorage] Removed old bundle: \(item)")
|
|
238
255
|
} catch {
|
|
239
256
|
NSLog("[BundleStorage] Failed to remove old bundle at \(fullPath): \(error)")
|
|
240
|
-
// Optionally, collect errors and return a multiple error type or first error
|
|
241
257
|
}
|
|
242
258
|
}
|
|
243
259
|
}
|
|
@@ -332,7 +348,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
332
348
|
return
|
|
333
349
|
}
|
|
334
350
|
|
|
335
|
-
// Start the bundle update process
|
|
351
|
+
// Start the bundle update process on a background queue
|
|
336
352
|
fileOperationQueue.async {
|
|
337
353
|
let storeDirResult = self.bundleStoreDir()
|
|
338
354
|
guard case .success(let storeDir) = storeDirResult else {
|
|
@@ -349,8 +365,6 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
349
365
|
if let bundlePath = existingBundlePath {
|
|
350
366
|
NSLog("[BundleStorage] Using cached bundle at path: \(bundlePath)")
|
|
351
367
|
do {
|
|
352
|
-
try self.fileSystem.setAttributes([.modificationDate: Date()], ofItemAtPath: finalBundleDir)
|
|
353
|
-
|
|
354
368
|
let setResult = self.setBundleURL(localPath: bundlePath)
|
|
355
369
|
switch setResult {
|
|
356
370
|
case .success:
|
|
@@ -360,7 +374,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
360
374
|
completion(.success(true))
|
|
361
375
|
case .failure(let error):
|
|
362
376
|
NSLog("[BundleStorage] Warning: Cleanup failed but bundle is set: \(error)")
|
|
363
|
-
completion(.failure(error))
|
|
377
|
+
completion(.failure(error))
|
|
364
378
|
}
|
|
365
379
|
case .failure(let error):
|
|
366
380
|
completion(.failure(error))
|
|
@@ -373,9 +387,8 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
373
387
|
NSLog("[BundleStorage] Cached directory exists but invalid, removing: \(finalBundleDir)")
|
|
374
388
|
do {
|
|
375
389
|
try self.fileSystem.removeItem(atPath: finalBundleDir)
|
|
376
|
-
// Continue with download process on success
|
|
377
|
-
|
|
378
|
-
self.prepareAndDownloadBundle(bundleId: bundleId, fileUrl: validFileUrl, finalBundleDir: finalBundleDir, completion: completion)
|
|
390
|
+
// Continue with download process on success
|
|
391
|
+
self.prepareAndDownloadBundle(bundleId: bundleId, fileUrl: validFileUrl, storeDir: storeDir, completion: completion)
|
|
379
392
|
} catch let error {
|
|
380
393
|
NSLog("[BundleStorage] Failed to remove invalid bundle dir: \(error.localizedDescription)")
|
|
381
394
|
completion(.failure(BundleStorageError.fileSystemError(error)))
|
|
@@ -385,7 +398,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
385
398
|
completion(.failure(error))
|
|
386
399
|
}
|
|
387
400
|
} else {
|
|
388
|
-
self.prepareAndDownloadBundle(bundleId: bundleId, fileUrl: validFileUrl,
|
|
401
|
+
self.prepareAndDownloadBundle(bundleId: bundleId, fileUrl: validFileUrl, storeDir: storeDir, completion: completion)
|
|
389
402
|
}
|
|
390
403
|
}
|
|
391
404
|
}
|
|
@@ -395,46 +408,44 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
395
408
|
* This method is part of the asynchronous `updateBundle` flow.
|
|
396
409
|
* @param bundleId ID of the bundle to update
|
|
397
410
|
* @param fileUrl URL of the bundle file to download
|
|
398
|
-
* @param
|
|
411
|
+
* @param storeDir Path to the bundle-store directory
|
|
399
412
|
* @param completion Callback with result of the operation
|
|
400
413
|
*/
|
|
401
|
-
private func prepareAndDownloadBundle(
|
|
402
|
-
|
|
414
|
+
private func prepareAndDownloadBundle(
|
|
415
|
+
bundleId: String,
|
|
416
|
+
fileUrl: URL,
|
|
417
|
+
storeDir: String,
|
|
418
|
+
completion: @escaping (Result<Bool, Error>) -> Void
|
|
419
|
+
) {
|
|
420
|
+
// 1) Prepare temp directory for download
|
|
403
421
|
let tempDirResult = tempDir()
|
|
404
422
|
guard case .success(let tempDirectory) = tempDirResult else {
|
|
405
423
|
completion(.failure(tempDirResult.failureError ?? BundleStorageError.unknown(nil)))
|
|
406
424
|
return
|
|
407
425
|
}
|
|
408
426
|
|
|
409
|
-
//
|
|
410
|
-
// This is already within a fileOperationQueue.async block from updateBundle or needs to be if called directly.
|
|
411
|
-
// For safety, ensure this block runs on the fileOperationQueue if it's not already.
|
|
412
|
-
// However, prepareAndDownloadBundle is called from an existing fileOperationQueue.async block in updateBundle.
|
|
413
|
-
|
|
414
|
-
// Clean up any previous temp dir (sync operation)
|
|
427
|
+
// 2) Clean up any previous temp dir
|
|
415
428
|
try? self.fileSystem.removeItem(atPath: tempDirectory)
|
|
416
429
|
|
|
417
|
-
// Create
|
|
430
|
+
// 3) Create temp dir
|
|
418
431
|
if !self.fileSystem.createDirectory(atPath: tempDirectory) {
|
|
419
432
|
completion(.failure(BundleStorageError.directoryCreationFailed))
|
|
420
433
|
return
|
|
421
434
|
}
|
|
422
435
|
|
|
436
|
+
// 4) Define paths for ZIP file
|
|
423
437
|
let tempZipFile = (tempDirectory as NSString).appendingPathComponent("bundle.zip")
|
|
424
|
-
let extractedDir = (tempDirectory as NSString).appendingPathComponent("extracted")
|
|
425
|
-
|
|
426
|
-
if !self.fileSystem.createDirectory(atPath: extractedDir) {
|
|
427
|
-
completion(.failure(BundleStorageError.directoryCreationFailed))
|
|
428
|
-
return
|
|
429
|
-
}
|
|
430
438
|
|
|
431
439
|
NSLog("[BundleStorage] Starting download from \(fileUrl)")
|
|
432
440
|
|
|
433
|
-
// DownloadService handles its own threading for the download task.
|
|
441
|
+
// 5) DownloadService handles its own threading for the download task.
|
|
434
442
|
// The completion handler for downloadService.downloadFile is then dispatched to fileOperationQueue.
|
|
435
|
-
let task = self.downloadService.downloadFile(from: fileUrl,
|
|
436
|
-
|
|
437
|
-
|
|
443
|
+
let task = self.downloadService.downloadFile(from: fileUrl,
|
|
444
|
+
to: tempZipFile,
|
|
445
|
+
progressHandler: { _ in
|
|
446
|
+
// Progress updates handled by notification system
|
|
447
|
+
},
|
|
448
|
+
completion: { [weak self] result in
|
|
438
449
|
guard let self = self else {
|
|
439
450
|
let error = NSError(domain: "HotUpdaterError", code: 998,
|
|
440
451
|
userInfo: [NSLocalizedDescriptionKey: "Self deallocated during download"])
|
|
@@ -446,16 +457,12 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
446
457
|
let workItem = DispatchWorkItem {
|
|
447
458
|
switch result {
|
|
448
459
|
case .success(let location):
|
|
449
|
-
self.
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
tempDirectory: tempDirectory,
|
|
456
|
-
completion: completion
|
|
457
|
-
)
|
|
458
|
-
|
|
460
|
+
self.processDownloadedFileWithTmp(location: location,
|
|
461
|
+
tempZipFile: tempZipFile,
|
|
462
|
+
storeDir: storeDir,
|
|
463
|
+
bundleId: bundleId,
|
|
464
|
+
tempDirectory: tempDirectory,
|
|
465
|
+
completion: completion)
|
|
459
466
|
case .failure(let error):
|
|
460
467
|
NSLog("[BundleStorage] Download failed: \(error.localizedDescription)")
|
|
461
468
|
self.cleanupTemporaryFiles([tempDirectory]) // Sync cleanup
|
|
@@ -471,30 +478,27 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
471
478
|
}
|
|
472
479
|
|
|
473
480
|
/**
|
|
474
|
-
* Processes a downloaded bundle file.
|
|
481
|
+
* Processes a downloaded bundle file using the “.tmp” rename approach.
|
|
475
482
|
* This method is part of the asynchronous `updateBundle` flow and is expected to run on a background thread.
|
|
476
483
|
* @param location URL of the downloaded file
|
|
477
484
|
* @param tempZipFile Path to store the downloaded zip file
|
|
478
|
-
* @param
|
|
479
|
-
* @param finalBundleDir Final directory for the bundle
|
|
485
|
+
* @param storeDir Path to the bundle-store directory
|
|
480
486
|
* @param bundleId ID of the bundle being processed
|
|
481
487
|
* @param tempDirectory Temporary directory for processing
|
|
482
488
|
* @param completion Callback with result of the operation
|
|
483
489
|
*/
|
|
484
|
-
private func
|
|
490
|
+
private func processDownloadedFileWithTmp(
|
|
485
491
|
location: URL,
|
|
486
492
|
tempZipFile: String,
|
|
487
|
-
|
|
488
|
-
finalBundleDir: String,
|
|
493
|
+
storeDir: String,
|
|
489
494
|
bundleId: String,
|
|
490
495
|
tempDirectory: String,
|
|
491
496
|
completion: @escaping (Result<Bool, Error>) -> Void
|
|
492
497
|
) {
|
|
493
498
|
NSLog("[BundleStorage] Processing downloaded file atPath: \(location.path)")
|
|
494
499
|
|
|
495
|
-
// 1
|
|
500
|
+
// 1) Ensure the ZIP file exists
|
|
496
501
|
guard self.fileSystem.fileExists(atPath: location.path) else {
|
|
497
|
-
NSLog("[BundleStorage] Source file does not exist atPath: \(location.path)")
|
|
498
502
|
self.cleanupTemporaryFiles([tempDirectory])
|
|
499
503
|
completion(.failure(BundleStorageError.fileSystemError(NSError(
|
|
500
504
|
domain: "HotUpdaterError",
|
|
@@ -503,79 +507,81 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
503
507
|
))))
|
|
504
508
|
return
|
|
505
509
|
}
|
|
506
|
-
|
|
507
|
-
// 2
|
|
510
|
+
|
|
511
|
+
// 2) Define tmpDir and realDir
|
|
512
|
+
let tmpDir = (storeDir as NSString).appendingPathComponent("\(bundleId).tmp")
|
|
513
|
+
let realDir = (storeDir as NSString).appendingPathComponent(bundleId)
|
|
514
|
+
|
|
508
515
|
do {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
try self.fileSystem.createDirectory(atPath: tempZipFileDirectory.path)
|
|
514
|
-
NSLog("[BundleStorage] Created directory atPath: \(tempZipFileDirectory.path)")
|
|
516
|
+
// 3) Remove any existing tmpDir
|
|
517
|
+
if self.fileSystem.fileExists(atPath: tmpDir) {
|
|
518
|
+
try self.fileSystem.removeItem(atPath: tmpDir)
|
|
519
|
+
NSLog("[BundleStorage] Removed existing tmpDir: \(tmpDir)")
|
|
515
520
|
}
|
|
516
|
-
NSLog("[BundleStorage] Successfully downloaded file to: \(tempZipFile)")
|
|
517
|
-
|
|
518
|
-
// 3. Unzip the file
|
|
519
|
-
try self.unzipService.unzip(file: tempZipFile, to: extractedDir)
|
|
520
|
-
NSLog("[BundleStorage] Successfully extracted to: \(extractedDir)")
|
|
521
521
|
|
|
522
|
-
//
|
|
522
|
+
// 4) Create tmpDir
|
|
523
|
+
try self.fileSystem.createDirectory(atPath: tmpDir)
|
|
524
|
+
NSLog("[BundleStorage] Created tmpDir: \(tmpDir)")
|
|
525
|
+
|
|
526
|
+
// 5) Unzip directly into tmpDir
|
|
527
|
+
NSLog("[BundleStorage] Unzipping \(tempZipFile) → \(tmpDir)")
|
|
528
|
+
try self.unzipService.unzip(file: tempZipFile, to: tmpDir)
|
|
529
|
+
NSLog("[BundleStorage] Unzip complete at \(tmpDir)")
|
|
530
|
+
|
|
531
|
+
// 6) Remove the downloaded ZIP file
|
|
523
532
|
try? self.fileSystem.removeItem(atPath: tempZipFile)
|
|
524
533
|
|
|
525
|
-
// 7
|
|
526
|
-
switch self.findBundleFile(in:
|
|
527
|
-
case .success(let
|
|
528
|
-
if let
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
try self.fileSystem.createDirectory(atPath: finalBundleDir)
|
|
534
|
-
NSLog("[BundleStorage] Created final bundle directory atPath: \(finalBundleDir)")
|
|
534
|
+
// 7) Verify that a valid bundle file exists inside tmpDir
|
|
535
|
+
switch self.findBundleFile(in: tmpDir) {
|
|
536
|
+
case .success(let maybeBundlePath):
|
|
537
|
+
if let bundlePathInTmp = maybeBundlePath {
|
|
538
|
+
// 8) Remove any existing realDir
|
|
539
|
+
if self.fileSystem.fileExists(atPath: realDir) {
|
|
540
|
+
try self.fileSystem.removeItem(atPath: realDir)
|
|
541
|
+
NSLog("[BundleStorage] Removed existing realDir: \(realDir)")
|
|
535
542
|
}
|
|
536
543
|
|
|
537
|
-
// 9
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
}
|
|
541
|
-
try self.fileSystem.moveItem(atPath: extractedDir, toPath: finalBundleDir)
|
|
542
|
-
NSLog("[BundleStorage] Successfully moved entire bundle directory to: \(finalBundleDir)")
|
|
544
|
+
// 9) Rename (move) tmpDir → realDir
|
|
545
|
+
try self.fileSystem.moveItem(atPath: tmpDir, toPath: realDir)
|
|
546
|
+
NSLog("[BundleStorage] Renamed tmpDir to realDir: \(realDir)")
|
|
543
547
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
completion(.success(true))
|
|
553
|
-
case .failure(let error):
|
|
554
|
-
self.cleanupTemporaryFiles([tempDirectory])
|
|
555
|
-
completion(.failure(error))
|
|
556
|
-
}
|
|
557
|
-
} else {
|
|
558
|
-
NSLog("[BundleStorage] No bundle file found in final directory")
|
|
559
|
-
self.cleanupTemporaryFiles([tempDirectory])
|
|
560
|
-
completion(.failure(BundleStorageError.invalidBundle))
|
|
561
|
-
}
|
|
562
|
-
case .failure(let error):
|
|
563
|
-
NSLog("[BundleStorage] Error finding bundle file: \(error.localizedDescription)")
|
|
548
|
+
// 10) Construct final bundlePath for preferences
|
|
549
|
+
let finalBundlePath = (realDir as NSString).appendingPathComponent((bundlePathInTmp as NSString).lastPathComponent)
|
|
550
|
+
|
|
551
|
+
// 11) Set the bundle URL in preferences
|
|
552
|
+
let setResult = self.setBundleURL(localPath: finalBundlePath)
|
|
553
|
+
switch setResult {
|
|
554
|
+
case .success:
|
|
555
|
+
// 12) Clean up the temporary directory
|
|
564
556
|
self.cleanupTemporaryFiles([tempDirectory])
|
|
565
|
-
|
|
557
|
+
|
|
558
|
+
// 13) Clean up old bundles, preserving current and latest
|
|
559
|
+
let _ = self.cleanupOldBundles(currentBundleId: bundleId)
|
|
560
|
+
|
|
561
|
+
// 14) Complete with success
|
|
562
|
+
completion(.success(true))
|
|
563
|
+
case .failure(let err):
|
|
564
|
+
// Preferences save failed → remove realDir and clean up
|
|
565
|
+
try? self.fileSystem.removeItem(atPath: realDir)
|
|
566
|
+
self.cleanupTemporaryFiles([tempDirectory])
|
|
567
|
+
completion(.failure(err))
|
|
566
568
|
}
|
|
567
569
|
} else {
|
|
568
|
-
|
|
570
|
+
// No valid .jsbundle found → delete tmpDir and fail
|
|
571
|
+
try? self.fileSystem.removeItem(atPath: tmpDir)
|
|
569
572
|
self.cleanupTemporaryFiles([tempDirectory])
|
|
570
573
|
completion(.failure(BundleStorageError.invalidBundle))
|
|
571
574
|
}
|
|
572
|
-
case .failure(let
|
|
573
|
-
|
|
575
|
+
case .failure(let findError):
|
|
576
|
+
// Error scanning tmpDir → delete tmpDir and fail
|
|
577
|
+
try? self.fileSystem.removeItem(atPath: tmpDir)
|
|
574
578
|
self.cleanupTemporaryFiles([tempDirectory])
|
|
575
|
-
completion(.failure(
|
|
579
|
+
completion(.failure(findError))
|
|
576
580
|
}
|
|
577
581
|
} catch let error {
|
|
578
|
-
|
|
582
|
+
// Any failure during unzip or rename → clean tmpDir and fail
|
|
583
|
+
NSLog("[BundleStorage] Error during tmpDir processing: \(error)")
|
|
584
|
+
try? self.fileSystem.removeItem(atPath: tmpDir)
|
|
579
585
|
self.cleanupTemporaryFiles([tempDirectory])
|
|
580
586
|
completion(.failure(BundleStorageError.fileSystemError(error)))
|
|
581
587
|
}
|
|
@@ -588,4 +594,4 @@ extension Result {
|
|
|
588
594
|
guard case .failure(let error) = self else { return nil }
|
|
589
595
|
return error
|
|
590
596
|
}
|
|
591
|
-
}
|
|
597
|
+
}
|
|
@@ -15,7 +15,6 @@ protocol FileSystemService {
|
|
|
15
15
|
func moveItem(atPath srcPath: String, toPath dstPath: String) throws
|
|
16
16
|
func copyItem(atPath srcPath: String, toPath dstPath: String) throws
|
|
17
17
|
func contentsOfDirectory(atPath path: String) throws -> [String]
|
|
18
|
-
func setAttributes(_ attributes: [FileAttributeKey: Any], ofItemAtPath path: String) throws
|
|
19
18
|
func attributesOfItem(atPath path: String) throws -> [FileAttributeKey: Any]
|
|
20
19
|
func documentsPath() -> String
|
|
21
20
|
}
|
|
@@ -72,15 +71,6 @@ class FileManagerService: FileSystemService {
|
|
|
72
71
|
throw FileSystemError.fileOperationFailed(path, error)
|
|
73
72
|
}
|
|
74
73
|
}
|
|
75
|
-
|
|
76
|
-
func setAttributes(_ attributes: [FileAttributeKey: Any], ofItemAtPath path: String) throws {
|
|
77
|
-
do {
|
|
78
|
-
try fileManager.setAttributes(attributes, ofItemAtPath: path)
|
|
79
|
-
} catch let error {
|
|
80
|
-
NSLog("[FileSystemService] Failed to set attributes for \(path): \(error)")
|
|
81
|
-
throw FileSystemError.fileOperationFailed(path, error)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
74
|
|
|
85
75
|
func attributesOfItem(atPath path: String) throws -> [FileAttributeKey: Any] {
|
|
86
76
|
do {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/react-native",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.3",
|
|
4
4
|
"description": "React Native OTA solution for self-hosted",
|
|
5
5
|
"main": "lib/commonjs/index",
|
|
6
6
|
"module": "lib/module/index",
|
|
@@ -118,8 +118,8 @@
|
|
|
118
118
|
},
|
|
119
119
|
"dependencies": {
|
|
120
120
|
"use-sync-external-store": "1.5.0",
|
|
121
|
-
"@hot-updater/core": "0.18.
|
|
122
|
-
"@hot-updater/js": "0.18.
|
|
121
|
+
"@hot-updater/core": "0.18.3",
|
|
122
|
+
"@hot-updater/js": "0.18.3"
|
|
123
123
|
},
|
|
124
124
|
"scripts": {
|
|
125
125
|
"build": "bob build && tsc -p plugin/tsconfig.json",
|