Skip to content

Commit

Permalink
Fix issues around multiple downloads (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
theogravity authored Aug 4, 2024
1 parent 205d625 commit 55358ba
Show file tree
Hide file tree
Showing 36 changed files with 77 additions and 733 deletions.
7 changes: 7 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
tools
biome.json
jest.config.js
test
config
__mocks__
.github
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# 3.2.0 (2024-08-04)

- Fix issues around starting multiple downloads at the same time.
- No longer need to use `await` before a download call when multiple downloads are started at the same time.

# 3.1.1 (2024-07-31)

- Added more debug logging
Expand Down
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ import { ElectronDownloadManager } from 'electron-dl-manager';
const manager = new ElectronDownloadManager();

// Start a download
// You *must* call manager.download() with await or
// you may get unexpected behavior
const id = await manager.download({
window: browserWindowInstance,
url: 'https://example.com/file.zip',
Expand Down Expand Up @@ -188,8 +186,6 @@ interface DownloadManagerConstructorParams {

Starts a file download. Returns the `id` of the download.

**You must call `download()` with `await` or you may get unexpected behavior.**

```typescript
download(params: DownloadParams): Promise<string>
```
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "electron-dl-manager",
"version": "3.1.1",
"version": "3.2.0",
"description": "A library for implementing file downloads in Electron with 'save as' dialog and id support.",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
Expand Down
86 changes: 62 additions & 24 deletions src/ElectronDownloadManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,34 @@ import type {
} from "./types";
import { truncateUrl } from "./utils";

/**
* This is used to solve an issue where multiple downloads are started at the same time.
* For example, Promise.all([download1, download2, ...]) will start both downloads at the same
* time. This is problematic because the will-download event is not guaranteed to fire in the
* order that the downloads were started.
*
* So we use this to make sure that will-download fires in the order that the downloads were
* started by executing the downloads in a sequential fashion.
*
* For more information see:
* https://github.com/theogravity/electron-dl-manager/issues/11
*/
class DownloadQueue {
private promise = Promise.resolve() as unknown as Promise<string>;

add(task: () => Promise<string>): Promise<string> {
this.promise = this.promise.then(() => task());
return this.promise;
}
}

/**
* Enables handling downloads in Electron.
*/
export class ElectronDownloadManager implements IElectronDownloadManager {
protected downloadData: Record<string, DownloadData>;
protected logger: DebugLoggerFn;
private downloadQueue = new DownloadQueue();

constructor(params: DownloadManagerConstructorParams = {}) {
this.downloadData = {};
Expand Down Expand Up @@ -88,30 +110,33 @@ export class ElectronDownloadManager implements IElectronDownloadManager {
* Returns the id of the download.
*/
async download(params: DownloadConfig): Promise<string> {
return new Promise((resolve, reject) => {
try {
if (params.saveAsFilename && params.saveDialogOptions) {
return reject(Error("You cannot define both saveAsFilename and saveDialogOptions to start a download"));
}

const downloadInitiator = new DownloadInitiator({
debugLogger: this.logger,
onCleanup: (data) => {
this.cleanup(data);
},
onDownloadInit: (data) => {
this.downloadData[data.id] = data;
resolve(data.id);
},
});

this.log(`[${downloadInitiator.getDownloadId()}] Registering download for url: ${truncateUrl(params.url)}`);
params.window.webContents.session.once("will-download", downloadInitiator.generateOnWillDownload(params));
params.window.webContents.downloadURL(params.url, params.downloadURLOptions);
} catch (e) {
reject(e);
}
});
return this.downloadQueue.add(
() =>
new Promise<string>((resolve, reject) => {
try {
if (params.saveAsFilename && params.saveDialogOptions) {
return reject(Error("You cannot define both saveAsFilename and saveDialogOptions to start a download"));
}

const downloadInitiator = new DownloadInitiator({
debugLogger: this.logger,
onCleanup: (data) => {
this.cleanup(data);
},
onDownloadInit: (data) => {
this.downloadData[data.id] = data;
resolve(data.id);
},
});

this.log(`[${downloadInitiator.getDownloadId()}] Registering download for url: ${truncateUrl(params.url)}`);
params.window.webContents.session.once("will-download", downloadInitiator.generateOnWillDownload(params));
params.window.webContents.downloadURL(params.url, params.downloadURLOptions);
} catch (e) {
reject(e);
}
}),
);
}

protected cleanup(data: DownloadData) {
Expand Down Expand Up @@ -161,3 +186,16 @@ export class ElectronDownloadManager implements IElectronDownloadManager {
dbg.detach();
}
}

function waitTicks(n: number): Promise<void> {
return new Promise((resolve) => {
function tick(count: number) {
if (count <= 0) {
resolve();
} else {
process.nextTick(() => tick(count - 1));
}
}
tick(n);
});
}
9 changes: 0 additions & 9 deletions test-app/.editorconfig

This file was deleted.

4 changes: 0 additions & 4 deletions test-app/.eslintignore

This file was deleted.

7 changes: 0 additions & 7 deletions test-app/.eslintrc.js

This file was deleted.

5 changes: 0 additions & 5 deletions test-app/.gitignore

This file was deleted.

6 changes: 0 additions & 6 deletions test-app/.prettierignore

This file was deleted.

4 changes: 0 additions & 4 deletions test-app/.prettierrc.yaml

This file was deleted.

3 changes: 0 additions & 3 deletions test-app/.vscode/extensions.json

This file was deleted.

39 changes: 0 additions & 39 deletions test-app/.vscode/launch.json

This file was deleted.

11 changes: 0 additions & 11 deletions test-app/.vscode/settings.json

This file was deleted.

34 changes: 0 additions & 34 deletions test-app/README.md

This file was deleted.

12 changes: 0 additions & 12 deletions test-app/build/entitlements.mac.plist

This file was deleted.

Binary file removed test-app/build/icon.icns
Binary file not shown.
Binary file removed test-app/build/icon.ico
Binary file not shown.
Binary file removed test-app/build/icon.png
Binary file not shown.
43 changes: 0 additions & 43 deletions test-app/electron-builder.yml

This file was deleted.

11 changes: 0 additions & 11 deletions test-app/electron.vite.config.ts

This file was deleted.

Loading

0 comments on commit 55358ba

Please sign in to comment.