1 | var request = require('request')
|
2 | var fs = require('fs')
|
3 | var path = require('path')
|
4 | var log = require('single-line-log').stdout
|
5 | var progress = require('progress-stream')
|
6 | var prettyBytes = require('pretty-bytes')
|
7 | var throttle = require('throttleit')
|
8 | var EventEmitter = require('events').EventEmitter
|
9 |
|
10 | function noop () {}
|
11 |
|
12 | module.exports = function(url, opts, cb) {
|
13 | opts.target = path.resolve(opts.dir || process.cwd(), opts.target || path.basename(url))
|
14 | if (opts.resume) {
|
15 | resume(url, opts, cb)
|
16 | } else {
|
17 | download(url, opts, cb)
|
18 | }
|
19 |
|
20 | var progressEmitter = new EventEmitter()
|
21 | return progressEmitter
|
22 |
|
23 | function resume(url, opts, cb) {
|
24 | fs.stat(opts.target, function (err, stats) {
|
25 | if (err && err.code === 'ENOENT') {
|
26 | return download(url, opts, cb)
|
27 | }
|
28 | if (err) {
|
29 | return cb(err)
|
30 | }
|
31 | var offset = stats.size
|
32 | var req = request.get(url)
|
33 |
|
34 | req.on('error', cb)
|
35 | req.on('response', function(resp) {
|
36 | resp.destroy()
|
37 |
|
38 | var length = parseInt(resp.headers['content-length'], 10)
|
39 |
|
40 |
|
41 | if (length === offset) return cb()
|
42 |
|
43 | if (!isNaN(length) && length > offset && /bytes/.test(resp.headers['accept-ranges'])) {
|
44 | opts.range = [offset, length]
|
45 | }
|
46 |
|
47 | download(url, opts, cb)
|
48 | })
|
49 | })
|
50 | }
|
51 |
|
52 | function download(url, opts, cb) {
|
53 | var headers = opts.headers || {}
|
54 | if (opts.range) {
|
55 | headers.Range = 'bytes=' + opts.range[0] + '-' + opts.range[1]
|
56 | }
|
57 | var _log = opts.verbose ? log : noop
|
58 | var read = request(url, { headers: headers })
|
59 | var throttledRender = throttle(render, opts.frequency || 100)
|
60 | var speed = "0 Kb"
|
61 |
|
62 | read.on('error', cb)
|
63 | read.on('response', function(resp) {
|
64 | if (resp.statusCode > 299 && !opts.force) return cb(new Error('GET ' + url + ' returned ' + resp.statusCode))
|
65 | var write = fs.createWriteStream(opts.target, {flags: opts.resume ? 'a' : 'w'})
|
66 | write.on('error', cb)
|
67 | write.on('finish', cb)
|
68 |
|
69 | var progressStream = progress({ length: Number(resp.headers['content-length']) }, onprogress)
|
70 |
|
71 | progressStream.on('progress', function(data) {
|
72 | speed = prettyBytes(data.speed)
|
73 | progressEmitter.emit('progress', data)
|
74 | })
|
75 |
|
76 | render(0)
|
77 | resp
|
78 | .pipe(progressStream)
|
79 | .pipe(write)
|
80 |
|
81 | })
|
82 |
|
83 | function render(pct) {
|
84 | var bar = Array(Math.floor(50 * pct / 100)).join('=')+'>'
|
85 | while (bar.length < 50) bar += ' '
|
86 |
|
87 | _log(
|
88 | 'Downloading '+path.basename(opts.target)+'\n'+
|
89 | '['+bar+'] '+pct.toFixed(1)+'% (' + speed + '/s)\n'
|
90 | )
|
91 | }
|
92 |
|
93 | function onprogress(p) {
|
94 | var pct = p.percentage
|
95 | if (pct === 100) render(pct)
|
96 | else throttledRender(pct)
|
97 | }
|
98 | }
|
99 | }
|