@ourongxing/downloader
Version:
A file downloader for NodeJs
202 lines (184 loc) • 5.08 kB
JavaScript
const rpur = require("./utils/rpur")
const { capitalize } = require("./utils/string")
const Download = require("./Download")
const configTypes = {
url: {
type: "string",
mandatory: true
},
directory: {
type: "string",
mandatory: false
},
fileName: {
type: "string",
mandatory: false
},
cloneFiles: {
type: "boolean",
mandatory: false
},
proxy: {
type: "string",
mandatory: false
}
}
const validateConfig = config => {
const generateTypeError = prop => {
throw new Error(`config.${prop} must be of type ${configTypes[prop].type}`)
}
for (let prop in configTypes) {
if (configTypes[prop].mandatory) {
if (!config[prop]) throw new Error(`Must supply a config.${prop}`)
if (-1 === [].concat(configTypes[prop].type).indexOf(typeof config[prop]))
generateTypeError(prop)
}
if (
config.hasOwnProperty(prop) &&
-1 === [].concat(configTypes[prop].type).indexOf(typeof config[prop])
)
generateTypeError(prop)
}
}
module.exports = class Downloader {
/**
*
* @param {object} config
* @param {string} config.url
* @param {boolean} [config.useHashFileName]
* @param {string} [config.directory]
* @param {string} [config.fileName = undefined]
* @param {boolean} [config.cloneFiles=true] true will create a duplicate. false will overwrite the existing file.
* @param {boolean} [config.skipExistingFileName = false] true will cause the downloader to skip the download process in case a file with the same name already exists.
* @param {number} [config.timeout=6000]
* @param {number} [config.maxAttempts=1]
* @param {object} [config.headers = undefined]
* @param {object} [config.httpsAgent = undefined]
* @param {string} [config.proxy = undefined]
* @param {function} [config.onError = undefined]
* @param {function} [config.onResponse = undefined]
* @param {function} [config.onBeforeSave = undefined]
* @param {function} [config.onProgress = undefined]
* @param {function} [config.shouldStop = undefined]
* @param {boolean} [config.shouldBufferResponse = false]
* @param {boolean} [config.useSynchronousMode = false]
*/
constructor(config) {
if (!config || typeof config !== "object") {
throw new Error("Must provide a valid config object")
}
validateConfig(config)
const defaultConfig = {
directory: "./",
fileName: undefined,
timeout: 6000,
maxAttempts: 1,
useSynchronousMode: false,
httpsAgent: undefined,
proxy: undefined,
headers: undefined,
cloneFiles: true,
skipExistingFileName: false,
shouldBufferResponse: false,
onResponse: undefined,
onBeforeSave: undefined,
onError: undefined,
onProgress: undefined
}
this.config = {
...defaultConfig,
...config
}
if (this.config.filename) {
this.config.fileName = this.config.filename
}
this._currentDownload = null
}
//For EventEmitter backwards compatibility
on(event, callback) {
this.config[`on${capitalize(event)}`] = callback
}
async download() {
const that = this
const {
url,
directory,
fileName,
cloneFiles,
skipExistingFileName,
timeout,
headers,
httpsAgent,
proxy,
onResponse,
onBeforeSave,
onProgress,
shouldBufferResponse,
useSynchronousMode,
useHashFileName
} = that.config
//Repeat downloading process until success
const {
filePath,
downloadStatus,
fileName: _fileName,
fileHash
} = await that._makeUntilSuccessful(async () => {
const download = new Download({
useHashFileName,
url,
directory,
fileName,
cloneFiles,
skipExistingFileName,
timeout,
headers,
httpsAgent,
proxy,
onResponse,
onBeforeSave,
onProgress,
shouldBufferResponse,
useSynchronousMode
})
this._currentDownload = download
return await download.start()
})
return { filePath, downloadStatus, fileHash, fileName: _fileName }
}
/**
* @param {Function} asyncFunc
* @return {Promise<any>}
*/
async _makeUntilSuccessful(asyncFunc) {
let data
const func = asyncFunc.bind(this)
await rpur(
async () => {
data = await func()
},
{
onError: async e => {
if (this.config.onError) {
await this.config.onError(e)
}
},
shouldStop: async e => {
if (e.code === "ERR_REQUEST_CANCELLED")
//Means the request was cancelled, therefore no repetition is required.
return true
if (this.config.shouldStop) {
if ((await this.config.shouldStop(e)) === true) {
return true
}
}
},
maxAttempts: this.config.maxAttempts
}
)
return data
}
cancel() {
this._currentDownload.cancel()
}
}