UNPKG

nxkit

Version:

This is a collection of tools, independent of any other libraries

247 lines (246 loc) 10.3 kB
"use strict"; /* ***** BEGIN LICENSE BLOCK ***** * Distributed under the BSD license: * * Copyright (c) 2015, xuewen.chu * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of xuewen.chu nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL xuewen.chu BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * ***** END LICENSE BLOCK ***** */ Object.defineProperty(exports, "__esModule", { value: true }); const util_1 = require("./util"); const event_1 = require("./event"); const http = require("http"); const https = require("https"); const url = require("url"); const fs = require("fs"); const errno_1 = require("./errno"); const request_1 = require("./request"); const wget = function wget(www, save, options) { var { renewal = false, limit = wget.LIMIT, // limit rate byte/second // limitTime = 0, // limt network use time onProgress, timeout = 12e4, } = options || {}; limit = Number(limit) || 0; renewal = renewal || false; var progress = onProgress || util_1.default.noop; var _reject, _req; var ok = false; function abort() { if (!ok) { // abrot ok = true; if (_reject) { _reject(Error.new(errno_1.default.ERR_WGET_FORCE_ABORT)); } if (_req) { _req.abort(); } } } var promise = new Promise((resolve, reject) => { _reject = reject; if (ok) // abort return _reject(Error.new(errno_1.default.ERR_WGET_FORCE_ABORT)); var uri = url.parse(String(www)); var isSSL = uri.protocol == 'https:'; var lib = isSSL ? https : http; var options = { hostname: uri.hostname, port: Number(uri.port) || (isSSL ? 443 : 80), path: uri.path, method: 'GET', headers: { 'User-Agent': request_1.default.userAgent, }, timeout: timeout || 12e4, rejectUnauthorized: false, }; options.rejectUnauthorized = false; if (isSSL) { options.agent = new https.Agent(options); } fs.stat(save, function (err, stat) { var start_range = 0; var download_total = 0; var download_size = 0; if (renewal) { if (!err) { if (stat.isFile()) { start_range = stat.size; download_size = start_range; } else { ok = true; // abort return reject(Error.new(errno_1.default.ERR_WGET_RENEWAL_FILE_TYPE_ERROR)); } } if (start_range) { options.headers.range = 'bytes=' + start_range + '-'; } } var fd = 0; var res_end = false; var buffers = new event_1.List(); function error(err) { if (!ok) { ok = true; var e = Error.new(err); if (fd) { var _fd = fd; fd = 0; fs.close(_fd, e => reject(e)); } else { reject(e); } } } function write() { if (fd) { if (buffers.length) { fs.write(fd, buffers.first.value, function (err) { if (err) { error(err); req.abort(); } else { buffers.shift(); write(); } }); } else if (res_end) { ok = true; var _fd = fd; fd = 0; fs.close(_fd, e => resolve({ total: download_total, size: download_size })); } } } // new request var req = lib.request(options, (res) => { if (ok) // abort return; _req = req; if (res.statusCode == 200 || res.statusCode == 206) { res.pause(); res.socket.setNoDelay(true); res.socket.setKeepAlive(true, 3e4); // 30s res.socket.on('error', e => error(e)); res.on('error', e => error(e)); var speed = 0; // speed / 3 second var time = 0; var ptime = 0; // pause time res.on('data', (chunk) => { download_size += chunk.length; var st = Date.now(); var ts = st - time; // time span if (ts) { var ispeed = chunk.length / ts * 1e3; // instantaneous speed/second // speed = (speed + ispeed * 0.11) * 0.901; // (100 + 100 * 0.11) * 0.901, Finally converges to ispeed speed = (speed + ispeed * 0.25) * 0.8; // (100 + 100 * 0.25) * 0.8, Finally converges to ispeed // limit flow, byte/second if (limit && time) { if (speed > limit) { ptime = Math.min(1e4, ptime + 5); // increase } else { ptime = Math.max(0, ptime - 5); // lessen } if (ptime > 0) { res.pause(); util_1.default.sleep(ptime).then(e => res.resume()).catch(e => { }); } } time = st; // console.log(Math.floor(speed / 1024), Math.floor(ispeed / 1024)); } try { progress({ total: download_total, size: download_size, speed }); } catch (e) { console.error(e); } buffers.push(chunk); if (buffers.length == 1) write(); }); res.on('end', () => { res_end = true; if (buffers.length == 0) write(); }); var flag = 'w'; // set file open flag if (start_range && res.statusCode == 206 && res.headers['accept-ranges'] == 'bytes') { var content_range = res.headers['content-range']; var m = content_range.match(/^bytes\s(\d+)-/); if (m && Number(m[1]) == start_range) { flag = 'a'; } } // set content total size download_total = Number(res.headers['content-length']) || 0; if (download_total) { if (flag == 'a') { download_total += download_size; } } fs.open(save, flag, function (err, _fd) { if (err) { error(err); req.abort(); } else { fd = _fd; res.resume(); } }); } else { var err = Error.new(errno_1.default.ERR_DOWNLOAD_FAIL); err.url = www; err.save = save; err.statusCode = res.statusCode; err.httpVersion = res.httpVersion; err.headers = res.headers; error(err); req.abort(); } }); req.on('abort', () => error(errno_1.default.ERR_HTTP_REQUEST_ABORT)); req.on('error', e => error(e)); req.on('timeout', () => { error(errno_1.default.ERR_HTTP_REQUEST_TIMEOUT); req.abort(); }); req.end(); // send }); }); promise.abort = abort; return promise; }; wget.LIMIT = 0; exports.default = wget;