Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ Here are the optional notification handlers you can pass to be informed of downl
|`onWillRemove?: (url: string) => Promise<void>`| Called before any url is removed from the queue. This is async because `removeUrl` (and also `setQueue`, when it needs to remove some urls) will block until you return from this, giving you the opportunity remove any dependencies on any downloaded local file before it's deleted.|
|`onError?: (url: string, error: any) => void`| Called when there's been an issue downloading the file. Note that this is mostly for you to communicate something to the user, or to do other housekeeping; DownloadQueue will automatically re-attempt the download every minute (while you're online) until it succeeds.|

### `terminate(): void`
### `async terminate(): Promise<void>`

Terminates all pending downloads and stops all activity, including
processing lazy-deletes. You can re-init() if you'd like -- but in most cases where you plan to re-init, `pause()` might be what you really meant.
Expand Down Expand Up @@ -120,11 +120,11 @@ Returns a `DownloadQueueStatus` object reflecting the status of a url's download

Returns the status of all urls in the queue, excluding urls marked for lazy deletion.

### `pauseAll(): void`
### `async pauseAll(): Promise<void>`

Pauses all active downloads. Note that if you just want to download on certain types of connections, you should instead use `activeNetworkTypes` in `init()`. For instance, to avoid cellular data charges, you might pass `activeNetworkTypes: ["wifi", "ethernet"]`.

### `resumeAll(): void`
### `async resumeAll(): Promise<void>`

Resumes all active downloads that were previously paused. If you `init()` with `startActive === false`, you'll want to call this at some point or else downloads will never happen. Also, downloads will only proceed if the network connection type passes the `activeNetworkTypes` filter (which by default allows all connection types).

Expand Down
47 changes: 16 additions & 31 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
"@ryansonshine/commitizen": "^4.2.8",
"@ryansonshine/cz-conventional-changelog": "^3.3.4",
"@types/jest": "^27.5.2",
"@types/kesha-antonov__react-native-background-downloader": "^2.6.0",
"@types/node": "^12.20.11",
"@types/react-native-background-downloader": "^2.3.6",
"@typescript-eslint/eslint-plugin": "^4.22.0",
Expand All @@ -70,7 +69,7 @@
"typescript": "^4.2.4"
},
"peerDependencies": {
"@kesha-antonov/react-native-background-downloader": "^3.2.6",
"@fivecar/react-native-background-downloader": "^4.5.6",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle unknown totals from the v4 downloader

The v4 downloader line introduces progress events where bytesTotal can be -1 when the server omits Content-Length, but addTask() still computes fraction as bytesDownloaded / bytesTotal before calling onProgress. For chunked or otherwise unknown-size downloads, consumers now receive negative progress values instead of an indeterminate/unknown progress signal, so the progress mapping needs to guard bytesTotal <= 0.

Useful? React with 👍 / 👎.

"@react-native-async-storage/async-storage": "^1.17.11",
"react-native-fs": "^2.20.0"
},
Expand Down
80 changes: 38 additions & 42 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import {
checkForExistingDownloads,
completeHandler,
download,
createDownloadTask,
DownloadTask,
ensureDownloadsAreRunning,
} from "@kesha-antonov/react-native-background-downloader";
getExistingDownloadTasks,
} from "@fivecar/react-native-background-downloader";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update install docs for the new peer package

After this import change, apps must install @fivecar/react-native-background-downloader, but the README's install section still tells users to install @kesha-antonov/react-native-background-downloader. A consumer following the documented peer-dependency steps will not have the module this package imports, so bundling/typechecking fails with an unresolved @fivecar/... module.

Useful? React with 👍 / 👎.

import AsyncStorage from "@react-native-async-storage/async-storage";
import KeyValueFileSystem from "key-value-file-system";
import { Platform } from "react-native";
Expand Down Expand Up @@ -229,7 +228,7 @@ export default class DownloadQueue {

const [specData, existingTasks, dirFilenames] = await Promise.all([
this.kvfs.readMulti<Spec>(`${this.keyFromId("")}*`),
checkForExistingDownloads(),
getExistingDownloadTasks(),
this.getDirFilenames(),
]);
const now = Date.now();
Expand Down Expand Up @@ -269,14 +268,6 @@ export default class DownloadQueue {
// First revive tasks that were working in the background
if (existingTasks.length > 0) {
await Promise.all(existingTasks.map(task => this.reviveTask(task)));
await ensureDownloadsAreRunning();
if (!this.active) {
// ensureDownloadsAreRunning forces all un-stopped downloads to start.
// So we'll need to stop them again if !active.
existingTasks
.filter(task => task.state === "DOWNLOADING")
.forEach(task => void task.pause());
}
}

// Now start downloads for specs that haven't finished
Expand Down Expand Up @@ -351,10 +342,12 @@ export default class DownloadQueue {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const state = await this.netInfoFetchState!();

this.onNetInfoChanged(state);
await this.onNetInfoChanged(state);
this.netInfoUnsubscriber = netInfoAddEventListener(
(state: DownloadQueueNetInfoState) => {
this.onNetInfoChanged(state);
// The caller's listener signature is synchronous (it comes from
// NetInfo.addEventListener), so we can't await this here.
void this.onNetInfoChanged(state);
}
);
}
Expand All @@ -367,9 +360,9 @@ export default class DownloadQueue {
* processing lazy-deletes. You can re-init() if you'd like -- but in most
* cases where you plan to re-init, pause() might be what you really meant.
*/
terminate(): void {
async terminate(): Promise<void> {
this.active = false;
this.tasks.forEach(task => void task.stop());
await Promise.all(this.tasks.map(task => task.stop()));
this.tasks = [];
this.specs = [];
this.handlers = undefined;
Expand Down Expand Up @@ -472,7 +465,7 @@ export default class DownloadQueue {

const task = this.removeTask(spec.id);
if (task) {
task.stop();
await task.stop();
}

// If it's a lazy delete, just update the spec but don't mess with files.
Expand Down Expand Up @@ -589,16 +582,16 @@ export default class DownloadQueue {
* Pauses all active downloads. Most used to implement wifi-only downloads,
* by pausing when NetInfo reports a non-wifi connection.
*/
pauseAll(): void {
async pauseAll(): Promise<void> {
this.verifyInitialized();

this.isPausedByUser = true;
this.pauseAllInternal();
await this.pauseAllInternal();
}

private pauseAllInternal(): void {
private async pauseAllInternal(): Promise<void> {
this.active = false;
this.tasks.forEach(task => void task.pause());
await Promise.all(this.tasks.map(task => task.pause()));

if (this.errorTimer) {
clearInterval(this.errorTimer);
Expand All @@ -613,20 +606,20 @@ export default class DownloadQueue {
* network connection type passes the `activeNetworkTypes` filter (which by
* default allows all connection types).
*/
resumeAll(): void {
async resumeAll(): Promise<void> {
this.verifyInitialized();

this.isPausedByUser = false;
if (!this.wouldAutoPause) {
// We only resume downloads if we weren't told otherwise to auto-pause
// based on network conditions.
this.resumeAllInternal();
await this.resumeAllInternal();
}
}

private resumeAllInternal() {
private async resumeAllInternal(): Promise<void> {
this.active = true;
this.tasks.forEach(task => void task.resume());
await Promise.all(this.tasks.map(task => task.resume()));

if (this.erroredIds.size > 0) {
this.ensureErrorTimerOn();
Expand Down Expand Up @@ -684,13 +677,14 @@ export default class DownloadQueue {
void this.kvfs.write(this.keyFromId(spec.id), spec);
}

const task = download({
const task = createDownloadTask({
id: spec.id,
url: spec.url,
destination: spec.path,
});

this.addTask(spec.url, task);
task.start();

// Bug: https://github.com/kesha-antonov/react-native-background-downloader/issues/23
// This is the ideal place to pause, but the downloader has a bug that
Expand Down Expand Up @@ -720,7 +714,7 @@ export default class DownloadQueue {
this.activeNetworkTypes = types;
if (this.netInfoFetchState) {
const state = await this.netInfoFetchState();
this.onNetInfoChanged(state);
await this.onNetInfoChanged(state);
} else {
throw new Error(
"Can't `setActiveNetworkType` without having init'd with `netInfoFetchState`"
Expand All @@ -743,7 +737,7 @@ export default class DownloadQueue {
// download sends us this begin() callback. When #23 is fixed, we can
// in theory move this into the end of start(), after download().
if (!this.active) {
task.pause();
void task.pause();
}
this.handlers?.onBegin?.(url, data.expectedBytes);
})
Expand All @@ -757,7 +751,7 @@ export default class DownloadQueue {
// See note in begin() above. We can get progress callbacks even without
// begin() (e.g. in the case of resuming a background task upon launch).
if (!this.active) {
task.pause();
void task.pause();
}
const fraction = bytesDownloaded / bytesTotal;
this.handlers?.onProgress?.(url, fraction, bytesDownloaded, bytesTotal);
Expand Down Expand Up @@ -790,7 +784,7 @@ export default class DownloadQueue {
await this.kvfs.write(this.keyFromId(spec.id), spec);

if (Platform.OS === "ios") {
completeHandler(task.id);
void completeHandler(task.id);
}

// Only notify the client once everything has completed successfully and
Expand Down Expand Up @@ -857,7 +851,9 @@ export default class DownloadQueue {
this.specs = this.specs.filter(spec => !delIds.has(spec.id));
}

private onNetInfoChanged(state: DownloadQueueNetInfoState) {
private async onNetInfoChanged(
state: DownloadQueueNetInfoState
): Promise<void> {
const shouldAutoPause =
!state.isConnected ||
(this.activeNetworkTypes.length > 0 &&
Expand All @@ -872,9 +868,9 @@ export default class DownloadQueue {
// asked us to pause. If they have, we leave their wishes alone.
if (!this.isPausedByUser) {
if (shouldAutoPause) {
this.pauseAllInternal();
await this.pauseAllInternal();
} else {
this.resumeAllInternal();
await this.resumeAllInternal();
}
}
}
Expand Down Expand Up @@ -916,7 +912,7 @@ export default class DownloadQueue {
// that we begin the download again.
// Downloader docs say every task needs to be paused or stopped.
// So we stop here.
task.stop();
await task.stop();
// Since the file is missing from disk, yet the downloader thinks
// it's done, we restart the download.
this.start(spec);
Expand All @@ -940,18 +936,18 @@ export default class DownloadQueue {
}

if (shouldAddTask) {
// Downloader docs say to pause tasks before reattaching our handlers
task.pause();
this.addTask(spec.url, task);
if (!this.active && task.state === "DOWNLOADING") {
await task.pause();
Comment on lines +940 to +941

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resume revived paused tasks when active

When init() restores an unfinished task whose native state is PAUSED, the default active queue now only reattaches handlers and skips this inactive-queue pause branch, so the task remains paused indefinitely and the later spec loop also skips it because it appears in existingTasks. This regresses the advertised/default behavior that suspended downloads are resumed on launch unless the caller passed startActive: false; resume PAUSED revived tasks when this.active is true.

Useful? React with 👍 / 👎.

}
} else {
// According to downloader docs, we must either stop or pause revived
// tasks because ensureTasksRunning() will unpause any download we
// didn't explicitly stop (!)
task.stop();
await task.stop();
}
} else {
if (this.isTaskDownloading(task)) {
task.stop();
// Await this so the unlink() below (for lazy-delete revivals) can't
// race a native write that's still in flight.
await task.stop();
}

if (!spec) {
Expand Down
Loading
Loading