nodejs-file-downloader
Advanced tools
Comparing version 4.5.1 to 4.5.2
263
Download.js
const fs = require('fs'); | ||
// const abort = require('./utils/abort') | ||
const http = require('http')//For jsdoc | ||
const IncomingMessage = http.IncomingMessage | ||
// const ClientRequest = http.ClientRequest | ||
const makeRequest = require('./makeRequest'); | ||
const stream = require('stream'); | ||
@@ -16,3 +14,2 @@ var HttpsProxyAgent = require('https-proxy-agent'); | ||
const { deduceFileName } = require('./utils/fileName'); | ||
const RequestWrapper = require('./RequestWrapper'); | ||
const unlink = util.promisify(fs.unlink) | ||
@@ -64,16 +61,9 @@ const rename = util.promisify(fs.rename) | ||
this.wrapperReject = null;//A reference to the reject function of the wrapping promise; | ||
this.wrapperPromise = null; | ||
this.wrapperPromiseRejected = false; | ||
this.saveStreamPromise = null; | ||
// this.isCancelled = false; | ||
this.isCancelled = false; | ||
this.cancelCb = null;//Function from makeRequest, to cancel the download. | ||
this.percentage = 0; | ||
this.fileSize = null; | ||
this.currentDataSize = 0; | ||
this.originalResponse = null;//The IncomingMessage read stream. | ||
/** | ||
* @property {RequestWrapper} requestWrapper | ||
*/ | ||
this.requestWrapper = null; | ||
@@ -84,4 +74,4 @@ } | ||
/** | ||
* The entire download process. | ||
* @return {Promise<void>} | ||
@@ -91,72 +81,37 @@ */ | ||
const prom = new Promise(async (resolve, reject) => { | ||
// debugger | ||
this.wrapperReject = reject; | ||
await this._verifyDirectoryExists(this.config.directory) | ||
try { | ||
const { dataStream, originalResponse } = await this._request(); | ||
this.originalResponse = originalResponse; | ||
try { | ||
await this._verifyDirectoryExists(this.config.directory) | ||
// debugger | ||
// if (this.config.onBeforeRequest) { | ||
// await this.config.onBeforeRequest(); | ||
// } | ||
this._makeRequest(); | ||
const response = await this._awaitResponse() | ||
if (this.config.onResponse) { | ||
if (this.config.onResponse) { | ||
// debugger | ||
const shouldContinue = await this.config.onResponse(response); | ||
if (shouldContinue === false) { | ||
return resolve(); | ||
} | ||
const shouldContinue = await this.config.onResponse(originalResponse); | ||
if (shouldContinue === false) { | ||
return; | ||
} | ||
// debugger | ||
await this._save(response) | ||
// debugger | ||
} | ||
await this._save({ dataStream, originalResponse }) | ||
} catch (error) { | ||
resolve(); | ||
// debugger | ||
} catch (error) { | ||
// debugger | ||
if (!this.wrapperPromiseRejected) {//If the request was cancelled, ignore any error. | ||
this.reject(error); | ||
} | ||
if (this.isCancelled) { | ||
const customError = new Error('Request cancelled') | ||
customError.code = 'ERR_REQUEST_CANCELLED' | ||
throw customError | ||
} | ||
}) | ||
throw error; | ||
} | ||
// this.wrapperPromise = prom; | ||
return prom; | ||
} | ||
// debugger | ||
/** | ||
* | ||
* @returns {Promise<IncomingMessage>} response | ||
* @param {string} directory | ||
*/ | ||
async _awaitResponse() { | ||
// debugger | ||
const response = await this.requestWrapper.getResponse() | ||
// debugger | ||
const headers = response.headers; | ||
// debugger | ||
const contentLength = headers['content-length'] || headers['Content-Length']; | ||
this.fileSize = parseInt(contentLength); | ||
return response; | ||
async _verifyDirectoryExists(directory) { | ||
await mkdir(directory, { recursive: true }); | ||
} | ||
@@ -166,66 +121,24 @@ | ||
/** | ||
* | ||
* @returns {Promise<void>} | ||
* @return {Promise<{dataStream:stream.Readable,originalResponse:IncomingMessage}} | ||
*/ | ||
_makeRequest() { | ||
const { timeout, headers, proxy, url, httpsAgent } = this.config; | ||
const options = { | ||
timeout, | ||
headers | ||
} | ||
if (httpsAgent) { | ||
options.httpsAgent = httpsAgent; | ||
} | ||
else if (proxy) { | ||
// debugger | ||
options.httpsAgent = new HttpsProxyAgent(proxy) | ||
} | ||
async _request() { | ||
const { dataStream, originalResponse } = await this._makeRequest(); | ||
const headers = originalResponse.headers; | ||
const contentLength = headers['content-length'] || headers['Content-Length']; | ||
this.fileSize = parseInt(contentLength); | ||
return { dataStream, originalResponse } | ||
// debugger | ||
const wrapper = new RequestWrapper(url, options) | ||
this.requestWrapper = wrapper; | ||
wrapper.makeRequest() | ||
// this.responsePromise = wrapper.responsePromise; | ||
// this.requestWrapper = request; | ||
this._setEvents() | ||
} | ||
_setEvents() { | ||
this.requestWrapper.onError((e) => { | ||
// debugger | ||
// console.log(e) | ||
this.reject(e) | ||
}) | ||
if (this.config.timeout) | ||
this.requestWrapper.setTimeout(this.config.timeout, () => { | ||
this.abort() | ||
const error = new Error(`Request timed out`) | ||
error.code = "ERR_REQUEST_TIMEDOUT" | ||
this.reject(new Error(error)) | ||
}) | ||
} | ||
/** | ||
* @param {IncomingMessage} response | ||
* @param {Promise<{dataStream:stream.Readable,originalResponse:IncomingMessage}} | ||
* @return {Promise<void>} | ||
*/ | ||
async _save(response) { | ||
async _save({ dataStream, originalResponse }) { | ||
try { | ||
// debugger | ||
let finalName = await this._getFinalFileName(response.headers); | ||
let finalName = await this._getFinalFileName(originalResponse.headers); | ||
if (this.config.onBeforeSave) { | ||
// debugger | ||
const clientOverideName = await this.config.onBeforeSave(finalName) | ||
@@ -242,13 +155,7 @@ if (clientOverideName && typeof clientOverideName === 'string') { | ||
if (this.config.shouldBufferResponse) { | ||
// debugger | ||
const buffer = await this._createBufferFromResponseStream(response); | ||
// debugger | ||
if (this.wrapperPromiseRejected) { | ||
return await this._removeFailedFile(tempPath) | ||
} | ||
const buffer = await this._createBufferFromResponseStream(dataStream); | ||
await this._saveFromBuffer(buffer, tempPath); | ||
// await this._saveFromBuffer(buffer, finalPath); | ||
} else { | ||
await this._saveFromReadableStream(response, tempPath); | ||
await this._saveFromReadableStream(dataStream, tempPath); | ||
// await this._saveFromReadableStream(response, finalPath); | ||
@@ -261,3 +168,5 @@ } | ||
// debugger | ||
await this._removeFailedFile(tempPath) | ||
if (!this.config.shouldBufferResponse) | ||
await this._removeFailedFile(tempPath) | ||
throw error; | ||
@@ -268,4 +177,35 @@ } | ||
} | ||
async _verifyDirectoryExists(directory) { | ||
await mkdir(directory, { recursive: true }); | ||
/** | ||
* | ||
* @return {Promise<{dataStream:stream.Readable,originalResponse:IncomingMessage}} | ||
*/ | ||
async _makeRequest() { | ||
const { timeout, headers, proxy, url, httpsAgent } = this.config; | ||
const options = { | ||
timeout, | ||
headers | ||
} | ||
if (httpsAgent) { | ||
options.httpsAgent = httpsAgent; | ||
} | ||
else if (proxy) { | ||
// debugger | ||
options.httpsAgent = new HttpsProxyAgent(proxy) | ||
} | ||
// debugger | ||
// const { response, request } = await makeRequest(url, options); | ||
const { makeRequestIter, cancel } = makeRequest(url, options) | ||
// debugger | ||
this.cancelCb = cancel | ||
const { dataStream, originalResponse, } = await makeRequestIter() | ||
// debugger | ||
return { dataStream, originalResponse } | ||
} | ||
@@ -287,15 +227,11 @@ | ||
* | ||
* @param {IncomingMessage} stream | ||
* @param {stream.Readable} stream | ||
* @returns | ||
*/ | ||
async _createBufferFromResponseStream(stream) { | ||
// debugger | ||
const chunks = [] | ||
for await (let chunk of stream) { | ||
// if(this.wrapperPromiseRejected){ | ||
// throw new Error('Stream interrupted') | ||
// } | ||
chunks.push(chunk) | ||
} | ||
// debugger | ||
const buffer = Buffer.concat(chunks) | ||
@@ -324,3 +260,2 @@ return buffer; | ||
if (that.config.onProgress) { | ||
// debugger | ||
that.config.onProgress(that.percentage, chunk, remainingSize); | ||
@@ -345,11 +280,3 @@ } | ||
async _pipeStreams(arrayOfStreams) { | ||
try { | ||
// debugger | ||
await pipelinePromisified(...arrayOfStreams); | ||
// debugger | ||
} catch (error) { | ||
// debugger | ||
throw error; | ||
} | ||
await pipelinePromisified(...arrayOfStreams); | ||
} | ||
@@ -360,3 +287,2 @@ | ||
async _saveFromReadableStream(read, path) { | ||
// debugger; | ||
const streams = [read]; | ||
@@ -370,5 +296,3 @@ const write = this._createWriteStream(path) | ||
streams.push(write) | ||
// debugger | ||
await this._pipeStreams(streams) | ||
// debugger | ||
@@ -381,4 +305,2 @@ | ||
async _saveFromBuffer(buffer, path) { | ||
// debugger; | ||
// const tempPath = this._getTempFilePath(path); | ||
await writeFile(path, buffer) | ||
@@ -428,36 +350,11 @@ | ||
/** | ||
* Reject the wrapping promise of the entire download process | ||
* @param {Error} e | ||
*/ | ||
reject(e) { | ||
// debugger | ||
this.wrapperPromiseRejected = true; | ||
this.wrapperReject(e) | ||
} | ||
async abort() { | ||
this.requestWrapper.abort(); | ||
} | ||
/** | ||
* Cancels the download, and rejects the wrapping promise | ||
*/ | ||
cancel() { | ||
// debugger | ||
if (this.cancelCb) { | ||
this.isCancelled = true; | ||
// this.isCancelled = true; | ||
// const request = this.requestWrapper.getRequest() | ||
// abort(request) | ||
if (this.requestWrapper) | ||
this.abort() | ||
this.cancelCb() | ||
} | ||
const customError = new Error('Request cancelled') | ||
customError.code = 'ERR_REQUEST_CANCELLED' | ||
// debugger | ||
this.reject(customError) | ||
} | ||
@@ -464,0 +361,0 @@ } |
@@ -28,28 +28,3 @@ | ||
mandatory: false | ||
}, | ||
onProgress: { | ||
type: 'function', | ||
mandatory: false | ||
}, | ||
onResponse: { | ||
type: 'function', | ||
mandatory: false | ||
}, | ||
shouldStop: { | ||
type: 'function', | ||
mandatory: false | ||
}, | ||
onBeforeSave: { | ||
type: 'function', | ||
mandatory: false | ||
}, | ||
onError: { | ||
type: 'function', | ||
mandatory: false | ||
}, | ||
maxAttempts: { | ||
type: 'number', | ||
mandatory: false | ||
}, | ||
} | ||
}; | ||
@@ -149,10 +124,7 @@ | ||
const that = this; | ||
const { url, directory, fileName, cloneFiles, timeout, headers, httpsAgent, proxy, onResponse, onBeforeSave, onProgress, shouldBufferResponse, useSynchronousMode } = that.config; | ||
const { url, directory, fileName, cloneFiles, timeout, headers, httpsAgent, proxy, onResponse, onBeforeSave, onProgress, shouldBufferResponse, useSynchronousMode } = that.config; | ||
//Repeat downloading process until success | ||
await that._makeUntilSuccessful(async () => { | ||
const download = new Download({ url, directory, fileName, cloneFiles, timeout, headers, httpsAgent, proxy, onResponse, onBeforeSave, onProgress, shouldBufferResponse, useSynchronousMode }); | ||
const download = new Download({ url, directory, fileName, cloneFiles, timeout, headers, httpsAgent, proxy, onResponse, onBeforeSave, onProgress, shouldBufferResponse, useSynchronousMode }); | ||
this._currentDownload = download | ||
@@ -185,3 +157,3 @@ | ||
// console.log('e from shouldstop',e) | ||
if (e.code === 'ERR_REQUEST_CANCELLED')//Means the request was cancelled, therefore no repetition is required. | ||
@@ -188,0 +160,0 @@ return true; |
{ | ||
"name": "nodejs-file-downloader", | ||
"version": "4.5.1", | ||
"version": "4.5.2", | ||
"description": "A file downloader for NodeJs", | ||
"main": "Downloader.js", | ||
"scripts": { | ||
"test": "mocha Downloader.test.js", | ||
"test": "mocha *.test.js", | ||
"test-timeout": "mocha timeout-cancellation.test.js", | ||
"test-watch": "nodemon --exec \"npm test\"" | ||
@@ -34,3 +35,4 @@ }, | ||
"nock": "^13.0.4", | ||
"rimraf": "^3.0.2" | ||
"rimraf": "^3.0.2", | ||
"supertest": "^6.1.3" | ||
}, | ||
@@ -37,0 +39,0 @@ "homepage": "https://github.com/ibrod83/nodejs-file-downloader", |
@@ -20,3 +20,2 @@ nodejs-file-downloader is a simple utility for downloading files. It hides the complexity of dealing with streams, redirects, paths and duplicate file names. Can automatically repeat failed downloads. | ||
* [Prevent unnecessary repetition](#prevent-unnecessary-repetition) | ||
* [Cancel a download](#cancel-a-download) | ||
* [Use a Proxy](#use-a-proxy) | ||
@@ -238,40 +237,2 @@ - [Error handling](#error-handling) | ||
#### Cancel a download | ||
This feature is new. Kindly report any bugs you encounter. | ||
Useful for Electron apps. | ||
```javascript | ||
const downloader = new Downloader({ | ||
url: 'http://212.183.159.230/200MB.zip', | ||
directory: "./", | ||
}) | ||
try { | ||
//Mocking cancellation | ||
setTimeout(()=>{ | ||
downloader.cancel() | ||
},2000) | ||
await downloader.download(); | ||
//If the download is cancelled, the promise will not be resolved, so this part is never reached | ||
console.log('done'); | ||
} catch (error) {//When the download is cancelled, 'ERR_REQUEST_CANCELLED' error is thrown. This is how you can handle cancellations in your code. | ||
if(error.code === 'ERR_REQUEST_CANCELLED'){ | ||
//do something after cancellation.. | ||
}else{ | ||
//handle general error.. | ||
} | ||
} | ||
``` | ||
| ||
#### Use a proxy | ||
@@ -278,0 +239,0 @@ |
@@ -16,3 +16,3 @@ | ||
// res.send('Hello World!') | ||
const read = fs.createReadStream('./fixtures/Desert.jpg'); | ||
const read = fs.createReadStream('./fixtures/Desert.jpg',{highWaterMark:2000}); | ||
// read.pipe(res); | ||
@@ -41,2 +41,5 @@ | ||
app.get('/cancelBeforeStream', async (req, res) => { | ||
@@ -70,3 +73,3 @@ // console.log('incoming request!') | ||
app.get('/timeout', (req, res) => { | ||
app.get('/timeoutDuringStream', (req, res) => { | ||
// console.log('incoming request!') | ||
@@ -113,2 +116,19 @@ // res.send('Hello World!') | ||
app.get('/timeoutBeforeResponse', async(req, res) => { | ||
// console.log('incoming request!') | ||
// res.send('Hello World!') | ||
const read = fs.createReadStream('./fixtures/Koala.jpg',{highWaterMark:2000}); | ||
// read.pipe(res); | ||
await timeout(5000) | ||
const modifiedStream = Readable.from((async function* () { | ||
for await (const chunk of read) { | ||
yield chunk | ||
} | ||
})()); | ||
modifiedStream.pipe(res); | ||
}) | ||
const server = app.listen(port, () => { | ||
@@ -134,1 +154,46 @@ console.log(port) | ||
// async function* sourceData() { | ||
// yield 1; | ||
// yield 2; | ||
// yield 3; | ||
// } | ||
// async function* filter(source, predicate) { | ||
// for await (const item of source) { | ||
// if (await predicate(item)) { | ||
// yield item; | ||
// } | ||
// } | ||
// } | ||
// async function* map(source, mapper) { | ||
// for await (const item of source) { | ||
// yield await mapper(item); | ||
// } | ||
// } | ||
// let iter = sourceData(); | ||
// iter = filter(iter, async val => await checkNumIsValidViaExternalService(val)); | ||
// iter = map(iter, val => val.toFixed(2)); | ||
// iter = map(iter, async val => { | ||
// await new Promise(resolve => setTimeout(resolve, 100)); | ||
// return val; | ||
// }); | ||
// const finalIter = sourceData() | ||
// .filter(...) | ||
// .map(...) | ||
// .map(...) | ||
// const finalIter = pipe( | ||
// sourceData(), | ||
// filter(...), | ||
// map(...), | ||
// map(...) | ||
// ); | ||
// for await (const item of iter) { | ||
// console.log(item); | ||
// } |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
192498
18
2203
7
260
5