UNPKG

@xan105/request

Version:

Simple HTTP request client with file download progress

141 lines (125 loc) 4.51 kB
/* Copyright (c) Anthony Beaumont This source code is licensed under the MIT License found in the LICENSE file in the root directory of this source tree. */ import { join, isAbsolute, relative } from "node:path"; import { resolve as resolvePath, normalize } from "@xan105/fs/path"; import { isStringNotEmpty, } from "@xan105/is"; import { asIntegerPositive, asIntegerPositiveOrZero, asArrayOfStringNotEmpty } from "@xan105/is/opt"; import { Failure } from "@xan105/error"; //optional peerDependencies import { load } from "./util/optPeerDep.js"; const webtorrent = await load("webtorrent"); function download(torrent, dest, option, callbackProgress = () => {}){ return new Promise((resolve, reject) => { //Multiple opt args if (typeof option === "function") { callbackProgress = option; option = null; } if (!option) option = {}; const options = { timeout: asIntegerPositiveOrZero(option.timeout) ?? 10, exclusion: asArrayOfStringNotEmpty(option.exclusion) ?? [], downloadLimit: asIntegerPositive(option.downloadLimit) ?? -1, uploadLimit: asIntegerPositive(option.uploadLimit) ?? 100 }; if(!webtorrent) return reject(new Failure("Couldn't load the module webtorrent", "ERR_MISSING_OPT_MODULE")); if(!isStringNotEmpty(torrent) || !isStringNotEmpty(dest)) return reject(new Failure("Expecting a non empty string for torrent and dest", "ERR_INVALID_ARGS")); const client = new webtorrent({ downloadLimit: options.downloadLimit > -1 ? options.downloadLimit * 1000 : -1, uploadLimit: options.uploadLimit * 1000 }); client.on("error", function (err) { client.destroy(function () { return reject(err); }); }); client.add(torrent, { path: dest }, function (torrent) { const stats = { speed: [], averageSpeed: 0, time: { started: Date.now(), elapsed: 0, previousElapsed: 0 }, }; torrent.deselect(0, torrent.pieces.length - 1, false); torrent.files.forEach(function (file) { if (options.exclusion.includes(file)) { file.deselect(); } else { file.select(); } }); const timeout = { timer: null, hasPeers: false, clear: function () { clearInterval(this.timer); }, set: function () { const self = this; self.timer = setTimeout(function () { if (!self.hasPeers) { self.clear(); client.destroy(function () { return reject(new Failure("timeout", "ERR_TIMEOUT_NO_PEERS")); }); } }, options.timeout * 1000); }, }; timeout.set(); torrent.on("noPeers", function () { if (timeout.hasPeers) timeout.set(); timeout.hasPeers = false; }).on("wire", function () { if (!timeout.hasPeers) timeout.clear(); timeout.hasPeers = true; }).on("download", function () { stats.time.elapsed = Math.floor((Date.now() - stats.time.started) / 1000); if (stats.time.elapsed >= 1) { const currentSpeed = Math.floor(torrent.downloadSpeed / 1000); stats.speed.push(currentSpeed); if ( stats.speed.length >= 1 && stats.time.elapsed == stats.time.previousElapsed + 1) { const sum = stats.speed.reduce((a, b) => a + b, 0); stats.averageSpeed = Math.floor(sum / stats.speed.length); stats.speed = []; } } const percent = Math.floor(torrent.progress * 100); callbackProgress(percent, stats.averageSpeed); stats.time.previousElapsed = stats.time.elapsed; }).on("done", function () { if (timeout.timer) timeout.clear(); const result = { path: resolvePath(torrent.path), name: torrent.name, file: [], }; torrent.files.forEach(function (file) { result.file.push({ name: file.name, path: normalize(isAbsolute(file.path) ? relative(result.path, file.path) : file.path), fullPath: isAbsolute(file.path) ? file.path : join(result.path, file.path) }); }); client.destroy(function () { return resolve(result); }); }).on("error", function (err) { client.destroy(function () { return reject(err); }); }); }); }); } export { download };