@kesha-antonov/react-native-background-downloader 4.3.4 → 4.3.7
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 +58 -1
- package/android/src/main/java/com/eko/DownloadConstants.kt +1 -1
- package/android/src/main/java/com/eko/DownloadEventEmitter.kt +1 -2
- package/android/src/main/java/com/eko/Downloader.kt +9 -9
- package/android/src/main/java/com/eko/RNBackgroundDownloaderModuleImpl.kt +60 -35
- package/android/src/main/java/com/eko/ResumableDownloadService.kt +16 -17
- package/android/src/main/java/com/eko/ResumableDownloader.kt +36 -37
- package/android/src/main/java/com/eko/utils/StorageManager.kt +21 -21
- package/android/src/main/java/com/eko/utils/TempFileUtils.kt +7 -7
- package/ios/RNBackgroundDownloader.mm +82 -7
- package/package.json +1 -1
- package/plugin/package.json +6 -0
- package/src/NativeRNBackgroundDownloader.ts +2 -0
- package/src/config.ts +13 -1
- package/src/index.ts +18 -0
- package/src/types.ts +4 -0
- package/CHANGELOG.md +0 -105
- package/example/android/app/debug.keystore +0 -0
- package/ios/.DS_Store +0 -0
package/README.md
CHANGED
|
@@ -355,6 +355,43 @@ task.start()
|
|
|
355
355
|
```
|
|
356
356
|
Headers given in `createDownloadTask()` are **merged** with the ones given in `setConfig({ headers: { ... } })`.
|
|
357
357
|
|
|
358
|
+
### Enabling Debug Logs
|
|
359
|
+
|
|
360
|
+
The library includes verbose debug logging that can help diagnose download issues. Logging is disabled by default but can be enabled at runtime using `setConfig()`. **Logging works in both debug and production/release builds.**
|
|
361
|
+
|
|
362
|
+
```javascript
|
|
363
|
+
import { setConfig } from '@kesha-antonov/react-native-background-downloader'
|
|
364
|
+
|
|
365
|
+
// Option 1: Enable native console logging (logs appear in Xcode/Android Studio console)
|
|
366
|
+
setConfig({
|
|
367
|
+
isLogsEnabled: true
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
// Option 2: Enable logging with a JavaScript callback to capture logs in your app
|
|
371
|
+
setConfig({
|
|
372
|
+
isLogsEnabled: true,
|
|
373
|
+
logCallback: (log) => {
|
|
374
|
+
// log.message - The debug message
|
|
375
|
+
// log.taskId - Optional task ID associated with the log (iOS only)
|
|
376
|
+
console.log('[BackgroundDownloader]', log.message)
|
|
377
|
+
|
|
378
|
+
// You can also send logs to your analytics/crash reporting service
|
|
379
|
+
// crashlytics.log(log.message)
|
|
380
|
+
}
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
// Disable logging
|
|
384
|
+
setConfig({
|
|
385
|
+
isLogsEnabled: false
|
|
386
|
+
})
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
**Notes:**
|
|
390
|
+
- When `isLogsEnabled` is `true`, native debug logs (NSLog on iOS, Log.d/w/e on Android) are printed
|
|
391
|
+
- The `logCallback` function is called for each native debug log (iOS only sends logs to callback currently)
|
|
392
|
+
- Logs include detailed information about download lifecycle, session management, and errors
|
|
393
|
+
- In production builds, logs are only printed when explicitly enabled via `isLogsEnabled`
|
|
394
|
+
|
|
358
395
|
### Handling Slow-Responding URLs
|
|
359
396
|
|
|
360
397
|
This library automatically includes connection timeout improvements for slow-responding URLs. By default, the following headers are added to all download requests on Android:
|
|
@@ -469,7 +506,27 @@ An object containing configuration properties
|
|
|
469
506
|
| `headers` | Record<string, string \| null> | Optional headers to use in all future downloads. Headers with null values will be removed |
|
|
470
507
|
| `progressInterval` | Number | Interval in milliseconds for download progress updates. Must be >= 250. Default is 1000 |
|
|
471
508
|
| `progressMinBytes` | Number | Minimum number of bytes that must be downloaded before a progress event is emitted. When set to 0, only the percentage threshold (1% change) triggers progress updates. Default is 1048576 (1MB) |
|
|
472
|
-
| `isLogsEnabled` | Boolean | Enables/disables debug logs in
|
|
509
|
+
| `isLogsEnabled` | Boolean | Enables/disables verbose debug logs in native code (iOS and Android). Works in both debug and release builds. Default is false |
|
|
510
|
+
| `logCallback` | (log: { message: string, taskId?: string }) => void | Optional callback function to receive native debug logs in JavaScript. Only called when `isLogsEnabled` is true |
|
|
511
|
+
|
|
512
|
+
**Example:**
|
|
513
|
+
|
|
514
|
+
```javascript
|
|
515
|
+
import { setConfig } from '@kesha-antonov/react-native-background-downloader'
|
|
516
|
+
|
|
517
|
+
// Enable verbose logging with callback
|
|
518
|
+
setConfig({
|
|
519
|
+
isLogsEnabled: true,
|
|
520
|
+
logCallback: (log) => {
|
|
521
|
+
console.log('[BackgroundDownloader]', log.message, log.taskId ? `(${log.taskId})` : '')
|
|
522
|
+
}
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
// Or just enable native console logging without JS callback
|
|
526
|
+
setConfig({
|
|
527
|
+
isLogsEnabled: true
|
|
528
|
+
})
|
|
529
|
+
```
|
|
473
530
|
|
|
474
531
|
### DownloadTask
|
|
475
532
|
|
|
@@ -40,7 +40,7 @@ object DownloadConstants {
|
|
|
40
40
|
const val KEEP_ALIVE_HEADER_VALUE = "timeout=600, max=1000"
|
|
41
41
|
|
|
42
42
|
/** Library version for User-Agent */
|
|
43
|
-
const val VERSION = "4.3.
|
|
43
|
+
const val VERSION = "4.3.5"
|
|
44
44
|
|
|
45
45
|
/** User-Agent string for HTTP requests */
|
|
46
46
|
const val USER_AGENT = "ReactNative-BackgroundDownloader/$VERSION"
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
package com.eko
|
|
2
2
|
|
|
3
|
-
import android.util.Log
|
|
4
3
|
import com.facebook.react.bridge.Arguments
|
|
5
4
|
import com.facebook.react.bridge.WritableMap
|
|
6
5
|
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
@@ -30,7 +29,7 @@ class DownloadEventEmitter(
|
|
|
30
29
|
try {
|
|
31
30
|
getEmitter().emit(eventName, params)
|
|
32
31
|
} catch (e: Exception) {
|
|
33
|
-
|
|
32
|
+
RNBackgroundDownloaderModuleImpl.logW(TAG, "Failed to emit $eventName event: ${e.message}")
|
|
34
33
|
}
|
|
35
34
|
}
|
|
36
35
|
|
|
@@ -48,7 +48,7 @@ class Downloader(private val context: Context) {
|
|
|
48
48
|
val binder = service as ResumableDownloadService.LocalBinder
|
|
49
49
|
downloadService = binder.getService()
|
|
50
50
|
serviceBound = true
|
|
51
|
-
|
|
51
|
+
RNBackgroundDownloaderModuleImpl.logD(TAG, "ResumableDownloadService connected")
|
|
52
52
|
|
|
53
53
|
// Execute any pending operations
|
|
54
54
|
var operation = pendingServiceOperations.poll()
|
|
@@ -61,7 +61,7 @@ class Downloader(private val context: Context) {
|
|
|
61
61
|
override fun onServiceDisconnected(name: ComponentName?) {
|
|
62
62
|
downloadService = null
|
|
63
63
|
serviceBound = false
|
|
64
|
-
|
|
64
|
+
RNBackgroundDownloaderModuleImpl.logD(TAG, "ResumableDownloadService disconnected")
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
|
|
@@ -132,7 +132,7 @@ class Downloader(private val context: Context) {
|
|
|
132
132
|
// Remove any stale paused state from a previous download with the same ID
|
|
133
133
|
val pausedInfo = pausedDownloads.remove(configId)
|
|
134
134
|
if (pausedInfo != null) {
|
|
135
|
-
|
|
135
|
+
RNBackgroundDownloaderModuleImpl.logD(TAG, "Cleaned up stale paused state for: $configId")
|
|
136
136
|
TempFileUtils.deleteTempFile(pausedInfo.destination)
|
|
137
137
|
}
|
|
138
138
|
}
|
|
@@ -195,7 +195,7 @@ class Downloader(private val context: Context) {
|
|
|
195
195
|
bytesTotal = bytesTotal
|
|
196
196
|
)
|
|
197
197
|
|
|
198
|
-
|
|
198
|
+
RNBackgroundDownloaderModuleImpl.logD(TAG, "Paused download $configId at $bytesDownloaded/$bytesTotal bytes")
|
|
199
199
|
return true
|
|
200
200
|
}
|
|
201
201
|
|
|
@@ -205,7 +205,7 @@ class Downloader(private val context: Context) {
|
|
|
205
205
|
fun resume(configId: String, listener: ResumableDownloader.DownloadListener): Boolean {
|
|
206
206
|
val pausedInfo = pausedDownloads[configId]
|
|
207
207
|
if (pausedInfo == null) {
|
|
208
|
-
|
|
208
|
+
RNBackgroundDownloaderModuleImpl.logW(TAG, "No paused download found for configId: $configId")
|
|
209
209
|
return false
|
|
210
210
|
}
|
|
211
211
|
|
|
@@ -220,7 +220,7 @@ class Downloader(private val context: Context) {
|
|
|
220
220
|
listener
|
|
221
221
|
)
|
|
222
222
|
|
|
223
|
-
|
|
223
|
+
RNBackgroundDownloaderModuleImpl.logD(TAG, "Resuming download $configId from ${pausedInfo.bytesDownloaded} bytes via service")
|
|
224
224
|
return true
|
|
225
225
|
}
|
|
226
226
|
|
|
@@ -247,7 +247,7 @@ class Downloader(private val context: Context) {
|
|
|
247
247
|
try {
|
|
248
248
|
context.startForegroundService(startIntent)
|
|
249
249
|
} catch (e: Exception) {
|
|
250
|
-
|
|
250
|
+
RNBackgroundDownloaderModuleImpl.logW(TAG, "Could not start foreground service: ${e.message}")
|
|
251
251
|
}
|
|
252
252
|
} else {
|
|
253
253
|
context.startService(startIntent)
|
|
@@ -281,7 +281,7 @@ class Downloader(private val context: Context) {
|
|
|
281
281
|
-1L, // Total bytes unknown
|
|
282
282
|
listener
|
|
283
283
|
)
|
|
284
|
-
|
|
284
|
+
RNBackgroundDownloaderModuleImpl.logD(TAG, "Started ResumableDownloader for $configId (DownloadManager fallback)")
|
|
285
285
|
}
|
|
286
286
|
|
|
287
287
|
/**
|
|
@@ -368,7 +368,7 @@ class Downloader(private val context: Context) {
|
|
|
368
368
|
}
|
|
369
369
|
}
|
|
370
370
|
} catch (e: Exception) {
|
|
371
|
-
|
|
371
|
+
RNBackgroundDownloaderModuleImpl.logE(TAG, "Downloader: ${Log.getStackTraceString(e)}")
|
|
372
372
|
}
|
|
373
373
|
|
|
374
374
|
return result
|
|
@@ -45,6 +45,23 @@ class RNBackgroundDownloaderModuleImpl(private val reactContext: ReactApplicatio
|
|
|
45
45
|
)
|
|
46
46
|
|
|
47
47
|
private val sharedLock = Any()
|
|
48
|
+
|
|
49
|
+
// Controls whether debug logs are enabled
|
|
50
|
+
@Volatile
|
|
51
|
+
private var isLogsEnabled = false
|
|
52
|
+
|
|
53
|
+
// Helper functions for conditional logging
|
|
54
|
+
fun logD(tag: String, message: String) {
|
|
55
|
+
if (isLogsEnabled) Log.d(tag, message)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fun logW(tag: String, message: String) {
|
|
59
|
+
if (isLogsEnabled) Log.w(tag, message)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
fun logE(tag: String, message: String) {
|
|
63
|
+
if (isLogsEnabled) Log.e(tag, message)
|
|
64
|
+
}
|
|
48
65
|
}
|
|
49
66
|
|
|
50
67
|
// Storage manager for persistent state
|
|
@@ -91,7 +108,7 @@ class RNBackgroundDownloaderModuleImpl(private val reactContext: ReactApplicatio
|
|
|
91
108
|
ee = reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
92
109
|
}
|
|
93
110
|
} catch (e: Exception) {
|
|
94
|
-
|
|
111
|
+
logW(NAME, "Event emitter not ready yet: ${e.message}")
|
|
95
112
|
return null
|
|
96
113
|
}
|
|
97
114
|
}
|
|
@@ -107,9 +124,9 @@ class RNBackgroundDownloaderModuleImpl(private val reactContext: ReactApplicatio
|
|
|
107
124
|
if (!::ee.isInitialized) {
|
|
108
125
|
try {
|
|
109
126
|
ee = reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
110
|
-
|
|
127
|
+
logD(NAME, "Event emitter initialized eagerly before download")
|
|
111
128
|
} catch (e: Exception) {
|
|
112
|
-
|
|
129
|
+
logW(NAME, "Could not initialize event emitter: ${e.message}")
|
|
113
130
|
}
|
|
114
131
|
}
|
|
115
132
|
}
|
|
@@ -123,7 +140,7 @@ class RNBackgroundDownloaderModuleImpl(private val reactContext: ReactApplicatio
|
|
|
123
140
|
synchronized(sharedLock) {
|
|
124
141
|
if (!isReceiverRegistered) {
|
|
125
142
|
registerDownloadReceiver()
|
|
126
|
-
|
|
143
|
+
logD(NAME, "Download receiver registered eagerly before download")
|
|
127
144
|
}
|
|
128
145
|
}
|
|
129
146
|
}
|
|
@@ -183,6 +200,10 @@ class RNBackgroundDownloaderModuleImpl(private val reactContext: ReactApplicatio
|
|
|
183
200
|
return constants
|
|
184
201
|
}
|
|
185
202
|
|
|
203
|
+
fun setLogsEnabled(enabled: Boolean) {
|
|
204
|
+
isLogsEnabled = enabled
|
|
205
|
+
}
|
|
206
|
+
|
|
186
207
|
fun initialize() {
|
|
187
208
|
ee = reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
188
209
|
isInitialized = true
|
|
@@ -217,7 +238,7 @@ class RNBackgroundDownloaderModuleImpl(private val reactContext: ReactApplicatio
|
|
|
217
238
|
val cancelIntent = downloader.getCancelIntent(downloadId)
|
|
218
239
|
if (cancelIntent != null) {
|
|
219
240
|
downloader.clearCancelIntent(downloadId)
|
|
220
|
-
|
|
241
|
+
logD(NAME, "Ignoring broadcast for ${cancelIntent.name.lowercase()} download: $downloadId")
|
|
221
242
|
return
|
|
222
243
|
}
|
|
223
244
|
|
|
@@ -284,7 +305,7 @@ class RNBackgroundDownloaderModuleImpl(private val reactContext: ReactApplicatio
|
|
|
284
305
|
try {
|
|
285
306
|
reactContext.unregisterReceiver(it)
|
|
286
307
|
} catch (e: Exception) {
|
|
287
|
-
|
|
308
|
+
logW(NAME, "Could not unregister receiver: ${e.message}")
|
|
288
309
|
}
|
|
289
310
|
downloadReceiver = null
|
|
290
311
|
isReceiverRegistered = false
|
|
@@ -319,7 +340,7 @@ class RNBackgroundDownloaderModuleImpl(private val reactContext: ReactApplicatio
|
|
|
319
340
|
val onProgressFuture = cachedExecutorPool.submit(onProgressCallable)
|
|
320
341
|
configIdToProgressFuture[config.id] = onProgressFuture
|
|
321
342
|
} catch (e: Exception) {
|
|
322
|
-
|
|
343
|
+
logE(NAME, "resumeTasks: ${Log.getStackTraceString(e)}")
|
|
323
344
|
}
|
|
324
345
|
}.start()
|
|
325
346
|
}
|
|
@@ -402,7 +423,7 @@ class RNBackgroundDownloaderModuleImpl(private val reactContext: ReactApplicatio
|
|
|
402
423
|
// This is a redirect
|
|
403
424
|
val location = connection.getHeaderField("Location")
|
|
404
425
|
if (location == null) {
|
|
405
|
-
|
|
426
|
+
logW(NAME, "Redirect response without Location header at: $currentUrl")
|
|
406
427
|
break
|
|
407
428
|
}
|
|
408
429
|
|
|
@@ -419,7 +440,7 @@ class RNBackgroundDownloaderModuleImpl(private val reactContext: ReactApplicatio
|
|
|
419
440
|
else -> location
|
|
420
441
|
}
|
|
421
442
|
|
|
422
|
-
|
|
443
|
+
logD(NAME, "Redirect ${redirectCount + 1}/$maxRedirects: $currentUrl -> $location")
|
|
423
444
|
redirectCount++
|
|
424
445
|
} else {
|
|
425
446
|
// Not a redirect, we've found the final URL
|
|
@@ -430,18 +451,18 @@ class RNBackgroundDownloaderModuleImpl(private val reactContext: ReactApplicatio
|
|
|
430
451
|
}
|
|
431
452
|
|
|
432
453
|
if (redirectCount >= maxRedirects) {
|
|
433
|
-
|
|
454
|
+
logW(
|
|
434
455
|
NAME,
|
|
435
456
|
"Reached maximum redirects ($maxRedirects) for URL: $originalUrl. Final URL: $currentUrl"
|
|
436
457
|
)
|
|
437
458
|
} else {
|
|
438
|
-
|
|
459
|
+
logD(NAME, "Resolved URL after $redirectCount redirects: $currentUrl")
|
|
439
460
|
}
|
|
440
461
|
|
|
441
462
|
return currentUrl
|
|
442
463
|
|
|
443
464
|
} catch (e: Exception) {
|
|
444
|
-
|
|
465
|
+
logE(NAME, "Failed to resolve redirects for URL: $originalUrl. Error: ${e.message}")
|
|
445
466
|
// Return original URL if redirect resolution fails
|
|
446
467
|
return originalUrl
|
|
447
468
|
}
|
|
@@ -474,7 +495,7 @@ class RNBackgroundDownloaderModuleImpl(private val reactContext: ReactApplicatio
|
|
|
474
495
|
JSONObject(it.toString()).toString()
|
|
475
496
|
} ?: "{}"
|
|
476
497
|
} catch (e: Exception) {
|
|
477
|
-
|
|
498
|
+
logW(NAME, "Failed to convert metadata map to string: ${e.message}")
|
|
478
499
|
"{}"
|
|
479
500
|
}
|
|
480
501
|
}
|
|
@@ -503,15 +524,15 @@ class RNBackgroundDownloaderModuleImpl(private val reactContext: ReactApplicatio
|
|
|
503
524
|
}
|
|
504
525
|
|
|
505
526
|
if (id == null || url == null || destination == null) {
|
|
506
|
-
|
|
527
|
+
logE(NAME, "download: id, url and destination must be set.")
|
|
507
528
|
return
|
|
508
529
|
}
|
|
509
530
|
|
|
510
531
|
// Resolve redirects if maxRedirects is specified
|
|
511
532
|
if (maxRedirects > 0) {
|
|
512
|
-
|
|
533
|
+
logD(NAME, "Resolving redirects for URL: $url (maxRedirects: $maxRedirects)")
|
|
513
534
|
url = resolveRedirects(url, maxRedirects, headers)
|
|
514
|
-
|
|
535
|
+
logD(NAME, "Final resolved URL: $url")
|
|
515
536
|
}
|
|
516
537
|
|
|
517
538
|
val request = DownloadManager.Request(Uri.parse(url))
|
|
@@ -596,18 +617,18 @@ class RNBackgroundDownloaderModuleImpl(private val reactContext: ReactApplicatio
|
|
|
596
617
|
} else {
|
|
597
618
|
// External files directory path is invalid or null
|
|
598
619
|
// Try setDestinationInExternalFilesDir as fallback, then ResumableDownloader if that fails
|
|
599
|
-
|
|
620
|
+
logW(NAME, "External files directory path may be invalid for DownloadManager: ${externalFilesDir?.absolutePath}")
|
|
600
621
|
|
|
601
622
|
try {
|
|
602
623
|
// Try standard setDestinationInExternalFilesDir - may work on some devices
|
|
603
624
|
request.setDestinationInExternalFilesDir(reactContext, null, filename)
|
|
604
625
|
startDownloadManagerDownload(downloader.download(request))
|
|
605
|
-
|
|
626
|
+
logD(NAME, "Using setDestinationInExternalFilesDir for download: $id")
|
|
606
627
|
} catch (e: Exception) {
|
|
607
628
|
// DownloadManager failed - fall back to ResumableDownloader
|
|
608
629
|
// This handles OnePlus and other devices that return paths like /data/local/tmp/external/
|
|
609
|
-
|
|
610
|
-
|
|
630
|
+
logW(NAME, "DownloadManager failed with: ${e.message}")
|
|
631
|
+
logD(NAME, "Using ResumableDownloader as fallback for download: $id")
|
|
611
632
|
startWithResumableDownloader()
|
|
612
633
|
}
|
|
613
634
|
}
|
|
@@ -620,7 +641,7 @@ class RNBackgroundDownloaderModuleImpl(private val reactContext: ReactApplicatio
|
|
|
620
641
|
// First check if it's an active resumable download
|
|
621
642
|
if (downloader.isResumableDownload(configId)) {
|
|
622
643
|
downloader.pauseResumable(configId)
|
|
623
|
-
|
|
644
|
+
logD(NAME, "Paused resumable download: $configId")
|
|
624
645
|
return
|
|
625
646
|
}
|
|
626
647
|
|
|
@@ -642,11 +663,15 @@ class RNBackgroundDownloaderModuleImpl(private val reactContext: ReactApplicatio
|
|
|
642
663
|
downloadIdToConfig.remove(downloadId)
|
|
643
664
|
configIdToDownloadId.remove(configId)
|
|
644
665
|
saveDownloadIdToConfigMap()
|
|
645
|
-
|
|
666
|
+
logD(NAME, "Paused DownloadManager download: $configId")
|
|
667
|
+
} else {
|
|
668
|
+
logD(NAME, "pauseTask: Download not paused (may already be paused): $configId")
|
|
646
669
|
}
|
|
670
|
+
} else {
|
|
671
|
+
logW(NAME, "pauseTask: No config found for downloadId: $downloadId")
|
|
647
672
|
}
|
|
648
673
|
} else {
|
|
649
|
-
|
|
674
|
+
logW(NAME, "pauseTask: No download found for configId: $configId")
|
|
650
675
|
}
|
|
651
676
|
}
|
|
652
677
|
}
|
|
@@ -657,7 +682,7 @@ class RNBackgroundDownloaderModuleImpl(private val reactContext: ReactApplicatio
|
|
|
657
682
|
// First check if it's a paused resumable download
|
|
658
683
|
if (downloader.resumableDownloader.isPaused(configId)) {
|
|
659
684
|
downloader.resumeResumable(configId, resumableDownloadListener)
|
|
660
|
-
|
|
685
|
+
logD(NAME, "Resumed paused resumable download: $configId")
|
|
661
686
|
return
|
|
662
687
|
}
|
|
663
688
|
|
|
@@ -665,14 +690,14 @@ class RNBackgroundDownloaderModuleImpl(private val reactContext: ReactApplicatio
|
|
|
665
690
|
if (downloader.isPaused(configId)) {
|
|
666
691
|
val resumed = downloader.resume(configId, resumableDownloadListener)
|
|
667
692
|
if (resumed) {
|
|
668
|
-
|
|
693
|
+
logD(NAME, "Resumed download via ResumableDownloader: $configId")
|
|
669
694
|
} else {
|
|
670
|
-
|
|
695
|
+
logW(NAME, "Failed to resume download: $configId")
|
|
671
696
|
}
|
|
672
697
|
return
|
|
673
698
|
}
|
|
674
699
|
|
|
675
|
-
|
|
700
|
+
logW(NAME, "resumeTask: No paused download found for configId: $configId")
|
|
676
701
|
}
|
|
677
702
|
}
|
|
678
703
|
|
|
@@ -699,11 +724,11 @@ class RNBackgroundDownloaderModuleImpl(private val reactContext: ReactApplicatio
|
|
|
699
724
|
// Firebase Performance compatibility: Add defensive programming to prevent crashes
|
|
700
725
|
// when Firebase Performance SDK is installed and uses bytecode instrumentation
|
|
701
726
|
|
|
702
|
-
|
|
727
|
+
logD(NAME, "completeHandler called with configId: $configId")
|
|
703
728
|
|
|
704
729
|
// Defensive programming: Validate parameters
|
|
705
730
|
if (configId.isEmpty()) {
|
|
706
|
-
|
|
731
|
+
logW(NAME, "completeHandler: Invalid configId provided")
|
|
707
732
|
return
|
|
708
733
|
}
|
|
709
734
|
|
|
@@ -711,12 +736,12 @@ class RNBackgroundDownloaderModuleImpl(private val reactContext: ReactApplicatio
|
|
|
711
736
|
// Currently this method doesn't have any implementation on Android
|
|
712
737
|
// as completion handlers are handled differently than iOS.
|
|
713
738
|
// This defensive structure ensures Firebase Performance compatibility.
|
|
714
|
-
|
|
739
|
+
logD(NAME, "completeHandler executed successfully for configId: $configId")
|
|
715
740
|
|
|
716
741
|
} catch (e: Exception) {
|
|
717
742
|
// Catch any potential exceptions that might be thrown due to Firebase Performance
|
|
718
743
|
// bytecode instrumentation interfering with method dispatch
|
|
719
|
-
|
|
744
|
+
logE(NAME, "completeHandler: Exception occurred: ${Log.getStackTraceString(e)}")
|
|
720
745
|
}
|
|
721
746
|
}
|
|
722
747
|
|
|
@@ -745,7 +770,7 @@ class RNBackgroundDownloaderModuleImpl(private val reactContext: ReactApplicatio
|
|
|
745
770
|
val future = setFileChangesBeforeCompletion(localUri, config.destination)
|
|
746
771
|
future.get()
|
|
747
772
|
} catch (e: Exception) {
|
|
748
|
-
|
|
773
|
+
logE(NAME, "Error moving completed download file: ${e.message}")
|
|
749
774
|
// Continue with normal processing even if file move fails
|
|
750
775
|
}
|
|
751
776
|
}
|
|
@@ -775,7 +800,7 @@ class RNBackgroundDownloaderModuleImpl(private val reactContext: ReactApplicatio
|
|
|
775
800
|
}
|
|
776
801
|
}
|
|
777
802
|
} catch (e: Exception) {
|
|
778
|
-
|
|
803
|
+
logE(NAME, "getExistingDownloadTasks: ${Log.getStackTraceString(e)}")
|
|
779
804
|
}
|
|
780
805
|
}
|
|
781
806
|
|
|
@@ -826,7 +851,7 @@ class RNBackgroundDownloaderModuleImpl(private val reactContext: ReactApplicatio
|
|
|
826
851
|
}
|
|
827
852
|
|
|
828
853
|
private fun onFailedDownload(config: RNBGDTaskConfig, downloadStatus: WritableMap) {
|
|
829
|
-
|
|
854
|
+
logE(
|
|
830
855
|
NAME, "onFailedDownload: " +
|
|
831
856
|
"${downloadStatus.getInt("status")}:" +
|
|
832
857
|
"${downloadStatus.getInt("reason")}:" +
|
|
@@ -838,7 +863,7 @@ class RNBackgroundDownloaderModuleImpl(private val reactContext: ReactApplicatio
|
|
|
838
863
|
|
|
839
864
|
// Enhanced handling for ERROR_CANNOT_RESUME (1008)
|
|
840
865
|
if (reason == DownloadManager.ERROR_CANNOT_RESUME) {
|
|
841
|
-
|
|
866
|
+
logW(
|
|
842
867
|
NAME, "ERROR_CANNOT_RESUME detected for download: ${config.id}" +
|
|
843
868
|
". This is a known Android DownloadManager issue with larger files. " +
|
|
844
869
|
"Consider restarting the download or using smaller file segments."
|
|
@@ -10,7 +10,6 @@ import android.os.Binder
|
|
|
10
10
|
import android.os.Build
|
|
11
11
|
import android.os.IBinder
|
|
12
12
|
import android.os.PowerManager
|
|
13
|
-
import android.util.Log
|
|
14
13
|
import androidx.core.app.NotificationCompat
|
|
15
14
|
import java.util.concurrent.ConcurrentHashMap
|
|
16
15
|
import java.util.concurrent.ExecutorService
|
|
@@ -92,7 +91,7 @@ class ResumableDownloadService : Service() {
|
|
|
92
91
|
private fun logStale(event: String) {
|
|
93
92
|
val currentJob = activeDownloads[id]
|
|
94
93
|
val currentGeneration = downloadGeneration[id] ?: 0
|
|
95
|
-
|
|
94
|
+
RNBackgroundDownloaderModuleImpl.logD(TAG, "Ignoring stale $event for $id (session: my=$sessionToken vs job=${currentJob?.sessionToken}, gen: my=$generation vs current=$currentGeneration)")
|
|
96
95
|
}
|
|
97
96
|
|
|
98
97
|
override fun onBegin(id: String, expectedBytes: Long, headers: Map<String, String>) {
|
|
@@ -112,7 +111,7 @@ class ResumableDownloadService : Service() {
|
|
|
112
111
|
val now = System.currentTimeMillis()
|
|
113
112
|
val lastLogTime = lastProgressLogTime[id] ?: 0L
|
|
114
113
|
if (now - lastLogTime >= DownloadConstants.PROGRESS_LOG_INTERVAL_MS) {
|
|
115
|
-
|
|
114
|
+
RNBackgroundDownloaderModuleImpl.logD(TAG, "onProgress: id=$id, bytes=$bytesDownloaded, myToken=$sessionToken, jobToken=${currentJob?.sessionToken}, myGen=$generation, currentGen=$currentGeneration")
|
|
116
115
|
lastProgressLogTime[id] = now
|
|
117
116
|
}
|
|
118
117
|
|
|
@@ -163,7 +162,7 @@ class ResumableDownloadService : Service() {
|
|
|
163
162
|
|
|
164
163
|
override fun onCreate() {
|
|
165
164
|
super.onCreate()
|
|
166
|
-
|
|
165
|
+
RNBackgroundDownloaderModuleImpl.logD(TAG, "Service created")
|
|
167
166
|
createNotificationChannel()
|
|
168
167
|
}
|
|
169
168
|
|
|
@@ -172,7 +171,7 @@ class ResumableDownloadService : Service() {
|
|
|
172
171
|
}
|
|
173
172
|
|
|
174
173
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
175
|
-
|
|
174
|
+
RNBackgroundDownloaderModuleImpl.logD(TAG, "onStartCommand: action=${intent?.action}")
|
|
176
175
|
|
|
177
176
|
when (intent?.action) {
|
|
178
177
|
ACTION_START_DOWNLOAD -> {
|
|
@@ -215,7 +214,7 @@ class ResumableDownloadService : Service() {
|
|
|
215
214
|
}
|
|
216
215
|
|
|
217
216
|
override fun onDestroy() {
|
|
218
|
-
|
|
217
|
+
RNBackgroundDownloaderModuleImpl.logD(TAG, "Service destroyed")
|
|
219
218
|
releaseWakeLock()
|
|
220
219
|
executorService.shutdownNow()
|
|
221
220
|
super.onDestroy()
|
|
@@ -244,7 +243,7 @@ class ResumableDownloadService : Service() {
|
|
|
244
243
|
startByte: Long,
|
|
245
244
|
totalBytes: Long
|
|
246
245
|
) {
|
|
247
|
-
|
|
246
|
+
RNBackgroundDownloaderModuleImpl.logD(TAG, "Starting download: $id from byte $startByte")
|
|
248
247
|
|
|
249
248
|
// Start foreground service if not already
|
|
250
249
|
startForegroundWithNotification()
|
|
@@ -254,7 +253,7 @@ class ResumableDownloadService : Service() {
|
|
|
254
253
|
// This ensures we can detect stale events even if job was removed and re-added
|
|
255
254
|
val generation = (downloadGeneration[id] ?: 0) + 1
|
|
256
255
|
downloadGeneration[id] = generation
|
|
257
|
-
|
|
256
|
+
RNBackgroundDownloaderModuleImpl.logD(TAG, "Download $id starting with generation $generation")
|
|
258
257
|
|
|
259
258
|
val job = DownloadJob(id, url, destination, headers, startByte, totalBytes)
|
|
260
259
|
activeDownloads[id] = job
|
|
@@ -275,7 +274,7 @@ class ResumableDownloadService : Service() {
|
|
|
275
274
|
}
|
|
276
275
|
|
|
277
276
|
fun pauseDownload(id: String): Boolean {
|
|
278
|
-
|
|
277
|
+
RNBackgroundDownloaderModuleImpl.logD(TAG, "Pausing download: $id")
|
|
279
278
|
val result = resumableDownloader.pause(id)
|
|
280
279
|
if (result) {
|
|
281
280
|
updateNotification()
|
|
@@ -285,13 +284,13 @@ class ResumableDownloadService : Service() {
|
|
|
285
284
|
}
|
|
286
285
|
|
|
287
286
|
fun resumeDownload(id: String): Boolean {
|
|
288
|
-
|
|
287
|
+
RNBackgroundDownloaderModuleImpl.logD(TAG, "Resuming download: $id")
|
|
289
288
|
|
|
290
289
|
if (this.listener == null) return false
|
|
291
290
|
val currentJob = activeDownloads[id] ?: return false
|
|
292
291
|
val sessionToken = currentJob.sessionToken
|
|
293
292
|
val generation = downloadGeneration[id] ?: 0
|
|
294
|
-
|
|
293
|
+
RNBackgroundDownloaderModuleImpl.logD(TAG, "Resuming $id with session=$sessionToken, generation=$generation")
|
|
295
294
|
|
|
296
295
|
// Create a validating listener wrapper (don't cleanup since job already exists)
|
|
297
296
|
val serviceListener = createValidatingListener(id, sessionToken, generation, cleanupOnTerminal = false)
|
|
@@ -304,7 +303,7 @@ class ResumableDownloadService : Service() {
|
|
|
304
303
|
}
|
|
305
304
|
|
|
306
305
|
fun cancelDownload(id: String): Boolean {
|
|
307
|
-
|
|
306
|
+
RNBackgroundDownloaderModuleImpl.logD(TAG, "Cancelling download: $id")
|
|
308
307
|
activeDownloads.remove(id)
|
|
309
308
|
val result = resumableDownloader.cancel(id)
|
|
310
309
|
stopServiceIfIdle()
|
|
@@ -324,7 +323,7 @@ class ResumableDownloadService : Service() {
|
|
|
324
323
|
startForeground(DownloadConstants.NOTIFICATION_ID, notification)
|
|
325
324
|
}
|
|
326
325
|
} catch (e: Exception) {
|
|
327
|
-
|
|
326
|
+
RNBackgroundDownloaderModuleImpl.logE(TAG, "Failed to start foreground service: ${e.message}")
|
|
328
327
|
}
|
|
329
328
|
}
|
|
330
329
|
|
|
@@ -379,7 +378,7 @@ class ResumableDownloadService : Service() {
|
|
|
379
378
|
setReferenceCounted(false)
|
|
380
379
|
acquire(DownloadConstants.WAKELOCK_TIMEOUT_MS)
|
|
381
380
|
}
|
|
382
|
-
|
|
381
|
+
RNBackgroundDownloaderModuleImpl.logD(TAG, "WakeLock acquired")
|
|
383
382
|
}
|
|
384
383
|
}
|
|
385
384
|
|
|
@@ -387,7 +386,7 @@ class ResumableDownloadService : Service() {
|
|
|
387
386
|
wakeLock?.let {
|
|
388
387
|
if (it.isHeld) {
|
|
389
388
|
it.release()
|
|
390
|
-
|
|
389
|
+
RNBackgroundDownloaderModuleImpl.logD(TAG, "WakeLock released")
|
|
391
390
|
}
|
|
392
391
|
}
|
|
393
392
|
wakeLock = null
|
|
@@ -399,12 +398,12 @@ class ResumableDownloadService : Service() {
|
|
|
399
398
|
val hasPausedInResumable = activeDownloads.keys.any { resumableDownloader.getState(it) != null }
|
|
400
399
|
|
|
401
400
|
if (!hasActiveDownloads && !hasPausedInResumable) {
|
|
402
|
-
|
|
401
|
+
RNBackgroundDownloaderModuleImpl.logD(TAG, "No active downloads, stopping service")
|
|
403
402
|
releaseWakeLock()
|
|
404
403
|
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
405
404
|
stopSelf()
|
|
406
405
|
} else {
|
|
407
|
-
|
|
406
|
+
RNBackgroundDownloaderModuleImpl.logD(TAG, "Service has active downloads, keeping alive")
|
|
408
407
|
updateNotification()
|
|
409
408
|
}
|
|
410
409
|
}
|