mirror of
https://github.com/zoriya/react-native-background-downloader.git
synced 2025-12-06 06:56:10 +00:00
improved progress events on ios/android
This commit is contained in:
@@ -19,19 +19,20 @@ public class OnProgress extends Thread {
|
||||
private Cursor cursor;
|
||||
private int lastBytesDownloaded;
|
||||
private int bytesTotal;
|
||||
private ProgressCallback callback;
|
||||
|
||||
private RNBGDTaskConfig config;
|
||||
private DeviceEventManagerModule.RCTDeviceEventEmitter ee;
|
||||
|
||||
public OnProgress(RNBGDTaskConfig config, long downloadId,
|
||||
DeviceEventManagerModule.RCTDeviceEventEmitter ee, Downloader downloader) {
|
||||
Downloader downloader,
|
||||
ProgressCallback callback) {
|
||||
this.config = config;
|
||||
this.callback = callback;
|
||||
|
||||
this.downloadId = downloadId;
|
||||
this.query = new DownloadManager.Query();
|
||||
query.setFilterById(this.downloadId);
|
||||
|
||||
this.ee = ee;
|
||||
this.downloader = downloader;
|
||||
}
|
||||
|
||||
@@ -73,7 +74,7 @@ public class OnProgress extends Thread {
|
||||
Thread.sleep(1000);
|
||||
} else {
|
||||
Log.d("RNBackgroundDownloader", "RNBD: OnProgress-2.3. downloadId " + downloadId);
|
||||
Thread.sleep(config.progressInterval);
|
||||
Thread.sleep(250);
|
||||
}
|
||||
|
||||
// get total bytes of the file
|
||||
@@ -93,21 +94,7 @@ public class OnProgress extends Thread {
|
||||
+ bytesDownloaded + " bytesTotal " + bytesTotal);
|
||||
|
||||
if (lastBytesDownloaded > 0 && bytesTotal > 0) {
|
||||
WritableMap params = Arguments.createMap();
|
||||
params.putString("id", config.id);
|
||||
params.putInt("bytesDownloaded", (int) lastBytesDownloaded);
|
||||
params.putInt("bytesTotal", (int) bytesTotal);
|
||||
|
||||
HashMap<String, WritableMap> progressReports = new HashMap<>();
|
||||
progressReports.put(config.id, params);
|
||||
|
||||
WritableArray reportsArray = Arguments.createArray();
|
||||
for (WritableMap report : progressReports.values()) {
|
||||
reportsArray.pushMap(report);
|
||||
}
|
||||
|
||||
Log.d("RNBackgroundDownloader", "RNBD: OnProgress-4. downloadId " + downloadId);
|
||||
ee.emit("downloadProgress", reportsArray);
|
||||
callback.onProgress(config.id, lastBytesDownloaded, bytesTotal);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return;
|
||||
|
||||
@@ -8,14 +8,12 @@ public class RNBGDTaskConfig implements Serializable {
|
||||
public String destination;
|
||||
public String metadata = "{}";
|
||||
public boolean reportedBegin;
|
||||
public int progressInterval;
|
||||
|
||||
public RNBGDTaskConfig(String id, String url, String destination, String metadata, int progressInterval) {
|
||||
public RNBGDTaskConfig(String id, String url, String destination, String metadata) {
|
||||
this.id = id;
|
||||
this.url = url;
|
||||
this.destination = destination;
|
||||
this.metadata = metadata;
|
||||
this.reportedBegin = false;
|
||||
this.progressInterval = progressInterval;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,8 +80,12 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule {
|
||||
|
||||
private Downloader downloader;
|
||||
|
||||
private Map<String, Long> condigIdToDownloadId = new HashMap<>();
|
||||
private Map<String, Long> configIdToDownloadId = new HashMap<>();
|
||||
private Map<Long, RNBGDTaskConfig> downloadIdToConfig = new HashMap<>();
|
||||
private Map<String, Double> configIdToPercent = new HashMap<>();
|
||||
private Map<String, WritableMap> progressReports = new HashMap<>();
|
||||
private Date lastProgressReportedAt = new Date();
|
||||
private int progressInterval;
|
||||
private DeviceEventManagerModule.RCTDeviceEventEmitter ee;
|
||||
private Map<String, OnProgress> onProgressThreads = new HashMap<>();
|
||||
|
||||
@@ -150,6 +154,7 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule {
|
||||
public RNBackgroundDownloaderModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
|
||||
loadDownloadIdToConfigMap();
|
||||
loadConfigMap();
|
||||
|
||||
downloader = new Downloader(reactContext);
|
||||
@@ -183,6 +188,7 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule {
|
||||
if (onProgressTh != null) {
|
||||
onProgressTh.interrupt();
|
||||
onProgressThreads.remove(configId);
|
||||
configIdToPercent.remove(configId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,19 +241,20 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule {
|
||||
synchronized (sharedLock) {
|
||||
RNBGDTaskConfig config = downloadIdToConfig.get(downloadId);
|
||||
if (config != null) {
|
||||
condigIdToDownloadId.remove(config.id);
|
||||
configIdToDownloadId.remove(config.id);
|
||||
downloadIdToConfig.remove(downloadId);
|
||||
configIdToPercent.remove(config.id);
|
||||
|
||||
saveConfigMap();
|
||||
saveDownloadIdToConfigMap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveConfigMap() {
|
||||
Log.d(getName(), "RNBD: saveConfigMap");
|
||||
private void saveDownloadIdToConfigMap() {
|
||||
Log.d(getName(), "RNBD: saveDownloadIdToConfigMap");
|
||||
|
||||
synchronized (sharedLock) {
|
||||
File file = new File(this.getReactApplicationContext().getFilesDir(), "RNFileBackgroundDownload_configMap");
|
||||
File file = new File(this.getReactApplicationContext().getFilesDir(), getName() + "_downloadIdToConfig");
|
||||
try {
|
||||
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(file));
|
||||
outputStream.writeObject(downloadIdToConfig);
|
||||
@@ -259,15 +266,74 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
}
|
||||
|
||||
private void loadConfigMap() {
|
||||
private void loadDownloadIdToConfigMap() {
|
||||
Log.d(getName(), "RNBD: loadDownloadIdToConfigMap");
|
||||
|
||||
File file = new File(this.getReactApplicationContext().getFilesDir(), getName() + "_downloadIdToConfig");
|
||||
if (file.exists()) {
|
||||
try {
|
||||
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file));
|
||||
downloadIdToConfig = (Map<Long, RNBGDTaskConfig>) inputStream.readObject();
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveConfigMap () {
|
||||
Log.d(getName(), "RNBD: saveConfigMap");
|
||||
|
||||
synchronized (sharedLock) {
|
||||
File file = new File(this.getReactApplicationContext().getFilesDir(), getName() + "_config");
|
||||
try {
|
||||
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(file));
|
||||
|
||||
Map<String, String> config = new HashMap<>();
|
||||
config.put("progressInterval", Integer.toString(progressInterval));
|
||||
outputStream.writeObject(config);
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadConfigMap () {
|
||||
Log.d(getName(), "RNBD: loadConfigMap");
|
||||
|
||||
File file = new File(this.getReactApplicationContext().getFilesDir(), "RNFileBackgroundDownload_configMap");
|
||||
try {
|
||||
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file));
|
||||
downloadIdToConfig = (Map<Long, RNBGDTaskConfig>) inputStream.readObject();
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
File file = new File(this.getReactApplicationContext().getFilesDir(), getName() + "_config");
|
||||
if (file.exists()) {
|
||||
try {
|
||||
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file));
|
||||
Map<String, Object> config = (HashMap<String, Object>) inputStream.readObject();
|
||||
// iterate over config
|
||||
for(Map.Entry<String, Object> entry : config.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
|
||||
Object valueObj = entry.getValue();
|
||||
Log.d(getName(), "RNBD: loadConfigMap-1 " + " key " + key + " valueObj " + valueObj);
|
||||
String value = null;
|
||||
if (valueObj instanceof Long) {
|
||||
value = Long.toString((Long) valueObj);
|
||||
} else if (valueObj instanceof String) {
|
||||
value = (String) valueObj;
|
||||
}
|
||||
|
||||
if (key.equals("progressInterval") && value != null) {
|
||||
try {
|
||||
int _progressInterval = Integer.parseInt(value);
|
||||
if (_progressInterval > 0) {
|
||||
progressInterval = _progressInterval;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,19 +345,23 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule {
|
||||
String destination = options.getString("destination");
|
||||
ReadableMap headers = options.getMap("headers");
|
||||
String metadata = options.getString("metadata");
|
||||
int progressInterval = options.getInt("progressInterval");
|
||||
int _progressInterval = options.getInt("progressInterval");
|
||||
if (_progressInterval > 0) {
|
||||
progressInterval = _progressInterval;
|
||||
saveConfigMap();
|
||||
}
|
||||
|
||||
boolean isAllowedOverRoaming = options.getBoolean("isAllowedOverRoaming");
|
||||
boolean isAllowedOverMetered = options.getBoolean("isAllowedOverMetered");
|
||||
|
||||
Log.d(getName(), "RNBD: download " + id + " " + url + " " + destination + " " + metadata);
|
||||
Log.d(getName(), "RNBD: download " + id + " " + url + " " + destination + " " + metadata + " " + progressInterval);
|
||||
|
||||
if (id == null || url == null || destination == null) {
|
||||
Log.e(getName(), "id, url and destination must be set");
|
||||
return;
|
||||
}
|
||||
|
||||
RNBGDTaskConfig config = new RNBGDTaskConfig(id, url, destination, metadata, progressInterval);
|
||||
RNBGDTaskConfig config = new RNBGDTaskConfig(id, url, destination, metadata);
|
||||
final Request request = new Request(Uri.parse(url));
|
||||
request.setAllowedOverRoaming(isAllowedOverRoaming);
|
||||
request.setAllowedOverMetered(isAllowedOverMetered);
|
||||
@@ -320,9 +390,10 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule {
|
||||
long downloadId = downloader.queueDownload(request);
|
||||
|
||||
synchronized (sharedLock) {
|
||||
condigIdToDownloadId.put(id, downloadId);
|
||||
configIdToDownloadId.put(id, downloadId);
|
||||
downloadIdToConfig.put(downloadId, config);
|
||||
saveConfigMap();
|
||||
configIdToPercent.put(id, 0.0);
|
||||
saveDownloadIdToConfigMap();
|
||||
|
||||
WritableMap downloadStatus = downloader.checkDownloadStatus(downloadId);
|
||||
int status = downloadStatus.getInt("status");
|
||||
@@ -341,7 +412,7 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule {
|
||||
Log.d(getName(), "RNBD: startReportingTasks-1 downloadId " + downloadId + " config.id " + config.id);
|
||||
config.reportedBegin = true;
|
||||
downloadIdToConfig.put(downloadId, config);
|
||||
saveConfigMap();
|
||||
saveDownloadIdToConfigMap();
|
||||
|
||||
Log.d(getName(), "RNBD: startReportingTasks-2 downloadId: " + downloadId);
|
||||
// report begin & progress
|
||||
@@ -376,7 +447,45 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule {
|
||||
onBeginTh.join();
|
||||
|
||||
Log.d(getName(), "RNBD: startReportingTasks-4 downloadId: " + downloadId);
|
||||
OnProgress onProgressTh = new OnProgress(config, downloadId, ee, downloader);
|
||||
OnProgress onProgressTh = new OnProgress(
|
||||
config,
|
||||
downloadId,
|
||||
downloader,
|
||||
new ProgressCallback() {
|
||||
@Override
|
||||
public void onProgress(String configId, int bytesDownloaded, int bytesTotal) {
|
||||
Log.d(getName(), "RNBD: onProgress-1 " + configId + " " + bytesDownloaded + " " + bytesTotal);
|
||||
|
||||
double prevPercent = configIdToPercent.get(configId);
|
||||
double percent = (double) bytesDownloaded / bytesTotal;
|
||||
if (percent - prevPercent > 0.01) {
|
||||
Log.d(getName(), "RNBD: onProgress-2 " + configId + " " + bytesDownloaded + " " + bytesTotal);
|
||||
WritableMap params = Arguments.createMap();
|
||||
params.putString("id", configId);
|
||||
params.putInt("bytesDownloaded", bytesDownloaded);
|
||||
params.putInt("bytesTotal", bytesTotal);
|
||||
|
||||
progressReports.put(configId, params);
|
||||
configIdToPercent.put(configId, percent);
|
||||
}
|
||||
|
||||
Date now = new Date();
|
||||
if (
|
||||
now.getTime() - lastProgressReportedAt.getTime() > progressInterval &&
|
||||
progressReports.size() > 0
|
||||
) {
|
||||
Log.d(getName(), "RNBD: onProgress-3 " + configId + " " + bytesDownloaded + " " + bytesTotal);
|
||||
WritableArray reportsArray = Arguments.createArray();
|
||||
for (Object report : progressReports.values()) {
|
||||
reportsArray.pushMap((WritableMap) report);
|
||||
}
|
||||
ee.emit("downloadProgress", reportsArray);
|
||||
lastProgressReportedAt = now;
|
||||
progressReports.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
onProgressThreads.put(config.id, onProgressTh);
|
||||
onProgressTh.start();
|
||||
Log.d(getName(), "RNBD: startReportingTasks-5 downloadId: " + downloadId);
|
||||
@@ -394,7 +503,7 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule {
|
||||
Log.d(getName(), "RNBD: pauseTask " + configId);
|
||||
|
||||
synchronized (sharedLock) {
|
||||
Long downloadId = condigIdToDownloadId.get(configId);
|
||||
Long downloadId = configIdToDownloadId.get(configId);
|
||||
if (downloadId != null) {
|
||||
downloader.pauseDownload(downloadId);
|
||||
}
|
||||
@@ -407,7 +516,7 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule {
|
||||
Log.d(getName(), "RNBD: resumeTask " + configId);
|
||||
|
||||
synchronized (sharedLock) {
|
||||
Long downloadId = condigIdToDownloadId.get(configId);
|
||||
Long downloadId = configIdToDownloadId.get(configId);
|
||||
if (downloadId != null) {
|
||||
downloader.resumeDownload(downloadId);
|
||||
}
|
||||
@@ -419,7 +528,7 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule {
|
||||
Log.d(getName(), "RNBD: stopTask-1 " + configId);
|
||||
|
||||
synchronized (sharedLock) {
|
||||
Long downloadId = condigIdToDownloadId.get(configId);
|
||||
Long downloadId = configIdToDownloadId.get(configId);
|
||||
Log.d(getName(), "RNBD: stopTask-2 " + configId + " downloadId " + downloadId);
|
||||
if (downloadId != null) {
|
||||
// DELETES CONFIG HERE SO receiver WILL NOT THROW ERROR DOWNLOAD_FAILED TO THE
|
||||
@@ -437,7 +546,7 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule {
|
||||
Log.d(getName(), "RNBD: completeHandler-1 " + configId);
|
||||
|
||||
synchronized (sharedLock) {
|
||||
Long downloadId = condigIdToDownloadId.get(configId);
|
||||
Long downloadId = configIdToDownloadId.get(configId);
|
||||
Log.d(getName(), "RNBD: completeHandler-2 " + configId + " downloadId " + downloadId);
|
||||
if (downloadId != null) {
|
||||
removeFromMaps(downloadId);
|
||||
@@ -470,14 +579,19 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule {
|
||||
params.putString("id", config.id);
|
||||
params.putString("metadata", config.metadata);
|
||||
params.putInt("state", stateMap.get(downloadStatus.getInt("status")));
|
||||
params.putInt("bytesDownloaded", downloadStatus.getInt("bytesDownloaded"));
|
||||
params.putInt("bytesTotal", downloadStatus.getInt("bytesTotal"));
|
||||
|
||||
int bytesDownloaded = downloadStatus.getInt("bytesDownloaded");
|
||||
params.putInt("bytesDownloaded", bytesDownloaded);
|
||||
|
||||
int bytesTotal = downloadStatus.getInt("bytesTotal");
|
||||
params.putInt("bytesTotal", bytesTotal);
|
||||
|
||||
foundIds.pushMap(params);
|
||||
|
||||
// TODO: MAYBE ADD headers
|
||||
|
||||
condigIdToDownloadId.put(config.id, downloadId);
|
||||
configIdToDownloadId.put(config.id, downloadId);
|
||||
configIdToPercent.put(config.id, (double) bytesDownloaded / bytesTotal);
|
||||
|
||||
// TOREMOVE
|
||||
// config.reportedBegin = true;
|
||||
|
||||
11
index.d.ts
vendored
11
index.d.ts
vendored
@@ -9,7 +9,11 @@ export interface DownloadHeaders {
|
||||
[key: string]: string | null;
|
||||
}
|
||||
|
||||
type SetHeaders = (h: DownloadHeaders) => void;
|
||||
type SetConfig = (
|
||||
headers: DownloadHeaders,
|
||||
progressInterval: number,
|
||||
isLogsEnabled: boolean
|
||||
) => void;
|
||||
|
||||
export interface BeginHandlerObject {
|
||||
expectedBytes: number;
|
||||
@@ -102,7 +106,6 @@ export interface DownloadOption {
|
||||
metadata?: object;
|
||||
isAllowedOverRoaming?: boolean;
|
||||
isAllowedOverMetered?: boolean;
|
||||
progressInterval?: number;
|
||||
}
|
||||
|
||||
export type Download = (options: DownloadOption) => DownloadTask;
|
||||
@@ -112,7 +115,7 @@ export interface Directories {
|
||||
documents: string;
|
||||
}
|
||||
|
||||
export const setHeaders: SetHeaders
|
||||
export const setConfig: SetConfig
|
||||
export const checkForExistingDownloads: CheckForExistingDownloads
|
||||
export const ensureDownloadsAreRunning: EnsureDownloadsAreRunning
|
||||
export const download: Download
|
||||
@@ -120,7 +123,7 @@ export const completeHandler: CompleteHandler
|
||||
export const directories: Directories
|
||||
|
||||
export interface RNBackgroundDownloader {
|
||||
setHeaders: SetHeaders;
|
||||
setConfig: SetConfig;
|
||||
checkForExistingDownloads: CheckForExistingDownloads;
|
||||
ensureDownloadsAreRunning: EnsureDownloadsAreRunning;
|
||||
download: Download;
|
||||
|
||||
62
index.ts
62
index.ts
@@ -5,26 +5,36 @@ const { RNBackgroundDownloader } = NativeModules
|
||||
const RNBackgroundDownloaderEmitter = new NativeEventEmitter(RNBackgroundDownloader)
|
||||
|
||||
const tasksMap = new Map()
|
||||
let headers = {}
|
||||
|
||||
const config = {
|
||||
headers: {},
|
||||
progressInterval: 1000,
|
||||
isLogsEnabled: false,
|
||||
}
|
||||
|
||||
function log(...args) {
|
||||
if (config.isLogsEnabled)
|
||||
console.log('[RNBackgroundDownloader]', ...args)
|
||||
}
|
||||
|
||||
RNBackgroundDownloaderEmitter.addListener('downloadBegin', ({ id, ...rest }) => {
|
||||
console.log('[RNBackgroundDownloader] downloadBegin', id, rest)
|
||||
log('[RNBackgroundDownloader] downloadBegin', id, rest)
|
||||
const task = tasksMap.get(id)
|
||||
task?.onBegin(rest)
|
||||
})
|
||||
|
||||
RNBackgroundDownloaderEmitter.addListener('downloadProgress', events => {
|
||||
// console.log('[RNBackgroundDownloader] downloadProgress-1', events, tasksMap)
|
||||
// log('[RNBackgroundDownloader] downloadProgress-1', events, tasksMap)
|
||||
for (const event of events) {
|
||||
const { id, ...rest } = event
|
||||
const task = tasksMap.get(id)
|
||||
// console.log('[RNBackgroundDownloader] downloadProgress-2', id, task)
|
||||
// log('[RNBackgroundDownloader] downloadProgress-2', id, task)
|
||||
task?.onProgress(rest)
|
||||
}
|
||||
})
|
||||
|
||||
RNBackgroundDownloaderEmitter.addListener('downloadComplete', ({ id, ...rest }) => {
|
||||
console.log('[RNBackgroundDownloader] downloadComplete', id, rest)
|
||||
log('[RNBackgroundDownloader] downloadComplete', id, rest)
|
||||
const task = tasksMap.get(id)
|
||||
task?.onDone(rest)
|
||||
|
||||
@@ -32,29 +42,34 @@ RNBackgroundDownloaderEmitter.addListener('downloadComplete', ({ id, ...rest })
|
||||
})
|
||||
|
||||
RNBackgroundDownloaderEmitter.addListener('downloadFailed', ({ id, ...rest }) => {
|
||||
console.log('[RNBackgroundDownloader] downloadFailed', id, rest)
|
||||
log('[RNBackgroundDownloader] downloadFailed', id, rest)
|
||||
const task = tasksMap.get(id)
|
||||
task?.onError(rest)
|
||||
|
||||
tasksMap.delete(id)
|
||||
})
|
||||
|
||||
export function setHeaders (h = {}) {
|
||||
if (typeof h !== 'object')
|
||||
throw new Error('[RNBackgroundDownloader] headers must be an object')
|
||||
export function setConfig({ headers, progressInterval, isLogsEnabled }) {
|
||||
if (typeof headers === 'object') config.headers = headers
|
||||
|
||||
headers = h
|
||||
if (progressInterval != null)
|
||||
if (typeof progressInterval === 'number' && progressInterval >= 200)
|
||||
config.progressInterval = progressInterval
|
||||
else
|
||||
console.warn(`[RNBackgroundDownloader] progressInterval must be a number >= 200. You passed ${progressInterval}`)
|
||||
|
||||
if (typeof isLogsEnabled === 'boolean') config.isLogsEnabled = isLogsEnabled
|
||||
}
|
||||
|
||||
export function checkForExistingDownloads () {
|
||||
console.log('[RNBackgroundDownloader] checkForExistingDownloads-1')
|
||||
export function checkForExistingDownloads() {
|
||||
log('[RNBackgroundDownloader] checkForExistingDownloads-1')
|
||||
return RNBackgroundDownloader.checkForExistingDownloads()
|
||||
.then(foundTasks => {
|
||||
console.log('[RNBackgroundDownloader] checkForExistingDownloads-2', foundTasks)
|
||||
log('[RNBackgroundDownloader] checkForExistingDownloads-2', foundTasks)
|
||||
return foundTasks.map(taskInfo => {
|
||||
// SECOND ARGUMENT RE-ASSIGNS EVENT HANDLERS
|
||||
const task = new DownloadTask(taskInfo, tasksMap.get(taskInfo.id))
|
||||
console.log('[RNBackgroundDownloader] checkForExistingDownloads-3', taskInfo)
|
||||
log('[RNBackgroundDownloader] checkForExistingDownloads-3', taskInfo)
|
||||
|
||||
if (taskInfo.state === RNBackgroundDownloader.TaskRunning) {
|
||||
task.state = 'DOWNLOADING'
|
||||
@@ -76,8 +91,8 @@ export function checkForExistingDownloads () {
|
||||
})
|
||||
}
|
||||
|
||||
export function ensureDownloadsAreRunning () {
|
||||
console.log('[RNBackgroundDownloader] ensureDownloadsAreRunning')
|
||||
export function ensureDownloadsAreRunning() {
|
||||
log('[RNBackgroundDownloader] ensureDownloadsAreRunning')
|
||||
return checkForExistingDownloads()
|
||||
.then(tasks => {
|
||||
for (const task of tasks)
|
||||
@@ -88,7 +103,7 @@ export function ensureDownloadsAreRunning () {
|
||||
})
|
||||
}
|
||||
|
||||
export function completeHandler (jobId: string) {
|
||||
export function completeHandler(jobId: string) {
|
||||
if (jobId == null) {
|
||||
console.warn('[RNBackgroundDownloader] completeHandler: jobId is empty')
|
||||
return
|
||||
@@ -105,15 +120,14 @@ type DownloadOptions = {
|
||||
metadata?: object,
|
||||
isAllowedOverRoaming?: boolean,
|
||||
isAllowedOverMetered?: boolean,
|
||||
progressInterval?: number,
|
||||
}
|
||||
|
||||
export function download (options: DownloadOptions) {
|
||||
console.log('[RNBackgroundDownloader] download', options)
|
||||
export function download(options: DownloadOptions) {
|
||||
log('[RNBackgroundDownloader] download', options)
|
||||
if (!options.id || !options.url || !options.destination)
|
||||
throw new Error('[RNBackgroundDownloader] id, url and destination are required')
|
||||
|
||||
options.headers = { ...headers, ...(options.headers || {}) }
|
||||
options.headers = { ...config.headers, ...options.headers }
|
||||
|
||||
if (!(options.metadata && typeof options.metadata === 'object'))
|
||||
options.metadata = {}
|
||||
@@ -122,7 +136,6 @@ export function download (options: DownloadOptions) {
|
||||
|
||||
if (options.isAllowedOverRoaming == null) options.isAllowedOverRoaming = true
|
||||
if (options.isAllowedOverMetered == null) options.isAllowedOverMetered = true
|
||||
if (options.progressInterval == null) options.progressInterval = 1000
|
||||
|
||||
const task = new DownloadTask({
|
||||
id: options.id,
|
||||
@@ -133,6 +146,7 @@ export function download (options: DownloadOptions) {
|
||||
RNBackgroundDownloader.download({
|
||||
...options,
|
||||
metadata: JSON.stringify(options.metadata),
|
||||
progressInterval: config.progressInterval,
|
||||
})
|
||||
|
||||
return task
|
||||
@@ -147,6 +161,8 @@ export default {
|
||||
checkForExistingDownloads,
|
||||
ensureDownloadsAreRunning,
|
||||
completeHandler,
|
||||
setHeaders,
|
||||
|
||||
setConfig,
|
||||
|
||||
directories,
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#import "RNBGDTaskConfig.h"
|
||||
|
||||
#define ID_TO_CONFIG_MAP_KEY @"com.eko.bgdownloadidmap"
|
||||
#define CONFIG_MAP_KEY @"com.eko.config_map"
|
||||
|
||||
static CompletionHandler storedCompletionHandler;
|
||||
|
||||
@@ -19,9 +20,11 @@ static CompletionHandler storedCompletionHandler;
|
||||
NSMutableDictionary<NSNumber *, RNBGDTaskConfig *> *taskToConfigMap;
|
||||
NSMutableDictionary<NSString *, NSURLSessionDownloadTask *> *idToTaskMap;
|
||||
NSMutableDictionary<NSString *, NSData *> *idToResumeDataMap;
|
||||
NSMutableDictionary<NSString *, NSNumber *> *idToPercentMap;
|
||||
NSMutableDictionary<NSString *, NSDictionary *> *progressReports;
|
||||
NSDate *lastProgressReport;
|
||||
NSDate *lastProgressReportedAt;
|
||||
NSNumber *sharedLock;
|
||||
float progressInterval; // IN SECONDS
|
||||
BOOL isNotificationCenterInited;
|
||||
}
|
||||
|
||||
@@ -65,8 +68,22 @@ RCT_EXPORT_MODULE();
|
||||
if (taskToConfigMap == nil) {
|
||||
taskToConfigMap = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
idToTaskMap = [[NSMutableDictionary alloc] init];
|
||||
|
||||
NSDictionary *configMap = [self deserialize:[[NSUserDefaults standardUserDefaults] objectForKey:CONFIG_MAP_KEY]];
|
||||
if (configMap != nil) {
|
||||
for (NSString *key in configMap) {
|
||||
if ([key isEqual: @"progressInterval"]) {
|
||||
progressInterval = [configMap[key] intValue];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isnan(progressInterval)) {
|
||||
progressInterval = 1.0;
|
||||
}
|
||||
|
||||
self->idToTaskMap = [[NSMutableDictionary alloc] init];
|
||||
idToResumeDataMap = [[NSMutableDictionary alloc] init];
|
||||
idToPercentMap = [[NSMutableDictionary alloc] init];
|
||||
NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
|
||||
NSString *sessonIdentifier = [bundleIdentifier stringByAppendingString:@".backgrounddownloadtask"];
|
||||
sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessonIdentifier];
|
||||
@@ -83,7 +100,7 @@ RCT_EXPORT_MODULE();
|
||||
}
|
||||
|
||||
progressReports = [[NSMutableDictionary alloc] init];
|
||||
lastProgressReport = [[NSDate alloc] init];
|
||||
lastProgressReportedAt = [[NSDate alloc] init];
|
||||
sharedLock = [NSNumber numberWithInt:1];
|
||||
}
|
||||
return self;
|
||||
@@ -151,7 +168,8 @@ RCT_EXPORT_MODULE();
|
||||
[[NSUserDefaults standardUserDefaults] setObject:[self serialize: taskToConfigMap] forKey:ID_TO_CONFIG_MAP_KEY];
|
||||
|
||||
if (taskConfig) {
|
||||
[idToTaskMap removeObjectForKey:taskConfig.id];
|
||||
[self->idToTaskMap removeObjectForKey:taskConfig.id];
|
||||
[idToPercentMap removeObjectForKey:taskConfig.id];
|
||||
}
|
||||
// TOREMOVE - GIVES ERROR IN JS ON HOT RELOAD
|
||||
// if (taskToConfigMap.count == 0) {
|
||||
@@ -194,12 +212,24 @@ RCT_EXPORT_MODULE();
|
||||
|
||||
#pragma mark - JS exported methods
|
||||
RCT_EXPORT_METHOD(download: (NSDictionary *) options) {
|
||||
NSLog(@"[RNBackgroundDownloader] - [download]");
|
||||
NSLog(@"[RNBackgroundDownloader] - [download] - 1");
|
||||
NSString *identifier = options[@"id"];
|
||||
NSString *url = options[@"url"];
|
||||
NSString *destination = options[@"destination"];
|
||||
NSString *metadata = options[@"metadata"];
|
||||
NSDictionary *headers = options[@"headers"];
|
||||
|
||||
|
||||
NSNumber *_progressInterval = options[@"progressInterval"];
|
||||
if (_progressInterval) {
|
||||
progressInterval = [_progressInterval intValue] / 1000; // progressInterval IN options SUPPLIED IN MILLISECONDS
|
||||
|
||||
NSDictionary *configMap = @{@"progressInterval": [NSNumber numberWithFloat:progressInterval]};
|
||||
[[NSUserDefaults standardUserDefaults] setObject:[self serialize: configMap] forKey:CONFIG_MAP_KEY];
|
||||
}
|
||||
|
||||
|
||||
NSLog(@"[RNBackgroundDownloader] - [download] - 1 url %@ destination %@ progressInterval %f", url, destination, progressInterval);
|
||||
if (identifier == nil || url == nil || destination == nil) {
|
||||
NSLog(@"[RNBackgroundDownloader] - [Error] id, url and destination must be set");
|
||||
return;
|
||||
@@ -225,17 +255,18 @@ RCT_EXPORT_METHOD(download: (NSDictionary *) options) {
|
||||
taskToConfigMap[@(task.taskIdentifier)] = taskConfig;
|
||||
[[NSUserDefaults standardUserDefaults] setObject:[self serialize: taskToConfigMap] forKey:ID_TO_CONFIG_MAP_KEY];
|
||||
|
||||
idToTaskMap[identifier] = task;
|
||||
self->idToTaskMap[identifier] = task;
|
||||
idToPercentMap[identifier] = @0.0;
|
||||
|
||||
[task resume];
|
||||
lastProgressReport = [[NSDate alloc] init];
|
||||
lastProgressReportedAt = [[NSDate alloc] init];
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(pauseTask: (NSString *)identifier) {
|
||||
NSLog(@"[RNBackgroundDownloader] - [pauseTask]");
|
||||
@synchronized (sharedLock) {
|
||||
NSURLSessionDownloadTask *task = idToTaskMap[identifier];
|
||||
NSURLSessionDownloadTask *task = self->idToTaskMap[identifier];
|
||||
if (task != nil && task.state == NSURLSessionTaskStateRunning) {
|
||||
[task suspend];
|
||||
}
|
||||
@@ -245,7 +276,7 @@ RCT_EXPORT_METHOD(pauseTask: (NSString *)identifier) {
|
||||
RCT_EXPORT_METHOD(resumeTask: (NSString *)identifier) {
|
||||
NSLog(@"[RNBackgroundDownloader] - [resumeTask]");
|
||||
@synchronized (sharedLock) {
|
||||
NSURLSessionDownloadTask *task = idToTaskMap[identifier];
|
||||
NSURLSessionDownloadTask *task = self->idToTaskMap[identifier];
|
||||
if (task != nil && task.state == NSURLSessionTaskStateSuspended) {
|
||||
[task resume];
|
||||
}
|
||||
@@ -255,7 +286,7 @@ RCT_EXPORT_METHOD(resumeTask: (NSString *)identifier) {
|
||||
RCT_EXPORT_METHOD(stopTask: (NSString *)identifier) {
|
||||
NSLog(@"[RNBackgroundDownloader] - [stopTask]");
|
||||
@synchronized (sharedLock) {
|
||||
NSURLSessionDownloadTask *task = idToTaskMap[identifier];
|
||||
NSURLSessionDownloadTask *task = self->idToTaskMap[identifier];
|
||||
if (task != nil) {
|
||||
[task cancel];
|
||||
[self removeTaskFromMap:task];
|
||||
@@ -268,16 +299,16 @@ RCT_EXPORT_METHOD(checkForExistingDownloads: (RCTPromiseResolveBlock)resolve rej
|
||||
[self lazyInitSession];
|
||||
[urlSession getTasksWithCompletionHandler:^(NSArray<NSURLSessionDataTask *> * _Nonnull dataTasks, NSArray<NSURLSessionUploadTask *> * _Nonnull uploadTasks, NSArray<NSURLSessionDownloadTask *> * _Nonnull downloadTasks) {
|
||||
NSMutableArray *idsFound = [[NSMutableArray alloc] init];
|
||||
@synchronized (sharedLock) {
|
||||
@synchronized (self->sharedLock) {
|
||||
for (NSURLSessionDownloadTask *foundTask in downloadTasks) {
|
||||
NSURLSessionDownloadTask __strong *task = foundTask;
|
||||
RNBGDTaskConfig *taskConfig = taskToConfigMap[@(task.taskIdentifier)];
|
||||
RNBGDTaskConfig *taskConfig = self->taskToConfigMap[@(task.taskIdentifier)];
|
||||
if (taskConfig) {
|
||||
if ((task.state == NSURLSessionTaskStateCompleted || task.state == NSURLSessionTaskStateSuspended) && task.countOfBytesReceived < task.countOfBytesExpectedToReceive) {
|
||||
if (task.error && task.error.userInfo[NSURLSessionDownloadTaskResumeData] != nil) {
|
||||
task = [urlSession downloadTaskWithResumeData:task.error.userInfo[NSURLSessionDownloadTaskResumeData]];
|
||||
task = [self->urlSession downloadTaskWithResumeData:task.error.userInfo[NSURLSessionDownloadTaskResumeData]];
|
||||
} else {
|
||||
task = [urlSession downloadTaskWithURL:task.currentRequest.URL];
|
||||
task = [self->urlSession downloadTaskWithURL:task.currentRequest.URL];
|
||||
}
|
||||
[task resume];
|
||||
}
|
||||
@@ -290,8 +321,11 @@ RCT_EXPORT_METHOD(checkForExistingDownloads: (RCTPromiseResolveBlock)resolve rej
|
||||
@"bytesTotal": [NSNumber numberWithLongLong:task.countOfBytesExpectedToReceive]
|
||||
}];
|
||||
taskConfig.reportedBegin = YES;
|
||||
taskToConfigMap[@(task.taskIdentifier)] = taskConfig;
|
||||
idToTaskMap[taskConfig.id] = task;
|
||||
self->taskToConfigMap[@(task.taskIdentifier)] = taskConfig;
|
||||
self->idToTaskMap[taskConfig.id] = task;
|
||||
|
||||
NSNumber *percent = task.countOfBytesExpectedToReceive > 0 ? [NSNumber numberWithFloat:(float)task.countOfBytesReceived/(float)task.countOfBytesExpectedToReceive] : @0.0;
|
||||
self->idToPercentMap[taskConfig.id] = percent;
|
||||
} else {
|
||||
[task cancel];
|
||||
}
|
||||
@@ -378,19 +412,24 @@ RCT_EXPORT_METHOD(completeHandler:(nonnull NSString *)jobId
|
||||
taskCofig.reportedBegin = YES;
|
||||
}
|
||||
|
||||
progressReports[taskCofig.id] = @{
|
||||
@"id": taskCofig.id,
|
||||
@"bytesDownloaded": [NSNumber numberWithLongLong: bytesTotalWritten],
|
||||
@"bytesTotal": [NSNumber numberWithLongLong: bytesTotalExpectedToWrite]
|
||||
};
|
||||
NSNumber *prevPercent = idToPercentMap[taskCofig.id];
|
||||
NSNumber *percent = [NSNumber numberWithFloat:(float)bytesTotalWritten/(float)bytesTotalExpectedToWrite];
|
||||
if ([percent floatValue] - [prevPercent floatValue] > 0.01f) {
|
||||
progressReports[taskCofig.id] = @{
|
||||
@"id": taskCofig.id,
|
||||
@"bytesDownloaded": [NSNumber numberWithLongLong: bytesTotalWritten],
|
||||
@"bytesTotal": [NSNumber numberWithLongLong: bytesTotalExpectedToWrite]
|
||||
};
|
||||
idToPercentMap[taskCofig.id] = percent;
|
||||
}
|
||||
|
||||
|
||||
NSDate *now = [[NSDate alloc] init];
|
||||
// TODO: PROPOSE OPTION TO SET PROGRESS INTERVAL (ITS COMMON FOR ALL DOWNLOADS. ON ANDROID SIDE ITS PER DOWNLOAD. MAYBE CHANGE ANDROID TO BE COMMON AS WELL AND PREVENT IT SEND TOO OFTEN. ALSO SEND IT ONLY IF PROGRESS CHANGED - SEE PREV IMPLEMENTATION IN 2.10)
|
||||
if ([now timeIntervalSinceDate:lastProgressReport] > 0.5 && progressReports.count > 0) {
|
||||
if ([now timeIntervalSinceDate:lastProgressReportedAt] > progressInterval && progressReports.count > 0) {
|
||||
if (self.bridge) {
|
||||
[self sendEventWithName:@"downloadProgress" body:[progressReports allValues]];
|
||||
}
|
||||
lastProgressReport = now;
|
||||
lastProgressReportedAt = now;
|
||||
[progressReports removeAllObjects];
|
||||
}
|
||||
}
|
||||
@@ -436,15 +475,27 @@ RCT_EXPORT_METHOD(completeHandler:(nonnull NSString *)jobId
|
||||
|
||||
#pragma mark - serialization
|
||||
- (NSData *)serialize: (id)obj {
|
||||
return [NSKeyedArchiver archivedDataWithRootObject:obj];
|
||||
NSError *error;
|
||||
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:obj requiringSecureCoding:NO error:&error];
|
||||
|
||||
if (error) {
|
||||
// Handle the error
|
||||
NSLog(@"[RNBackgroundDownloader] Serialization error: %@", error);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
- (id)deserialize: (NSData *)data {
|
||||
if (data == nil) {
|
||||
return nil;
|
||||
NSError *error;
|
||||
id obj = [NSKeyedUnarchiver unarchivedObjectOfClass:[NSObject class] fromData:data error:&error];
|
||||
|
||||
if (error) {
|
||||
// Handle the error
|
||||
NSLog(@"[RNBackgroundDownloader] Deserialization error: %@", error);
|
||||
}
|
||||
|
||||
return [NSKeyedUnarchiver unarchiveObjectWithData:data];
|
||||
return obj;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Reference in New Issue
Block a user