Comparing version 3.0.12 to 3.1.0
#!/usr/bin/env node | ||
import path from "path"; | ||
import { Command } from "commander"; | ||
import { Command, Option } from "commander"; | ||
import { packageJson } from "../const.js"; | ||
@@ -14,4 +14,5 @@ import { downloadFile, downloadSequence } from "../download/node-download.js"; | ||
.option("-c --connections [number]", "Number of parallel connections", "4") | ||
.addOption(new Option("-p --program [type]", "The download strategy").choices(["stream", "chunks"])) | ||
.option("-t --truncate-name", "Truncate file names in the CLI status to make them appear shorter") | ||
.action(async (files = [], { save: saveLocation, truncateName, number }) => { | ||
.action(async (files = [], { save: saveLocation, truncateName, number, program }) => { | ||
if (files.length === 0) { | ||
@@ -30,3 +31,4 @@ pullCommand.outputHelp(); | ||
truncateName, | ||
parallelStreams: Number(number) || 4 | ||
parallelStreams: Number(number) || 4, | ||
programType: program | ||
}); | ||
@@ -33,0 +35,0 @@ })); |
@@ -53,2 +53,3 @@ import ProgressStatusFile, { DownloadStatus, ProgressStatus } from "./progress-status-file.js"; | ||
private get _downloadedPartsSize(); | ||
private get _activeDownloadedChunkSize(); | ||
get transferredBytes(): number; | ||
@@ -59,3 +60,3 @@ protected _emptyChunksForPart(part: number): any[]; | ||
protected _downloadSlice(startChunk: number, endChunk: number): Promise<void>; | ||
protected _saveProgress(): Promise<void>; | ||
protected _saveProgress(): Promise<void> | undefined; | ||
protected _sendProgressDownloadPart(): void; | ||
@@ -62,0 +63,0 @@ pause(): void; |
@@ -28,3 +28,3 @@ import ProgressStatusFile, { DownloadStatus } from "./progress-status-file.js"; | ||
this.file = file; | ||
this._progressStatus = new ProgressStatusFile(file.totalSize, file.parts.length, file.localFileName, options.comment, options.fetchStream.transferAction); | ||
this._progressStatus = new ProgressStatusFile(file.parts.length, file.localFileName, options.comment, options.fetchStream.transferAction); | ||
this.options = { ...DEFAULT_OPTIONS, ...options }; | ||
@@ -34,3 +34,3 @@ this._initProgress(); | ||
get downloadSize() { | ||
return this.file.totalSize; | ||
return this.file.parts.reduce((acc, part) => acc + part.size, 0); | ||
} | ||
@@ -41,3 +41,3 @@ get fileName() { | ||
get status() { | ||
return this._progressStatus.createStatus(this._progress.part + 1, this.transferredBytes, this._downloadStatus); | ||
return this._progressStatus.createStatus(this._progress.part + 1, this.transferredBytes, this.downloadSize, this._downloadStatus); | ||
} | ||
@@ -51,12 +51,21 @@ get _activePart() { | ||
} | ||
get _activeDownloadedChunkSize() { | ||
return this._progress.chunks.filter(c => c === ChunkStatus.COMPLETE).length * this._progress.chunkSize; | ||
} | ||
get transferredBytes() { | ||
const activeDownloadBytes = this._progress.chunks.filter(c => c === ChunkStatus.COMPLETE).length * this._progress.chunkSize; | ||
const streamingBytes = Object.values(this._activeStreamBytes) | ||
.reduce((acc, bytes) => acc + bytes, 0); | ||
const partNotFinishedYet = this._downloadStatus !== DownloadStatus.Finished; | ||
const chunksBytes = (partNotFinishedYet ? activeDownloadBytes : 0) + this._downloadedPartsSize; | ||
return Math.min(chunksBytes + streamingBytes, this.downloadSize); | ||
const chunksBytes = (partNotFinishedYet ? this._activeDownloadedChunkSize : 0) + this._downloadedPartsSize; | ||
const allBytes = chunksBytes + streamingBytes; | ||
if (this._activePart.size === 0) { | ||
return allBytes; | ||
} | ||
return Math.min(allBytes, this.downloadSize); | ||
} | ||
_emptyChunksForPart(part) { | ||
const partInfo = this.file.parts[part]; | ||
if (!partInfo.acceptRange) { | ||
return [ChunkStatus.NOT_STARTED]; | ||
} | ||
const chunksCount = Math.ceil(partInfo.size / this.options.chunkSize); | ||
@@ -91,6 +100,2 @@ return new Array(chunksCount).fill(ChunkStatus.NOT_STARTED); | ||
} | ||
// If the part does not support range, we can only download it with a single stream | ||
if (!this._activePart.acceptRange) { | ||
this._progress.parallelStreams = 1; | ||
} | ||
// Reset in progress chunks | ||
@@ -100,4 +105,9 @@ this._progress.chunks = this._progress.chunks.map(chunk => (chunk === ChunkStatus.COMPLETE ? ChunkStatus.COMPLETE : ChunkStatus.NOT_STARTED)); | ||
this._activeStreamBytes = {}; | ||
this._activeProgram = switchProgram(this._progress, this._downloadSlice.bind(this), this.options.fetchStream.programType || this.options.programType); | ||
await this._activeProgram.download(); | ||
if (this._activePart.acceptRange) { | ||
this._activeProgram = switchProgram(this._progress, this._downloadSlice.bind(this), this.options.fetchStream.programType || this.options.programType); | ||
await this._activeProgram.download(); | ||
} | ||
else { | ||
await this._downloadSlice(0, Infinity); | ||
} | ||
} | ||
@@ -145,2 +155,6 @@ // All parts are downloaded, we can clear the progress | ||
} | ||
// if content length is 0, we do not know how many chunks we should have | ||
if (this._activePart.size === 0) { | ||
this._progress.chunks.push(ChunkStatus.NOT_STARTED); | ||
} | ||
this._progress.chunks[index] = ChunkStatus.COMPLETE; | ||
@@ -158,2 +172,6 @@ delete this._activeStreamBytes[startChunk]; | ||
}); | ||
if (this._activePart.size === 0) { | ||
this._activePart.size = this._activeDownloadedChunkSize; | ||
this._progress.chunks = this._progress.chunks.filter(c => c === ChunkStatus.COMPLETE); | ||
} | ||
delete this._activeStreamBytes[startChunk]; | ||
@@ -166,2 +184,4 @@ await Promise.all(allWrites); | ||
this._sendProgressDownloadPart(); | ||
if (!this._activePart.acceptRange) | ||
return; | ||
this.emit("save", this._progress); | ||
@@ -168,0 +188,0 @@ return withLock(this, "_saveLock", async () => { |
@@ -21,3 +21,2 @@ export type ProgressStatus = { | ||
export default class ProgressStatusFile { | ||
readonly totalBytes: number; | ||
readonly totalDownloadParts: number; | ||
@@ -30,8 +29,9 @@ readonly fileName: string; | ||
readonly downloadStatus: DownloadStatus; | ||
totalBytes: number; | ||
startTime: number; | ||
endTime: number; | ||
constructor(totalBytes: number, totalDownloadParts: number, fileName: string, comment?: string, transferAction?: string, downloadPart?: number, transferredBytes?: number, downloadStatus?: DownloadStatus); | ||
constructor(totalDownloadParts: number, fileName: string, comment?: string, transferAction?: string, downloadPart?: number, transferredBytes?: number, downloadStatus?: DownloadStatus); | ||
started(): void; | ||
finished(): void; | ||
createStatus(downloadPart: number, transferredBytes: number, downloadStatus?: DownloadStatus): ProgressStatusFile; | ||
createStatus(downloadPart: number, transferredBytes: number, totalBytes?: number, downloadStatus?: DownloadStatus): ProgressStatusFile; | ||
} |
@@ -10,3 +10,2 @@ export var DownloadStatus; | ||
export default class ProgressStatusFile { | ||
totalBytes; | ||
totalDownloadParts; | ||
@@ -19,5 +18,6 @@ fileName; | ||
downloadStatus = DownloadStatus.Active; | ||
totalBytes = 0; | ||
startTime = 0; | ||
endTime = 0; | ||
constructor(totalBytes, totalDownloadParts, fileName, comment, transferAction = "Transferring", downloadPart = 0, transferredBytes = 0, downloadStatus = DownloadStatus.Active) { | ||
constructor(totalDownloadParts, fileName, comment, transferAction = "Transferring", downloadPart = 0, transferredBytes = 0, downloadStatus = DownloadStatus.Active) { | ||
this.transferAction = transferAction; | ||
@@ -29,3 +29,2 @@ this.transferredBytes = transferredBytes; | ||
this.totalDownloadParts = totalDownloadParts; | ||
this.totalBytes = totalBytes; | ||
this.downloadStatus = downloadStatus; | ||
@@ -39,4 +38,5 @@ } | ||
} | ||
createStatus(downloadPart, transferredBytes, downloadStatus = DownloadStatus.Active) { | ||
const newStatus = new ProgressStatusFile(this.totalBytes, this.totalDownloadParts, this.fileName, this.comment, this.transferAction, downloadPart, transferredBytes, downloadStatus); | ||
createStatus(downloadPart, transferredBytes, totalBytes = this.totalBytes, downloadStatus = DownloadStatus.Active) { | ||
const newStatus = new ProgressStatusFile(this.totalDownloadParts, this.fileName, this.comment, this.transferAction, downloadPart, transferredBytes, downloadStatus); | ||
newStatus.totalBytes = totalBytes; | ||
newStatus.startTime = this.startTime; | ||
@@ -43,0 +43,0 @@ newStatus.endTime = this.endTime; |
@@ -7,2 +7,4 @@ import UrlInputError from "./error/url-input-error.js"; | ||
import InvalidContentLengthError from "./error/invalid-content-length-error.js"; | ||
import StatusCodeError from "../streams/download-engine-fetch-stream/errors/status-code-error.js"; | ||
const IGNORE_HEAD_STATUS_CODES = [405, 501, 404]; | ||
export default class BaseDownloadEngine extends EventEmitter { | ||
@@ -25,3 +27,3 @@ options; | ||
get downloadSize() { | ||
return this.file.totalSize; | ||
return this._engine.downloadSize; | ||
} | ||
@@ -98,15 +100,29 @@ get fileName() { | ||
for (const part of parts) { | ||
const { length, acceptRange, newURL, fileName } = await fetchStream.fetchDownloadInfo(part); | ||
const downloadURL = newURL ?? part; | ||
if (isNaN(length)) { | ||
throw new InvalidContentLengthError(downloadURL); | ||
try { | ||
const { length, acceptRange, newURL, fileName } = await fetchStream.fetchDownloadInfo(part); | ||
const downloadURL = newURL ?? part; | ||
if (acceptRange && isNaN(length)) { | ||
throw new InvalidContentLengthError(downloadURL); | ||
} | ||
downloadFile.totalSize += length || 0; | ||
downloadFile.parts.push({ | ||
downloadURL, | ||
size: length || 0, | ||
acceptRange | ||
}); | ||
if (counter++ === 0 && fileName) { | ||
downloadFile.localFileName = fileName; | ||
} | ||
} | ||
downloadFile.totalSize += length; | ||
downloadFile.parts.push({ | ||
downloadURL, | ||
size: length, | ||
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; | ||
} | ||
@@ -113,0 +129,0 @@ } |
@@ -26,3 +26,3 @@ import path from "path"; | ||
this._engine.options.onFinishAsync = async () => { | ||
await this.options.writeStream.ftruncate(); | ||
await this.options.writeStream.ftruncate(this.downloadSize); | ||
}; | ||
@@ -29,0 +29,0 @@ this._engine.options.onCloseAsync = async () => { |
@@ -15,5 +15,7 @@ import BaseDownloadEngineFetchStream from "./base-download-engine-fetch-stream.js"; | ||
accept: "*/*", | ||
...this.options.headers, | ||
range: `bytes=${this._startSize}-${this._endSize - 1}` | ||
...this.options.headers | ||
}; | ||
if (this.state.rangeSupport) { | ||
headers.range = `bytes=${this._startSize}-${this._endSize - 1}`; | ||
} | ||
const controller = new AbortController(); | ||
@@ -29,3 +31,3 @@ const response = await fetch(this.appendToURL(this.state.url), { | ||
const expectedContentLength = this._endSize - this._startSize; | ||
if (contentLength !== expectedContentLength) { | ||
if (this.state.rangeSupport && contentLength !== expectedContentLength) { | ||
throw new InvalidContentLengthError(expectedContentLength, contentLength); | ||
@@ -32,0 +34,0 @@ } |
@@ -24,5 +24,7 @@ import BaseDownloadEngineFetchStream from "./base-download-engine-fetch-stream.js"; | ||
accept: "*/*", | ||
...this.options.headers, | ||
range: `bytes=${start}-${end - 1}` // get the range up to end-1. Length 2: 0-1 | ||
...this.options.headers | ||
}; | ||
if (this.state.rangeSupport) { | ||
headers.range = `bytes=${start}-${end - 1}`; | ||
} | ||
const xhr = new XMLHttpRequest(); | ||
@@ -34,5 +36,5 @@ xhr.responseType = "arraybuffer"; | ||
} | ||
xhr.onload = function () { | ||
xhr.onload = () => { | ||
const contentLength = parseInt(xhr.getResponseHeader("content-length")); | ||
if (contentLength !== end - start) { | ||
if (this.state.rangeSupport && contentLength !== end - start) { | ||
throw new InvalidContentLengthError(end - start, contentLength); | ||
@@ -53,6 +55,6 @@ } | ||
}; | ||
xhr.onerror = function () { | ||
xhr.onerror = () => { | ||
reject(new XhrError(`Failed to fetch ${url}`)); | ||
}; | ||
xhr.onprogress = function (event) { | ||
xhr.onprogress = (event) => { | ||
if (event.lengthComputable) { | ||
@@ -59,0 +61,0 @@ onProgress?.(event.loaded); |
@@ -14,5 +14,5 @@ import retry from "async-retry"; | ||
protected _bytesWritten: number; | ||
get writerClosed(): boolean; | ||
get writerClosed(): boolean | 0 | undefined; | ||
constructor(_writer?: DownloadEngineWriteStreamBrowserWriter, options?: DownloadEngineWriteStreamOptionsBrowser); | ||
protected _ensureBuffer(): Uint8Array; | ||
protected _ensureBuffer(length: number): Uint8Array; | ||
write(cursor: number, buffer: Uint8Array): void | Promise<void>; | ||
@@ -19,0 +19,0 @@ get result(): Uint8Array; |
@@ -10,3 +10,3 @@ import BaseDownloadEngineWriteStream from "./base-download-engine-write-stream.js"; | ||
get writerClosed() { | ||
return this._bytesWritten === this.options.file?.totalSize; | ||
return this.options.file?.totalSize && this._bytesWritten === this.options.file.totalSize; | ||
} | ||
@@ -18,4 +18,4 @@ constructor(_writer, options = {}) { | ||
} | ||
_ensureBuffer() { | ||
if (this._memory.length > 0) { | ||
_ensureBuffer(length) { | ||
if (this._memory.length >= length) { | ||
return this._memory; | ||
@@ -26,3 +26,6 @@ } | ||
} | ||
return this._memory = new Uint8Array(this.options.file.totalSize); | ||
const newSize = Math.max(length, this.options.file.totalSize); | ||
const newMemory = new Uint8Array(newSize); | ||
newMemory.set(this._memory); | ||
return this._memory = newMemory; | ||
} | ||
@@ -34,3 +37,3 @@ write(cursor, buffer) { | ||
if (!this._writer) { | ||
this._ensureBuffer() | ||
this._ensureBuffer(cursor + buffer.byteLength) | ||
.set(buffer, cursor); | ||
@@ -37,0 +40,0 @@ this._bytesWritten += buffer.byteLength; |
@@ -35,5 +35,5 @@ import prettyBytes from "pretty-bytes"; | ||
const formatTransferred = prettyBytes(fullStatus.transferredBytes, PRETTY_BYTES_OPTIONS); | ||
const formatTotal = prettyBytes(fullStatus.totalBytes, PRETTY_BYTES_OPTIONS); | ||
const formatTotal = fullStatus.totalBytes === 0 ? "???" : prettyBytes(fullStatus.totalBytes, PRETTY_BYTES_OPTIONS); | ||
const formatTransferredOfTotal = `${formatTransferred}/${formatTotal}`; | ||
const formatTimeLeft = prettyMilliseconds(fullStatus.timeLeft, PRETTY_MS_OPTIONS); | ||
const formatTimeLeft = fullStatus.totalBytes === 0 ? "unknown time" : prettyMilliseconds(fullStatus.timeLeft, PRETTY_MS_OPTIONS); | ||
const formattedPercentage = fullStatus.percentage.toLocaleString(DEFAULT_LOCALIZATION, { | ||
@@ -40,0 +40,0 @@ minimumIntegerDigits: 1, |
@@ -38,3 +38,3 @@ import { clamp } from "./utils/numbers.js"; | ||
updateProgress(transferred, total) { | ||
const speed = clamp(this._calculateSpeed(transferred), 0); | ||
const speed = clamp(this._calculateSpeed(transferred)); | ||
const timeLeft = (total - transferred) / speed; | ||
@@ -41,0 +41,0 @@ const timeLeftFinalNumber = clamp((timeLeft || 0) * 1000, 0, MAX_TIME_LEFT); |
@@ -1,4 +0,4 @@ | ||
export function clamp(value, min = Number.MIN_VALUE, max = Number.MAX_VALUE) { | ||
export function clamp(value, min = 0, max = Number.MAX_VALUE) { | ||
return Math.min(Math.max(value, min), max); | ||
} | ||
//# sourceMappingURL=numbers.js.map |
{ | ||
"name": "ipull", | ||
"version": "3.0.12", | ||
"version": "3.1.0", | ||
"description": "The only file downloader you'll ever need. For node.js and the browser, CLI and library for fast and reliable file downloads.", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -96,5 +96,7 @@ <div align="center"> | ||
Options: | ||
-s --save [path] Save location (directory/file) | ||
-c --connections [number] Number of parallel connections (default: "4") | ||
-p --program [type] The download strategy (choices: "stream", "chunks") | ||
-t --truncate-name Truncate file names in the CLI status to make them appear shorter | ||
-V, --version output the version number | ||
-s --save [path] Save location (directory/file) | ||
-f --full-name Show full name of the file while downloading, even if it long | ||
-h, --help display help for command | ||
@@ -104,2 +106,3 @@ | ||
set [options] [path] <value> Set download locations | ||
``` | ||
@@ -106,0 +109,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
244928
3266
309