First upload version 0.0.1

This commit is contained in:
Neyra
2026-02-05 15:27:49 +08:00
commit 8e9b7201ed
4182 changed files with 593136 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
import { DownloadFile, SaveProgressInfo } from "../types.js";
import DownloadEngineFile, { DownloadEngineFileOptions } from "../download-file/download-engine-file.js";
import BaseDownloadEngineFetchStream, { BaseDownloadEngineFetchStreamOptions } from "../streams/download-engine-fetch-stream/base-download-engine-fetch-stream.js";
import { EventEmitter } from "eventemitter3";
import ProgressStatisticsBuilder, { ProgressStatusWithIndex } from "../../transfer-visualize/progress-statistics-builder.js";
import retry from "async-retry";
import { AvailablePrograms } from "../download-file/download-programs/switch-program.js";
export type InputURLOptions = {
partURLs: string[];
} | {
url: string;
};
export type BaseDownloadEngineOptions = InputURLOptions & BaseDownloadEngineFetchStreamOptions & {
chunkSize?: number;
parallelStreams?: number;
retry?: retry.Options;
comment?: string;
programType?: AvailablePrograms;
};
export type BaseDownloadEngineEvents = {
start: () => void;
paused: () => void;
resumed: () => void;
progress: (progress: ProgressStatusWithIndex) => void;
save: (progress: SaveProgressInfo) => void;
finished: () => void;
closed: () => void;
[key: string]: any;
};
export default class BaseDownloadEngine extends EventEmitter<BaseDownloadEngineEvents> {
readonly options: DownloadEngineFileOptions;
protected readonly _engine: DownloadEngineFile;
protected _progressStatisticsBuilder: ProgressStatisticsBuilder;
protected _downloadStarted: boolean;
protected _latestStatus?: ProgressStatusWithIndex;
protected constructor(engine: DownloadEngineFile, options: DownloadEngineFileOptions);
get file(): DownloadFile;
get downloadSize(): number;
get fileName(): string;
get status(): import("../../transfer-visualize/format-transfer-status.js").FormattedStatus;
get downloadStatues(): import("../../transfer-visualize/format-transfer-status.js").FormattedStatus[];
protected _initEvents(): void;
download(): Promise<void>;
pause(): void;
resume(): void;
close(): Promise<void>;
protected static _createDownloadFile(parts: string[], fetchStream: BaseDownloadEngineFetchStream): Promise<DownloadFile>;
protected static _validateURL(options: InputURLOptions): void;
protected static _validateOptions(options: BaseDownloadEngineOptions): void;
}

View File

@@ -0,0 +1,140 @@
import UrlInputError from "./error/url-input-error.js";
import { EventEmitter } from "eventemitter3";
import ProgressStatisticsBuilder from "../../transfer-visualize/progress-statistics-builder.js";
import DownloadAlreadyStartedError from "./error/download-already-started-error.js";
import StatusCodeError from "../streams/download-engine-fetch-stream/errors/status-code-error.js";
import { InvalidOptionError } from "./error/InvalidOptionError.js";
const IGNORE_HEAD_STATUS_CODES = [405, 501, 404];
export default class BaseDownloadEngine extends EventEmitter {
options;
_engine;
_progressStatisticsBuilder = new ProgressStatisticsBuilder();
_downloadStarted = false;
_latestStatus;
constructor(engine, options) {
super();
this.options = options;
this._engine = engine;
this._progressStatisticsBuilder.add(engine);
this._initEvents();
}
get file() {
return this._engine.file;
}
get downloadSize() {
return this._engine.downloadSize;
}
get fileName() {
return this.file.localFileName;
}
get status() {
return this._latestStatus ?? ProgressStatisticsBuilder.oneStatistics(this._engine);
}
get downloadStatues() {
return [this.status];
}
/**
* @internal
*/
get _fileEngineOptions() {
return this._engine.options;
}
_initEvents() {
this._engine.on("start", () => {
return this.emit("start");
});
this._engine.on("save", (info) => {
return this.emit("save", info);
});
this._engine.on("finished", () => {
return this.emit("finished");
});
this._engine.on("closed", () => {
return this.emit("closed");
});
this._engine.on("paused", () => {
return this.emit("paused");
});
this._engine.on("resumed", () => {
return this.emit("resumed");
});
this._progressStatisticsBuilder.on("progress", (status) => {
this._latestStatus = status;
return this.emit("progress", status);
});
}
async download() {
if (this._downloadStarted) {
throw new DownloadAlreadyStartedError();
}
try {
this._downloadStarted = true;
await this._engine.download();
}
finally {
await this.close();
}
}
pause() {
return this._engine.pause();
}
resume() {
return this._engine.resume();
}
close() {
return this._engine.close();
}
static async _createDownloadFile(parts, fetchStream) {
const localFileName = decodeURIComponent(new URL(parts[0], "https://example").pathname.split("/")
.pop() || "");
const downloadFile = {
totalSize: 0,
parts: [],
localFileName
};
let counter = 0;
for (const part of parts) {
try {
const { length, acceptRange, newURL, fileName } = await fetchStream.fetchDownloadInfo(part);
const downloadURL = newURL ?? part;
const size = length || 0;
downloadFile.totalSize += size;
downloadFile.parts.push({
downloadURL,
size,
acceptRange: size > 0 && acceptRange
});
if (counter++ === 0 && fileName) {
downloadFile.localFileName = fileName;
}
}
catch (error) {
if (error instanceof StatusCodeError && IGNORE_HEAD_STATUS_CODES.includes(error.statusCode)) {
// if the server does not support HEAD request, we will skip that step
downloadFile.parts.push({
downloadURL: part,
size: 0,
acceptRange: false
});
continue;
}
throw error;
}
}
return downloadFile;
}
static _validateURL(options) {
if ("partURLs" in options && "url" in options) {
throw new UrlInputError("Either `partURLs` or `url` should be provided, not both");
}
if (!("partURLs" in options) && !("url" in options)) {
throw new UrlInputError("Either `partURLs` or `url` should be provided");
}
}
static _validateOptions(options) {
if ("tryHeaders" in options && options.tryHeaders?.length && "defaultFetchDownloadInfo" in options) {
throw new InvalidOptionError("Cannot use `tryHeaders` with `defaultFetchDownloadInfo`");
}
}
}
//# sourceMappingURL=base-download-engine.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"base-download-engine.js","sourceRoot":"","sources":["../../../../src/download/download-engine/engine/base-download-engine.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,32 @@
import { SaveProgressInfo } from "../types.js";
import DownloadEngineFile from "../download-file/download-engine-file.js";
import DownloadEngineWriteStreamBrowser, { DownloadEngineWriteStreamBrowserWriter } from "../streams/download-engine-write-stream/download-engine-write-stream-browser.js";
import BaseDownloadEngine, { BaseDownloadEngineOptions } from "./base-download-engine.js";
import BaseDownloadEngineWriteStream from "../streams/download-engine-write-stream/base-download-engine-write-stream.js";
import BaseDownloadEngineFetchStream from "../streams/download-engine-fetch-stream/base-download-engine-fetch-stream.js";
export type DownloadEngineOptionsBrowser = BaseDownloadEngineOptions & {
onWrite?: DownloadEngineWriteStreamBrowserWriter;
progress?: SaveProgressInfo;
fetchStrategy?: "xhr" | "fetch";
};
export type DownloadEngineOptionsCustomFetchBrowser = DownloadEngineOptionsBrowser & {
partURLs: string[];
fetchStream: BaseDownloadEngineFetchStream;
};
export type DownloadEngineOptionsBrowserConstructor<WriteStream = DownloadEngineWriteStreamBrowser> = DownloadEngineOptionsCustomFetchBrowser & {
writeStream: WriteStream;
};
/**
* Download engine for browser
*/
export default class DownloadEngineBrowser<WriteStream extends BaseDownloadEngineWriteStream = BaseDownloadEngineWriteStream> extends BaseDownloadEngine {
readonly options: DownloadEngineOptionsBrowserConstructor<WriteStream>;
protected constructor(engine: DownloadEngineFile, _options: DownloadEngineOptionsBrowserConstructor<WriteStream>);
get writeStream(): Omit<WriteStream, "write" | "close">;
/**
* Download file
*/
static createFromOptions(options: DownloadEngineOptionsBrowser): Promise<DownloadEngineBrowser<DownloadEngineWriteStreamBrowser>>;
protected static _createFromOptionsWithCustomFetch(options: DownloadEngineOptionsCustomFetchBrowser): Promise<DownloadEngineBrowser<DownloadEngineWriteStreamBrowser>>;
protected static _validateOptions(options: DownloadEngineOptionsBrowser): void;
}

View File

@@ -0,0 +1,52 @@
import DownloadEngineFile from "../download-file/download-engine-file.js";
import DownloadEngineFetchStreamFetch from "../streams/download-engine-fetch-stream/download-engine-fetch-stream-fetch.js";
import DownloadEngineFetchStreamXhr from "../streams/download-engine-fetch-stream/download-engine-fetch-stream-xhr.js";
import DownloadEngineWriteStreamBrowser from "../streams/download-engine-write-stream/download-engine-write-stream-browser.js";
import BaseDownloadEngine from "./base-download-engine.js";
/**
* Download engine for browser
*/
export default class DownloadEngineBrowser extends BaseDownloadEngine {
options;
constructor(engine, _options) {
super(engine, _options);
this.options = _options;
}
get writeStream() {
return this.options.writeStream;
}
/**
* Download file
*/
static async createFromOptions(options) {
DownloadEngineBrowser._validateOptions(options);
const partURLs = "partURLs" in options ? options.partURLs : [options.url];
const fetchStream = options.fetchStrategy === "xhr" ?
new DownloadEngineFetchStreamXhr(options) : new DownloadEngineFetchStreamFetch(options);
return DownloadEngineBrowser._createFromOptionsWithCustomFetch({ ...options, partURLs, fetchStream });
}
static async _createFromOptionsWithCustomFetch(options) {
const downloadFile = await DownloadEngineBrowser._createDownloadFile(options.partURLs, options.fetchStream);
downloadFile.downloadProgress = options.progress;
const writeStream = new DownloadEngineWriteStreamBrowser(options.onWrite, {
...options,
file: downloadFile
});
if (options.acceptRangeIsKnown == null) {
const doesNotAcceptRange = downloadFile.parts.find(p => !p.acceptRange);
if (doesNotAcceptRange) {
console.warn(`Server does not accept range requests for "${doesNotAcceptRange.downloadURL}". Meaning fast-downloads/pausing/resuming will not work.
This may be related to cors origin policy (range header is ignored in the browser).
If you know the server accepts range requests, you can set "acceptRangeIsKnown" to true. To dismiss this warning, set "acceptRangeIsKnown" to false.`);
}
}
const allOptions = { ...options, writeStream };
const engine = new DownloadEngineFile(downloadFile, allOptions);
return new DownloadEngineBrowser(engine, allOptions);
}
static _validateOptions(options) {
super._validateOptions(options);
DownloadEngineBrowser._validateURL(options);
}
}
//# sourceMappingURL=download-engine-browser.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"download-engine-browser.js","sourceRoot":"","sources":["../../../../src/download/download-engine/engine/download-engine-browser.ts"],"names":[],"mappings":"AACA,OAAO,kBAAkB,MAAM,0CAA0C,CAAC;AAC1E,OAAO,8BAA8B,MAAM,+EAA+E,CAAC;AAC3H,OAAO,4BAA4B,MAAM,6EAA6E,CAAC;AACvH,OAAO,gCAA0E,MAAM,iFAAiF,CAAC;AACzK,OAAO,kBAA+C,MAAM,2BAA2B,CAAC;AAsBxF;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,qBAAyG,SAAQ,kBAAkB;IAC3H,OAAO,CAAuD;IAEvF,YAAsB,MAA0B,EAAE,QAA8D;QAC5G,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACxB,IAAI,CAAC,OAAO,GAAG,QAAQ,CAAC;IAC5B,CAAC;IAED,IAAW,WAAW;QAClB,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC;IACpC,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,OAAqC;QACvE,qBAAqB,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;QAChD,MAAM,QAAQ,GAAG,UAAU,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAE1E,MAAM,WAAW,GAAG,OAAO,CAAC,aAAa,KAAK,KAAK,CAAC,CAAC;YACjD,IAAI,4BAA4B,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,8BAA8B,CAAC,OAAO,CAAC,CAAC;QAE5F,OAAO,qBAAqB,CAAC,iCAAiC,CAAC,EAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAC,CAAC,CAAC;IACxG,CAAC;IAGS,MAAM,CAAC,KAAK,CAAC,iCAAiC,CAAC,OAAgD;QACrG,MAAM,YAAY,GAAG,MAAM,qBAAqB,CAAC,mBAAmB,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;QAC5G,YAAY,CAAC,gBAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC;QAEjD,MAAM,WAAW,GAAG,IAAI,gCAAgC,CAAC,OAAO,CAAC,OAAO,EAAE;YACtE,GAAG,OAAO;YACV,IAAI,EAAE,YAAY;SACrB,CAAC,CAAC;QAEH,IAAI,OAAO,CAAC,kBAAkB,IAAI,IAAI,EAAE,CAAC;YACrC,MAAM,kBAAkB,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;YACxE,IAAI,kBAAkB,EAAE,CAAC;gBACrB,OAAO,CAAC,IAAI,CAAC,8CAA8C,kBAAkB,CAAC,WAAW;;qJAE4C,CAAC,CAAC;YAC3I,CAAC;QACL,CAAC;QAED,MAAM,UAAU,GAA4C,EAAC,GAAG,OAAO,EAAE,WAAW,EAAC,CAAC;QACtF,MAAM,MAAM,GAAG,IAAI,kBAAkB,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QAChE,OAAO,IAAI,qBAAqB,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACzD,CAAC;IAES,MAAM,CAAU,gBAAgB,CAAC,OAAqC;QAC5E,KAAK,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;QAChC,qBAAqB,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IAChD,CAAC;CACJ"}

View File

@@ -0,0 +1,39 @@
import { EventEmitter } from "eventemitter3";
import { FormattedStatus } from "../../transfer-visualize/format-transfer-status.js";
import ProgressStatisticsBuilder, { ProgressStatusWithIndex } from "../../transfer-visualize/progress-statistics-builder.js";
import BaseDownloadEngine, { BaseDownloadEngineEvents } from "./base-download-engine.js";
type DownloadEngineMultiAllowedEngines = BaseDownloadEngine;
type DownloadEngineMultiDownloadEvents<Engine = DownloadEngineMultiAllowedEngines> = BaseDownloadEngineEvents & {
childDownloadStarted: (engine: Engine) => void;
childDownloadClosed: (engine: Engine) => void;
};
export type DownloadEngineMultiDownloadOptions = {
parallelDownloads?: number;
};
export default class DownloadEngineMultiDownload<Engine extends DownloadEngineMultiAllowedEngines = DownloadEngineMultiAllowedEngines> extends EventEmitter<DownloadEngineMultiDownloadEvents> {
readonly downloads: Engine[];
readonly options: DownloadEngineMultiDownloadOptions;
protected _aborted: boolean;
protected _activeEngines: Set<Engine>;
protected _progressStatisticsBuilder: ProgressStatisticsBuilder;
protected _downloadStatues: (ProgressStatusWithIndex | FormattedStatus)[];
protected _closeFiles: (() => Promise<void>)[];
protected _lastStatus?: ProgressStatusWithIndex;
protected _loadingDownloads: number;
protected constructor(engines: (DownloadEngineMultiAllowedEngines | DownloadEngineMultiDownload)[], options: DownloadEngineMultiDownloadOptions);
get downloadStatues(): (FormattedStatus | ProgressStatusWithIndex)[];
get status(): ProgressStatusWithIndex;
get downloadSize(): number;
protected _init(): void;
private _addEngine;
addDownload(engine: Engine | DownloadEngineMultiDownload<any> | Promise<Engine | DownloadEngineMultiDownload<any>>): Promise<void>;
download(): Promise<void>;
private _changeEngineFinishDownload;
private _finishEnginesDownload;
pause(): void;
resume(): void;
close(): Promise<void>;
protected static _extractEngines<Engine>(engines: Engine[]): any[];
static fromEngines<Engine extends DownloadEngineMultiAllowedEngines>(engines: (Engine | Promise<Engine>)[], options?: DownloadEngineMultiDownloadOptions): Promise<DownloadEngineMultiDownload<BaseDownloadEngine>>;
}
export {};

View File

@@ -0,0 +1,151 @@
import { EventEmitter } from "eventemitter3";
import ProgressStatisticsBuilder from "../../transfer-visualize/progress-statistics-builder.js";
import DownloadAlreadyStartedError from "./error/download-already-started-error.js";
import { concurrency } from "./utils/concurrency.js";
import { DownloadFlags, DownloadStatus } from "../download-file/progress-status-file.js";
import { NoDownloadEngineProvidedError } from "./error/no-download-engine-provided-error.js";
const DEFAULT_PARALLEL_DOWNLOADS = 1;
export default class DownloadEngineMultiDownload extends EventEmitter {
downloads;
options;
_aborted = false;
_activeEngines = new Set();
_progressStatisticsBuilder = new ProgressStatisticsBuilder();
_downloadStatues = [];
_closeFiles = [];
_lastStatus;
_loadingDownloads = 0;
constructor(engines, options) {
super();
this.downloads = DownloadEngineMultiDownload._extractEngines(engines);
this.options = options;
this._init();
}
get downloadStatues() {
return this._downloadStatues;
}
get status() {
if (!this._lastStatus) {
throw new NoDownloadEngineProvidedError();
}
return this._lastStatus;
}
get downloadSize() {
return this.downloads.reduce((acc, engine) => acc + engine.downloadSize, 0);
}
_init() {
this._progressStatisticsBuilder.downloadStatus = DownloadStatus.NotStarted;
this._progressStatisticsBuilder.on("progress", progress => {
progress = {
...progress,
downloadFlags: progress.downloadFlags.concat([DownloadFlags.DownloadSequence])
};
this._lastStatus = progress;
this.emit("progress", progress);
});
let index = 0;
for (const engine of this.downloads) {
this._addEngine(engine, index++);
}
// Prevent multiple progress events on adding engines
this._progressStatisticsBuilder.add(...this.downloads);
}
_addEngine(engine, index) {
this._downloadStatues[index] = engine.status;
engine.on("progress", (progress) => {
this._downloadStatues[index] = progress;
});
this._changeEngineFinishDownload(engine);
}
async addDownload(engine) {
const index = this.downloads.length + this._loadingDownloads;
this._downloadStatues[index] = ProgressStatisticsBuilder.loadingStatusEmptyStatistics();
this._loadingDownloads++;
this._progressStatisticsBuilder._totalDownloadParts++;
const awaitEngine = engine instanceof Promise ? await engine : engine;
this._progressStatisticsBuilder._totalDownloadParts--;
this._loadingDownloads--;
if (awaitEngine instanceof DownloadEngineMultiDownload) {
let countEngines = 0;
for (const subEngine of awaitEngine.downloads) {
this._addEngine(subEngine, index + countEngines++);
this.downloads.push(subEngine);
}
this._progressStatisticsBuilder.add(...awaitEngine.downloads);
}
else {
this._addEngine(awaitEngine, index);
this.downloads.push(awaitEngine);
this._progressStatisticsBuilder.add(awaitEngine);
}
}
async download() {
if (this._activeEngines.size) {
throw new DownloadAlreadyStartedError();
}
this._progressStatisticsBuilder.downloadStatus = DownloadStatus.Active;
this.emit("start");
const concurrencyCount = this.options.parallelDownloads ?? DEFAULT_PARALLEL_DOWNLOADS;
await concurrency(this.downloads, concurrencyCount, async (engine) => {
if (this._aborted)
return;
this._activeEngines.add(engine);
this.emit("childDownloadStarted", engine);
await engine.download();
this.emit("childDownloadClosed", engine);
this._activeEngines.delete(engine);
});
this._progressStatisticsBuilder.downloadStatus = DownloadStatus.Finished;
this.emit("finished");
await this._finishEnginesDownload();
await this.close();
}
_changeEngineFinishDownload(engine) {
const options = engine._fileEngineOptions;
const onFinishAsync = options.onFinishAsync;
const onCloseAsync = options.onCloseAsync;
options.onFinishAsync = undefined;
options.onCloseAsync = undefined;
this._closeFiles.push(async () => {
await onFinishAsync?.();
await options.writeStream.close();
await onCloseAsync?.();
});
}
async _finishEnginesDownload() {
await Promise.all(this._closeFiles.map(func => func()));
}
pause() {
this._progressStatisticsBuilder.downloadStatus = DownloadStatus.Paused;
this._activeEngines.forEach(engine => engine.pause());
}
resume() {
this._progressStatisticsBuilder.downloadStatus = DownloadStatus.Active;
this._activeEngines.forEach(engine => engine.resume());
}
async close() {
if (this._aborted)
return;
this._aborted = true;
if (this._progressStatisticsBuilder.downloadStatus !== DownloadStatus.Finished) {
this._progressStatisticsBuilder.downloadStatus = DownloadStatus.Cancelled;
}
const closePromises = Array.from(this._activeEngines)
.map(engine => engine.close());
await Promise.all(closePromises);
this.emit("closed");
}
static _extractEngines(engines) {
return engines.map(engine => {
if (engine instanceof DownloadEngineMultiDownload) {
return engine.downloads;
}
return engine;
})
.flat();
}
static async fromEngines(engines, options = {}) {
return new DownloadEngineMultiDownload(await Promise.all(engines), options);
}
}
//# sourceMappingURL=download-engine-multi-download.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,67 @@
import { DownloadFile } from "../types.js";
import DownloadEngineFile from "../download-file/download-engine-file.js";
import DownloadEngineWriteStreamNodejs from "../streams/download-engine-write-stream/download-engine-write-stream-nodejs.js";
import BaseDownloadEngine, { BaseDownloadEngineOptions } from "./base-download-engine.js";
import BaseDownloadEngineFetchStream from "../streams/download-engine-fetch-stream/base-download-engine-fetch-stream.js";
export declare const PROGRESS_FILE_EXTENSION = ".ipull";
type PathOptions = {
directory: string;
} | {
savePath: string;
};
export type DownloadEngineOptionsNodejs = PathOptions & BaseDownloadEngineOptions & {
fileName?: string;
fetchStrategy?: "localFile" | "fetch";
skipExisting?: boolean;
};
export type DownloadEngineOptionsNodejsCustomFetch = DownloadEngineOptionsNodejs & {
partURLs: string[];
fetchStream: BaseDownloadEngineFetchStream;
};
export type DownloadEngineOptionsNodejsConstructor<WriteStream = DownloadEngineWriteStreamNodejs> = DownloadEngineOptionsNodejsCustomFetch & {
writeStream: WriteStream;
};
/**
* Download engine for Node.js
*/
export default class DownloadEngineNodejs<T extends DownloadEngineWriteStreamNodejs = DownloadEngineWriteStreamNodejs> extends BaseDownloadEngine {
readonly options: DownloadEngineOptionsNodejsConstructor<T>;
protected constructor(engine: DownloadEngineFile, _options: DownloadEngineOptionsNodejsConstructor<T>);
protected _initEvents(): void;
/**
* The file path with the progress extension or the final file path if the download is finished
*/
get fileAbsolutePath(): string;
/**
* The final file path (without the progress extension)
*/
get finalFileAbsolutePath(): string;
/**
* Abort the download & delete the file (**even if** the download is finished)
* @deprecated use `close` with flag `deleteFile` instead
*
* TODO: remove in the next major version
*/
closeAndDeleteFile(): Promise<void>;
/**
* Close the download engine
* @param deleteTempFile {boolean} - delete the temp file (when the download is **not finished**).
* @param deleteFile {boolean} - delete the **temp** or **final file** (clean everything up).
*/
close({ deleteTempFile, deleteFile }?: {
deleteTempFile?: boolean;
deleteFile?: boolean;
}): Promise<void>;
/**
* Download/copy a file
*
* if `fetchStrategy` is defined as "localFile" it will copy the file, otherwise it will download it
* By default, it will guess the strategy based on the URL
*/
static createFromOptions(options: DownloadEngineOptionsNodejs): Promise<DownloadEngineNodejs<DownloadEngineWriteStreamNodejs>>;
protected static _createFromOptionsWithCustomFetch(options: DownloadEngineOptionsNodejsCustomFetch): Promise<DownloadEngineNodejs<DownloadEngineWriteStreamNodejs>>;
protected static _createDownloadLocation(download: DownloadFile, options: DownloadEngineOptionsNodejs): string;
protected static _validateOptions(options: DownloadEngineOptionsNodejs): void;
protected static _guessFetchStrategy(url: string): "localFile" | "fetch";
}
export {};

View File

@@ -0,0 +1,154 @@
import path from "path";
import DownloadEngineFile from "../download-file/download-engine-file.js";
import DownloadEngineFetchStreamFetch from "../streams/download-engine-fetch-stream/download-engine-fetch-stream-fetch.js";
import DownloadEngineWriteStreamNodejs from "../streams/download-engine-write-stream/download-engine-write-stream-nodejs.js";
import DownloadEngineFetchStreamLocalFile from "../streams/download-engine-fetch-stream/download-engine-fetch-stream-local-file.js";
import BaseDownloadEngine from "./base-download-engine.js";
import SavePathError from "./error/save-path-error.js";
import fs from "fs-extra";
import filenamify from "filenamify";
import { DownloadStatus } from "../download-file/progress-status-file.js";
export const PROGRESS_FILE_EXTENSION = ".ipull";
/**
* Download engine for Node.js
*/
export default class DownloadEngineNodejs extends BaseDownloadEngine {
options;
constructor(engine, _options) {
super(engine, _options);
this.options = _options;
}
_initEvents() {
super._initEvents();
this._engine.options.onSaveProgressAsync = async (progress) => {
if (this.options.skipExisting)
return;
await this.options.writeStream.saveMedataAfterFile(progress);
};
// Try to clone the file if it's a single part download
this._engine.options.onStartedAsync = async () => {
if (this.options.skipExisting || this.options.fetchStrategy !== "localFile" || this.options.partURLs.length !== 1)
return;
try {
const { reflinkFile } = await import("@reflink/reflink");
await fs.remove(this.options.writeStream.path);
await reflinkFile(this.options.partURLs[0], this.options.writeStream.path);
this._engine.finished("cloned");
}
catch { }
};
this._engine.options.onFinishAsync = async () => {
if (this.options.skipExisting)
return;
await this.options.writeStream.ftruncate(this.downloadSize);
};
this._engine.options.onCloseAsync = async () => {
if (this.status.ended && this.options.writeStream.path != this.options.writeStream.finalPath) {
await fs.rename(this.options.writeStream.path, this.options.writeStream.finalPath);
this.options.writeStream.path = this.options.writeStream.finalPath;
}
};
if (this.options.skipExisting) {
this.options.writeStream.path = this.options.writeStream.finalPath;
}
}
/**
* The file path with the progress extension or the final file path if the download is finished
*/
get fileAbsolutePath() {
return path.resolve(this.options.writeStream.path);
}
/**
* The final file path (without the progress extension)
*/
get finalFileAbsolutePath() {
return path.resolve(this.options.writeStream.finalPath);
}
/**
* Abort the download & delete the file (**even if** the download is finished)
* @deprecated use `close` with flag `deleteFile` instead
*
* TODO: remove in the next major version
*/
async closeAndDeleteFile() {
await this.close({ deleteFile: true });
}
/**
* Close the download engine
* @param deleteTempFile {boolean} - delete the temp file (when the download is **not finished**).
* @param deleteFile {boolean} - delete the **temp** or **final file** (clean everything up).
*/
async close({ deleteTempFile, deleteFile } = {}) {
await super.close();
if (deleteFile || deleteTempFile && this.status.downloadStatus != DownloadStatus.Finished) {
try {
await fs.unlink(this.fileAbsolutePath);
}
catch { }
}
}
/**
* Download/copy a file
*
* if `fetchStrategy` is defined as "localFile" it will copy the file, otherwise it will download it
* By default, it will guess the strategy based on the URL
*/
static async createFromOptions(options) {
DownloadEngineNodejs._validateOptions(options);
const partURLs = "partURLs" in options ? options.partURLs : [options.url];
options.fetchStrategy ??= DownloadEngineNodejs._guessFetchStrategy(partURLs[0]);
const fetchStream = options.fetchStrategy === "localFile" ?
new DownloadEngineFetchStreamLocalFile(options) :
new DownloadEngineFetchStreamFetch(options);
return DownloadEngineNodejs._createFromOptionsWithCustomFetch({ ...options, partURLs, fetchStream });
}
static async _createFromOptionsWithCustomFetch(options) {
const downloadFile = await DownloadEngineNodejs._createDownloadFile(options.partURLs, options.fetchStream);
const downloadLocation = DownloadEngineNodejs._createDownloadLocation(downloadFile, options);
downloadFile.localFileName = path.basename(downloadLocation);
const writeStream = new DownloadEngineWriteStreamNodejs(downloadLocation + PROGRESS_FILE_EXTENSION, downloadLocation, options);
writeStream.fileSize = downloadFile.totalSize;
downloadFile.downloadProgress = await writeStream.loadMetadataAfterFileWithoutRetry();
if (options.skipExisting) {
options.skipExisting = false;
if (downloadFile.totalSize > 0 && !downloadFile.downloadProgress) {
try {
const stat = await fs.stat(downloadLocation);
if (stat.isFile() && stat.size === downloadFile.totalSize) {
options.skipExisting = true;
}
}
catch { }
}
}
const allOptions = { ...options, writeStream };
const engine = new DownloadEngineFile(downloadFile, allOptions);
return new DownloadEngineNodejs(engine, allOptions);
}
static _createDownloadLocation(download, options) {
if ("savePath" in options) {
return options.savePath;
}
const fileName = options.fileName || download.localFileName;
return path.join(options.directory, filenamify(fileName));
}
static _validateOptions(options) {
super._validateOptions(options);
if (!("directory" in options) && !("savePath" in options)) {
throw new SavePathError("Either `directory` or `savePath` must be provided");
}
if ("directory" in options && "savePath" in options) {
throw new SavePathError("Both `directory` and `savePath` cannot be provided");
}
DownloadEngineNodejs._validateURL(options);
}
static _guessFetchStrategy(url) {
try {
new URL(url);
return "fetch";
}
catch { }
return "localFile";
}
}
//# sourceMappingURL=download-engine-nodejs.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,4 @@
import EngineError from "./engine-error.js";
export declare class InvalidOptionError extends EngineError {
constructor(message: string);
}

View File

@@ -0,0 +1,7 @@
import EngineError from "./engine-error.js";
export class InvalidOptionError extends EngineError {
constructor(message) {
super(message);
}
}
//# sourceMappingURL=InvalidOptionError.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"InvalidOptionError.js","sourceRoot":"","sources":["../../../../../src/download/download-engine/engine/error/InvalidOptionError.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,mBAAmB,CAAC;AAE5C,MAAM,OAAO,kBAAmB,SAAQ,WAAW;IAC/C,YAAY,OAAe;QACvB,KAAK,CAAC,OAAO,CAAC,CAAC;IACnB,CAAC;CACJ"}

View File

@@ -0,0 +1,4 @@
import EngineError from "./engine-error.js";
export default class DownloadAlreadyStartedError extends EngineError {
constructor();
}

View File

@@ -0,0 +1,7 @@
import EngineError from "./engine-error.js";
export default class DownloadAlreadyStartedError extends EngineError {
constructor() {
super("Download already started");
}
}
//# sourceMappingURL=download-already-started-error.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"download-already-started-error.js","sourceRoot":"","sources":["../../../../../src/download/download-engine/engine/error/download-already-started-error.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,mBAAmB,CAAC;AAE5C,MAAM,CAAC,OAAO,OAAO,2BAA4B,SAAQ,WAAW;IAChE;QACI,KAAK,CAAC,0BAA0B,CAAC,CAAC;IACtC,CAAC;CACJ"}

View File

@@ -0,0 +1,3 @@
import IpullError from "../../../../errors/ipull-error.js";
export default class EngineError extends IpullError {
}

View File

@@ -0,0 +1,4 @@
import IpullError from "../../../../errors/ipull-error.js";
export default class EngineError extends IpullError {
}
//# sourceMappingURL=engine-error.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"engine-error.js","sourceRoot":"","sources":["../../../../../src/download/download-engine/engine/error/engine-error.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,mCAAmC,CAAC;AAE3D,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,UAAU;CAAG"}

View File

@@ -0,0 +1,4 @@
import EngineError from "./engine-error.js";
export default class InvalidContentLengthError extends EngineError {
constructor(url: string);
}

View File

@@ -0,0 +1,7 @@
import EngineError from "./engine-error.js";
export default class InvalidContentLengthError extends EngineError {
constructor(url) {
super(`Invalid content length, for request URL: ${url}`);
}
}
//# sourceMappingURL=invalid-content-length-error.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"invalid-content-length-error.js","sourceRoot":"","sources":["../../../../../src/download/download-engine/engine/error/invalid-content-length-error.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,mBAAmB,CAAC;AAE5C,MAAM,CAAC,OAAO,OAAO,yBAA0B,SAAQ,WAAW;IAC9D,YAAY,GAAW;QACnB,KAAK,CAAC,4CAA4C,GAAG,EAAE,CAAC,CAAC;IAC7D,CAAC;CACJ"}

View File

@@ -0,0 +1,3 @@
import EngineError from "./engine-error.js";
export default class MissingEventError extends EngineError {
}

View File

@@ -0,0 +1,4 @@
import EngineError from "./engine-error.js";
export default class MissingEventError extends EngineError {
}
//# sourceMappingURL=missing-event-error.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"missing-event-error.js","sourceRoot":"","sources":["../../../../../src/download/download-engine/engine/error/missing-event-error.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,mBAAmB,CAAC;AAE5C,MAAM,CAAC,OAAO,OAAO,iBAAkB,SAAQ,WAAW;CAAG"}

View File

@@ -0,0 +1,4 @@
import EngineError from "./engine-error.js";
export declare class NoDownloadEngineProvidedError extends EngineError {
constructor(error?: string);
}

View File

@@ -0,0 +1,7 @@
import EngineError from "./engine-error.js";
export class NoDownloadEngineProvidedError extends EngineError {
constructor(error = "No download engine provided for download sequence") {
super(error);
}
}
//# sourceMappingURL=no-download-engine-provided-error.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"no-download-engine-provided-error.js","sourceRoot":"","sources":["../../../../../src/download/download-engine/engine/error/no-download-engine-provided-error.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,mBAAmB,CAAC;AAE5C,MAAM,OAAO,6BAA8B,SAAQ,WAAW;IAC1D,YAAY,KAAK,GAAG,mDAAmD;QACnE,KAAK,CAAC,KAAK,CAAC,CAAC;IACjB,CAAC;CACJ"}

View File

@@ -0,0 +1,4 @@
import { InvalidOptionError } from "./InvalidOptionError.js";
export default class SavePathError extends InvalidOptionError {
constructor(message: string);
}

View File

@@ -0,0 +1,7 @@
import { InvalidOptionError } from "./InvalidOptionError.js";
export default class SavePathError extends InvalidOptionError {
constructor(message) {
super(message);
}
}
//# sourceMappingURL=save-path-error.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"save-path-error.js","sourceRoot":"","sources":["../../../../../src/download/download-engine/engine/error/save-path-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,kBAAkB,EAAC,MAAM,yBAAyB,CAAC;AAE3D,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,kBAAkB;IACzD,YAAY,OAAe;QACvB,KAAK,CAAC,OAAO,CAAC,CAAC;IACnB,CAAC;CACJ"}

View File

@@ -0,0 +1,4 @@
import { InvalidOptionError } from "./InvalidOptionError.js";
export default class UrlInputError extends InvalidOptionError {
constructor(message: string);
}

View File

@@ -0,0 +1,7 @@
import { InvalidOptionError } from "./InvalidOptionError.js";
export default class UrlInputError extends InvalidOptionError {
constructor(message) {
super(message);
}
}
//# sourceMappingURL=url-input-error.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"url-input-error.js","sourceRoot":"","sources":["../../../../../src/download/download-engine/engine/error/url-input-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,kBAAkB,EAAC,MAAM,yBAAyB,CAAC;AAE3D,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,kBAAkB;IACzD,YAAY,OAAe;QACvB,KAAK,CAAC,OAAO,CAAC,CAAC;IACnB,CAAC;CACJ"}

View File

@@ -0,0 +1 @@
export declare function concurrency<Value>(array: Value[], concurrencyCount: number, callback: (value: Value) => Promise<void>): Promise<void>;

View File

@@ -0,0 +1,22 @@
export function concurrency(array, concurrencyCount, callback) {
return new Promise((resolve, reject) => {
let index = 0;
let activeCount = 0;
function next() {
if (index === array.length && activeCount === 0) {
resolve();
return;
}
while (activeCount < concurrencyCount && index < array.length) {
activeCount++;
callback(array[index++])
.then(() => {
activeCount--;
next();
}, reject);
}
}
next();
});
}
//# sourceMappingURL=concurrency.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"concurrency.js","sourceRoot":"","sources":["../../../../../src/download/download-engine/engine/utils/concurrency.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,WAAW,CAAQ,KAAc,EAAE,gBAAwB,EAAE,QAAyC;IAClH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACnC,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,WAAW,GAAG,CAAC,CAAC;QAEpB,SAAS,IAAI;YACT,IAAI,KAAK,KAAK,KAAK,CAAC,MAAM,IAAI,WAAW,KAAK,CAAC,EAAE,CAAC;gBAC9C,OAAO,EAAE,CAAC;gBACV,OAAO;YACX,CAAC;YAED,OAAO,WAAW,GAAG,gBAAgB,IAAI,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;gBAC5D,WAAW,EAAE,CAAC;gBACd,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;qBACnB,IAAI,CAAC,GAAG,EAAE;oBACP,WAAW,EAAE,CAAC;oBACd,IAAI,EAAE,CAAC;gBACX,CAAC,EAAE,MAAM,CAAC,CAAC;YACnB,CAAC;QACL,CAAC;QAED,IAAI,EAAE,CAAC;IACX,CAAC,CAAC,CAAC;AACP,CAAC"}