Skip to content

Instantly share code, notes, and snippets.

@niklasfi
Created January 30, 2011 15:40
Show Gist options
  • Save niklasfi/802954 to your computer and use it in GitHub Desktop.
Save niklasfi/802954 to your computer and use it in GitHub Desktop.
var fs = require('fs');
var http = require('http');
var urlparser = require('url');
var k = process.binding('constants');
function Download(url,path, options,callback){
/*
existing options:
- range: [start, end] specifies the Range of the part to be downloaded. start or end may be null to specify download "from beginning" or "to end" respecively.
- continue: if not set to true file will be truncated before writing to it
*/
this.callback=callback;
this.http.tx={
headers: null,
range: null,
parsedUrl: urlparser.parse(url)
};
this.http.rx= {
headers: null,
status: null,
range: null,
bytes: 0,
responseObject: null
}
this.http.state.current=this.http.state.clear;
this.fs={
fd: null,
position: 0,
target: null,
fileSize: null,
truncate: true,
path: null
}
this.cache.buffer = new Buffer(this.cache.size),
this.position= 0
this.timer= {timerObject: null, bytesLastStatsUpdate: 0, timerInterval: 2000};
//this.parseOptions(options || {});
this.Download(url,path);
}
Download.prototype = {
http: {
state: {clear: 0, awaitingResponse: 1, downloading: 2, finished: 3},
parseResponseHeaders: function(that){
var rx=that.http.rx;
var headers=rx.headers;
that.fs.target=headers['content-length'] || null;
console.log('target: ' + that.fs.target);
if(headers['content-range'] && new RegExp("bytes \d+-\d+/((\d+)|\*)").test(headers['content-range'])){
var s = headers['content-range'].substr(0,6).split('/')
that.rx.range=s[0].split('-');
that.fs.fileSize=s[1]!="*" ? s[1] : null;
}
}
},
cache: {
size: 524288, //512kB
flush: function(that){
that.http.rx.responseObject.pause();
fs.write(that.fs.fd,that.cache.buffer,0,that.cache.buffer.position,that.fs.position,function(err,written){
if(that.http.state.current == that.http.state.finished){
fs.close(this.fs.fd);
delete this.cache;
}
else{
that.http.rx.responseObject.resume()
}
});
that.fs.position+=that.cache.position;
that.cache.position=0;
},
append: function(that,chunk){
chunk.position=0;
if(Buffer.isBuffer(chunk)){
console.log('is buffer!');
}
else{
console.log('not a buffer!');
}
while(that.cache.position+chunk.length-chunk.position>=that.cache.size){
chunk.copy(that.cache.buffer,that.cache.position,chunk.position,that.cache.size-that.cache.position);
chunk.position+=that.cache.size-that.cache.position;
that.buffer.flush(that);
}
chunk.copy(that.cache.buffer,that.cache.position,chunk.size-chunk.position)
that.cache.position+=chunk.size-chunk.position;
if(that.http.rx.bytes==that.fs.target || that.http.state.current == that.http.state.finished){
that.http.state.current=that.http.state.finished
clearInterval(that.timer.timerObject);
that.rx.responseObject.destroy();
}
}
},
Download: function (url,path){
this.http.state.current = this.http.state.awaitingResponse;
var options={host: this.http.tx.parsedUrl.host, port: this.http.tx.parsedUrl.port ||'80', path: this.http.tx.parsedUrl.pathname, method: 'GET'};
if(this.http.tx.range && (options['Range'][0] || options['Range'][1])) options['Range'] = 'bytes='+(this.http.tx.range[0] || "") +"-"+(this.http.range[1] || "")
var g=http.get(options);
console.log('now waiting for a response');
var that= this;
g.on('response', function(res) {
that.http.rx.responseObject = res;
that.http.rx.responseObject.pause();
that.http.rx.responseObject.setEncoding('binary');
console.log('got response: ' + that.http.rx.responseObject.statusCode);
that.http.rx.headers=that.http.rx.responseObject.headers;
that.http.parseResponseHeaders(that)
that.http.rx.status=that.http.rx.responseObject.statusCode;
that.http.state.current = that.http.state.downloading;
res.on('data',function(chunk){that.http.rx.bytes+=chunk.length; that.cache.append(that,chunk)});
that.openFile(path);
});
g.on('error', function(e) {
console.log('got error while sending get request: ' + e.message);
process.exit(2);
});
g.on('end', function() {
this.http.state.current = this.http.state.finished;
console.log('download complete');
});
},
openFile: function(path){
console.log('path: ' + path)
var that=this;
fs.open(path, k.O_WRONLY | k.O_CREAT | (this.fs.truncate ? 0 : k.O_TRUNC ) , 0777, function(err, fd){
if(err){
console.error("Got error while opening file: " + err.message);
process.exit(1);
}
that.fs.fd=fd;
console.log('file sucessfully opened')
that.http.rx.responseObject.resume();
that.timer.timerObject=setInterval(function(){that.statsTick(that)},that.timer.timerInterval);
});
},
statsTick: function(that){
console.log('recieved: ' + that.http.rx.bytes + 'B of '+ that.fs.target + 'B, progress: ' + ( that.http.rx.bytes /that.fs.target*100).toPrecision(4) + '%, download-rate: '
+ ((that.http.rx.bytes-that.timer.bytesLastStatsUpdate)/that.timer.timerInterval).toPrecision(4) + 'kB/s, connection status: ' + that.http.state.current);
that.timer.bytesLastStatsUpdate = that.http.rx.bytes;
}
}
var d = new Download('http://dl.google.com/earth/client/current/GoogleEarthLinux.bin', 'googleearth.bin');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment