123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109 |
- var progress = require('request-progress');
- var request = require('request');
- var Q = require('q');
- var mout = require('mout');
- var retry = require('retry');
- var fs = require('graceful-fs');
- var createError = require('./createError');
- var errorCodes = [
- 'EADDRINFO',
- 'ETIMEDOUT',
- 'ECONNRESET',
- 'ESOCKETTIMEDOUT'
- ];
- function download(url, file, options) {
- var operation;
- var response;
- var deferred = Q.defer();
- var progressDelay = 8000;
- options = mout.object.mixIn({
- retries: 5,
- factor: 2,
- minTimeout: 1000,
- maxTimeout: 35000,
- randomize: true
- }, options || {});
- // Retry on network errors
- operation = retry.operation(options);
- operation.attempt(function () {
- var req;
- var writeStream;
- var contentLength;
- var bytesDownloaded = 0;
- req = progress(request(url, options), {
- delay: progressDelay
- })
- .on('response', function (res) {
- var status = res.statusCode;
- if (status < 200 || status >= 300) {
- return deferred.reject(createError('Status code of ' + status, 'EHTTP'));
- }
- response = res;
- contentLength = Number(res.headers['content-length']);
- })
- .on('data', function (data) {
- bytesDownloaded += data.length;
- })
- .on('progress', function (state) {
- deferred.notify(state);
- })
- .on('end', function () {
- // Check if the whole file was downloaded
- // In some unstable connections the ACK/FIN packet might be sent in the
- // middle of the download
- // See: https://github.com/joyent/node/issues/6143
- if (contentLength && bytesDownloaded < contentLength) {
- req.emit('error', createError('Transfer closed with ' + (contentLength - bytesDownloaded) + ' bytes remaining to read', 'EINCOMPLETE'));
- }
- })
- .on('error', function (error) {
- var timeout = operation._timeouts[0];
- // Reject if error is not a network error
- if (errorCodes.indexOf(error.code) === -1) {
- return deferred.reject(error);
- }
- // Next attempt will start reporting download progress immediately
- progressDelay = 0;
- // Check if there are more retries
- if (operation.retry(error)) {
- // Ensure that there are no more events from this request
- req.removeAllListeners();
- req.on('error', function () {});
- // Ensure that there are no more events from the write stream
- writeStream.removeAllListeners();
- writeStream.on('error', function () {});
- return deferred.notify({
- retry: true,
- delay: timeout,
- error: error
- });
- }
- // No more retries, reject!
- deferred.reject(error);
- });
- // Pipe read stream to write stream
- writeStream = req
- .pipe(fs.createWriteStream(file))
- .on('error', deferred.reject)
- .on('close', function () {
- deferred.resolve(response);
- });
- });
- return deferred.promise;
- }
- module.exports = download;
|