@kesha-antonov/react-native-background-downloader 4.5.3 → 4.5.5

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.
Files changed (34) hide show
  1. package/README.md +74 -8
  2. package/android/build.gradle +4 -2
  3. package/android/src/main/java/com/eko/DownloadConstants.kt +3 -0
  4. package/android/src/main/java/com/eko/Downloader.kt +102 -0
  5. package/android/src/main/java/com/eko/RNBackgroundDownloaderModuleImpl.kt +86 -5
  6. package/android/src/main/java/com/eko/ResumableDownloader.kt +7 -0
  7. package/android/src/main/java/com/eko/UIDTDownloadJobService.kt +42 -4
  8. package/android/src/main/java/com/eko/uidt/UIDTJobManager.kt +11 -1
  9. package/android/src/main/java/com/eko/uidt/UIDTJobState.kt +62 -0
  10. package/android/src/main/java/com/eko/utils/StorageManager.kt +95 -0
  11. package/ios/RNBGDTaskConfig.h +3 -0
  12. package/ios/RNBGDTaskConfig.mm +3 -0
  13. package/ios/RNBackgroundDownloader.mm +375 -48
  14. package/lib/DownloadTask.d.ts +39 -0
  15. package/lib/DownloadTask.js +176 -0
  16. package/lib/NativeRNBackgroundDownloader.d.ts +127 -0
  17. package/lib/NativeRNBackgroundDownloader.js +4 -0
  18. package/lib/UploadTask.d.ts +30 -0
  19. package/lib/UploadTask.js +174 -0
  20. package/lib/config.d.ts +33 -0
  21. package/lib/config.js +79 -0
  22. package/lib/index.d.ts +26 -0
  23. package/lib/index.js +498 -0
  24. package/lib/types.d.ts +249 -0
  25. package/lib/types.js +2 -0
  26. package/package.json +7 -4
  27. package/plugin/build/index.d.ts +2 -2
  28. package/plugin/build/index.js +1 -1
  29. package/react-native-background-downloader.podspec +8 -2
  30. package/src/DownloadTask.ts +2 -0
  31. package/src/NativeRNBackgroundDownloader.ts +2 -0
  32. package/src/config.ts +6 -1
  33. package/src/index.ts +4 -0
  34. package/src/types.ts +27 -0
@@ -1,7 +1,10 @@
1
1
  package com.eko.uidt
2
2
 
3
3
  import android.app.job.JobParameters
4
+ import android.content.Context
5
+ import com.eko.RNBackgroundDownloaderModuleImpl
4
6
  import com.eko.ResumableDownloader
7
+ import org.json.JSONObject
5
8
  import java.util.concurrent.ConcurrentHashMap
6
9
 
7
10
  /**
@@ -127,6 +130,65 @@ data class UIDTJobInfo(
127
130
  * Singleton for managing active UIDT jobs state.
128
131
  */
129
132
  object UIDTJobRegistry {
133
+
134
+ private const val PREFS_NAME = "rnbd_uidt_resume"
135
+ private const val KEY_BYTES_PREFIX = "bytes_"
136
+ private const val KEY_HEADERS_PREFIX = "headers_"
137
+
138
+ /**
139
+ * Persist UIDT resume state (headers + byte position) to disk so it
140
+ * survives process death and can be used when the job is rescheduled in a
141
+ * new process.
142
+ */
143
+ fun saveResumeState(context: Context, configId: String, headers: Map<String, String>, bytesDownloaded: Long) {
144
+ try {
145
+ val headersJson = JSONObject(headers as Map<*, *>).toString()
146
+ context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit()
147
+ .putLong("$KEY_BYTES_PREFIX$configId", bytesDownloaded)
148
+ .putString("$KEY_HEADERS_PREFIX$configId", headersJson)
149
+ .apply()
150
+ RNBackgroundDownloaderModuleImpl.logD(UIDTConstants.TAG, "Saved UIDT resume state for $configId: bytes=$bytesDownloaded")
151
+ } catch (e: Exception) {
152
+ RNBackgroundDownloaderModuleImpl.logE(UIDTConstants.TAG, "Failed to save UIDT resume state for $configId: ${e.message}")
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Load persisted UIDT resume state for a download.
158
+ * Returns (headers, bytesDownloaded) or null if no state was saved.
159
+ */
160
+ fun loadResumeState(context: Context, configId: String): Pair<Map<String, String>, Long>? {
161
+ try {
162
+ val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
163
+ val bytesDownloaded = prefs.getLong("$KEY_BYTES_PREFIX$configId", -1L)
164
+ val headersJson = prefs.getString("$KEY_HEADERS_PREFIX$configId", null)
165
+ if (bytesDownloaded < 0 || headersJson == null) return null
166
+ val json = JSONObject(headersJson)
167
+ val headers = mutableMapOf<String, String>()
168
+ for (key in json.keys()) headers[key] = json.getString(key)
169
+ RNBackgroundDownloaderModuleImpl.logD(UIDTConstants.TAG, "Loaded UIDT resume state for $configId: bytes=$bytesDownloaded, headers=${headers.size}")
170
+ return Pair(headers, bytesDownloaded)
171
+ } catch (e: Exception) {
172
+ RNBackgroundDownloaderModuleImpl.logE(UIDTConstants.TAG, "Failed to load UIDT resume state for $configId: ${e.message}")
173
+ return null
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Clear persisted UIDT resume state for a download once it is no longer needed.
179
+ */
180
+ fun clearResumeState(context: Context, configId: String) {
181
+ try {
182
+ context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit()
183
+ .remove("$KEY_BYTES_PREFIX$configId")
184
+ .remove("$KEY_HEADERS_PREFIX$configId")
185
+ .apply()
186
+ RNBackgroundDownloaderModuleImpl.logD(UIDTConstants.TAG, "Cleared UIDT resume state for $configId")
187
+ } catch (e: Exception) {
188
+ RNBackgroundDownloaderModuleImpl.logE(UIDTConstants.TAG, "Failed to clear UIDT resume state for $configId: ${e.message}")
189
+ }
190
+ }
191
+
130
192
  // Track active jobs for pause/resume
131
193
  val activeJobs = ConcurrentHashMap<String, JobState>()
132
194
 
@@ -20,6 +20,7 @@ class StorageManager(context: Context, private val name: String) {
20
20
  private const val TAG = "StorageManager"
21
21
  private const val KEY_DOWNLOAD_ID_TO_CONFIG = "_downloadIdToConfig"
22
22
  private const val KEY_PAUSED_DOWNLOADS = "_pausedDownloads"
23
+ private const val KEY_ACTIVE_DOWNLOADS = "_activeDownloads"
23
24
  private const val KEY_UPLOAD_CONFIGS = "_uploadConfigs"
24
25
  private const val KEY_PROGRESS_INTERVAL = "_progressInterval"
25
26
  private const val KEY_PROGRESS_MIN_BYTES = "_progressMinBytes"
@@ -280,6 +281,100 @@ class StorageManager(context: Context, private val name: String) {
280
281
  }
281
282
  }
282
283
 
284
+ /**
285
+ * Save the set of in-progress resumable downloads ("recovery snapshots").
286
+ * These let getExistingDownloadTasks recover an active download after the app
287
+ * is force-stopped (which kills the foreground service and loses in-memory
288
+ * state). Stored separately from paused downloads so it never interferes with
289
+ * the live pause/resume bookkeeping.
290
+ */
291
+ fun saveActiveDownloads(activeDownloads: Map<String, Downloader.PausedDownloadInfo>) {
292
+ try {
293
+ val gson = Gson()
294
+ val mapCopy = activeDownloads.mapValues { (_, info) ->
295
+ PausedDownloadInfoData(
296
+ configId = info.configId,
297
+ url = info.url,
298
+ destination = info.destination,
299
+ headers = info.headers,
300
+ bytesDownloaded = info.bytesDownloaded,
301
+ bytesTotal = info.bytesTotal,
302
+ metadata = info.metadata
303
+ )
304
+ }
305
+ val str = gson.toJson(mapCopy)
306
+
307
+ if (isMMKVAvailable && mmkv != null) {
308
+ mmkv!!.encode("$name$KEY_ACTIVE_DOWNLOADS", str)
309
+ } else {
310
+ sharedPreferences.edit()
311
+ .putString("$name$KEY_ACTIVE_DOWNLOADS", str)
312
+ .commit()
313
+ }
314
+ } catch (e: Exception) {
315
+ RNBackgroundDownloaderModuleImpl.logE(TAG, "Failed to save active downloads: ${e.message}")
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Load the in-progress resumable download recovery snapshots.
321
+ */
322
+ fun loadActiveDownloads(): MutableMap<String, Downloader.PausedDownloadInfo> {
323
+ try {
324
+ val str = if (isMMKVAvailable && mmkv != null) {
325
+ mmkv!!.decodeString("$name$KEY_ACTIVE_DOWNLOADS")
326
+ } else {
327
+ sharedPreferences.getString("$name$KEY_ACTIVE_DOWNLOADS", null)
328
+ }
329
+
330
+ if (str != null && str.isNotEmpty()) {
331
+ val gson = Gson()
332
+ val mapType = object : TypeToken<Map<String, PausedDownloadInfoData>>() {}.type
333
+ val dataMap: Map<String, PausedDownloadInfoData> = gson.fromJson(str, mapType)
334
+ return dataMap.mapValues { (_, data) ->
335
+ Downloader.PausedDownloadInfo(
336
+ configId = data.configId,
337
+ url = data.url,
338
+ destination = data.destination,
339
+ headers = data.headers,
340
+ bytesDownloaded = data.bytesDownloaded,
341
+ bytesTotal = data.bytesTotal,
342
+ metadata = data.metadata
343
+ )
344
+ }.toMutableMap()
345
+ }
346
+ } catch (e: Exception) {
347
+ RNBackgroundDownloaderModuleImpl.logE(TAG, "Failed to load active downloads: ${e.message}")
348
+ }
349
+ return mutableMapOf()
350
+ }
351
+
352
+ /**
353
+ * Upsert a single in-progress resumable download recovery snapshot.
354
+ */
355
+ fun saveActiveDownload(info: Downloader.PausedDownloadInfo) {
356
+ val map = loadActiveDownloads()
357
+ map[info.configId] = info
358
+ saveActiveDownloads(map)
359
+ }
360
+
361
+ /**
362
+ * Remove a single recovery snapshot by config ID.
363
+ */
364
+ fun removeActiveDownload(configId: String) {
365
+ val map = loadActiveDownloads()
366
+ if (map.remove(configId) != null) {
367
+ saveActiveDownloads(map)
368
+ }
369
+ }
370
+
371
+ /**
372
+ * Clear all recovery snapshots.
373
+ */
374
+ fun clearActiveDownloads() {
375
+ saveActiveDownloads(emptyMap())
376
+ }
377
+
283
378
  /**
284
379
  * Save upload configs map.
285
380
  */
@@ -14,6 +14,9 @@ NS_ASSUME_NONNULL_BEGIN
14
14
  @property (nonatomic, assign) NSInteger state;
15
15
  @property (nonatomic, assign) NSInteger errorCode;
16
16
  @property (nonatomic, assign) BOOL hasResumeData;
17
+ // iOS Data Protection level key for the saved file
18
+ // (e.g. NSFileProtectionCompleteUntilFirstUserAuthentication). nil = library default.
19
+ @property (nonatomic, copy, nullable) NSString *dataProtection;
17
20
 
18
21
  - (instancetype)initWithDictionary:(NSDictionary *)dict;
19
22
 
@@ -16,6 +16,7 @@
16
16
  self.url = dict[@"url"];
17
17
  self.destination = dict[@"destination"];
18
18
  self.metadata = dict[@"metadata"];
19
+ self.dataProtection = dict[@"dataProtection"];
19
20
  self.reportedBegin = NO;
20
21
  self.bytesDownloaded = 0;
21
22
  self.bytesTotal = 0;
@@ -33,6 +34,7 @@
33
34
  [aCoder encodeObject:self.url forKey:@"url"];
34
35
  [aCoder encodeObject:self.destination forKey:@"destination"];
35
36
  [aCoder encodeObject:self.metadata forKey:@"metadata"];
37
+ [aCoder encodeObject:self.dataProtection forKey:@"dataProtection"];
36
38
  [aCoder encodeBool:self.reportedBegin forKey:@"reportedBegin"];
37
39
  [aCoder encodeInt64:self.bytesDownloaded forKey:@"bytesDownloaded"];
38
40
  [aCoder encodeInt64:self.bytesTotal forKey:@"bytesTotal"];
@@ -52,6 +54,7 @@
52
54
  self.destination = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"destination"];
53
55
  NSString *metadata = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"metadata"];
54
56
  self.metadata = metadata != nil ? metadata : @"{}";
57
+ self.dataProtection = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"dataProtection"];
55
58
  self.reportedBegin = [aDecoder decodeBoolForKey:@"reportedBegin"];
56
59
  self.bytesDownloaded = [aDecoder decodeInt64ForKey:@"bytesDownloaded"];
57
60
  self.bytesTotal = [aDecoder decodeInt64ForKey:@"bytesTotal"];