You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							296 lines
						
					
					
						
							7.6 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							296 lines
						
					
					
						
							7.6 KiB
						
					
					
				
								var fs = require('fs');
							 | 
						|
								var util = require('util');
							 | 
						|
								var stream = require('stream');
							 | 
						|
								var Readable = stream.Readable;
							 | 
						|
								var Writable = stream.Writable;
							 | 
						|
								var PassThrough = stream.PassThrough;
							 | 
						|
								var Pend = require('pend');
							 | 
						|
								var EventEmitter = require('events').EventEmitter;
							 | 
						|
								
							 | 
						|
								exports.createFromBuffer = createFromBuffer;
							 | 
						|
								exports.createFromFd = createFromFd;
							 | 
						|
								exports.BufferSlicer = BufferSlicer;
							 | 
						|
								exports.FdSlicer = FdSlicer;
							 | 
						|
								
							 | 
						|
								util.inherits(FdSlicer, EventEmitter);
							 | 
						|
								function FdSlicer(fd, options) {
							 | 
						|
								  options = options || {};
							 | 
						|
								  EventEmitter.call(this);
							 | 
						|
								
							 | 
						|
								  this.fd = fd;
							 | 
						|
								  this.pend = new Pend();
							 | 
						|
								  this.pend.max = 1;
							 | 
						|
								  this.refCount = 0;
							 | 
						|
								  this.autoClose = !!options.autoClose;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								FdSlicer.prototype.read = function(buffer, offset, length, position, callback) {
							 | 
						|
								  var self = this;
							 | 
						|
								  self.pend.go(function(cb) {
							 | 
						|
								    fs.read(self.fd, buffer, offset, length, position, function(err, bytesRead, buffer) {
							 | 
						|
								      cb();
							 | 
						|
								      callback(err, bytesRead, buffer);
							 | 
						|
								    });
							 | 
						|
								  });
							 | 
						|
								};
							 | 
						|
								
							 | 
						|
								FdSlicer.prototype.write = function(buffer, offset, length, position, callback) {
							 | 
						|
								  var self = this;
							 | 
						|
								  self.pend.go(function(cb) {
							 | 
						|
								    fs.write(self.fd, buffer, offset, length, position, function(err, written, buffer) {
							 | 
						|
								      cb();
							 | 
						|
								      callback(err, written, buffer);
							 | 
						|
								    });
							 | 
						|
								  });
							 | 
						|
								};
							 | 
						|
								
							 | 
						|
								FdSlicer.prototype.createReadStream = function(options) {
							 | 
						|
								  return new ReadStream(this, options);
							 | 
						|
								};
							 | 
						|
								
							 | 
						|
								FdSlicer.prototype.createWriteStream = function(options) {
							 | 
						|
								  return new WriteStream(this, options);
							 | 
						|
								};
							 | 
						|
								
							 | 
						|
								FdSlicer.prototype.ref = function() {
							 | 
						|
								  this.refCount += 1;
							 | 
						|
								};
							 | 
						|
								
							 | 
						|
								FdSlicer.prototype.unref = function() {
							 | 
						|
								  var self = this;
							 | 
						|
								  self.refCount -= 1;
							 | 
						|
								
							 | 
						|
								  if (self.refCount > 0) return;
							 | 
						|
								  if (self.refCount < 0) throw new Error("invalid unref");
							 | 
						|
								
							 | 
						|
								  if (self.autoClose) {
							 | 
						|
								    fs.close(self.fd, onCloseDone);
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  function onCloseDone(err) {
							 | 
						|
								    if (err) {
							 | 
						|
								      self.emit('error', err);
							 | 
						|
								    } else {
							 | 
						|
								      self.emit('close');
							 | 
						|
								    }
							 | 
						|
								  }
							 | 
						|
								};
							 | 
						|
								
							 | 
						|
								util.inherits(ReadStream, Readable);
							 | 
						|
								function ReadStream(context, options) {
							 | 
						|
								  options = options || {};
							 | 
						|
								  Readable.call(this, options);
							 | 
						|
								
							 | 
						|
								  this.context = context;
							 | 
						|
								  this.context.ref();
							 | 
						|
								
							 | 
						|
								  this.start = options.start || 0;
							 | 
						|
								  this.endOffset = options.end;
							 | 
						|
								  this.pos = this.start;
							 | 
						|
								  this.destroyed = false;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								ReadStream.prototype._read = function(n) {
							 | 
						|
								  var self = this;
							 | 
						|
								  if (self.destroyed) return;
							 | 
						|
								
							 | 
						|
								  var toRead = Math.min(self._readableState.highWaterMark, n);
							 | 
						|
								  if (self.endOffset != null) {
							 | 
						|
								    toRead = Math.min(toRead, self.endOffset - self.pos);
							 | 
						|
								  }
							 | 
						|
								  if (toRead <= 0) {
							 | 
						|
								    self.destroyed = true;
							 | 
						|
								    self.push(null);
							 | 
						|
								    self.context.unref();
							 | 
						|
								    return;
							 | 
						|
								  }
							 | 
						|
								  self.context.pend.go(function(cb) {
							 | 
						|
								    if (self.destroyed) return cb();
							 | 
						|
								    var buffer = new Buffer(toRead);
							 | 
						|
								    fs.read(self.context.fd, buffer, 0, toRead, self.pos, function(err, bytesRead) {
							 | 
						|
								      if (err) {
							 | 
						|
								        self.destroy(err);
							 | 
						|
								      } else if (bytesRead === 0) {
							 | 
						|
								        self.destroyed = true;
							 | 
						|
								        self.push(null);
							 | 
						|
								        self.context.unref();
							 | 
						|
								      } else {
							 | 
						|
								        self.pos += bytesRead;
							 | 
						|
								        self.push(buffer.slice(0, bytesRead));
							 | 
						|
								      }
							 | 
						|
								      cb();
							 | 
						|
								    });
							 | 
						|
								  });
							 | 
						|
								};
							 | 
						|
								
							 | 
						|
								ReadStream.prototype.destroy = function(err) {
							 | 
						|
								  if (this.destroyed) return;
							 | 
						|
								  err = err || new Error("stream destroyed");
							 | 
						|
								  this.destroyed = true;
							 | 
						|
								  this.emit('error', err);
							 | 
						|
								  this.context.unref();
							 | 
						|
								};
							 | 
						|
								
							 | 
						|
								util.inherits(WriteStream, Writable);
							 | 
						|
								function WriteStream(context, options) {
							 | 
						|
								  options = options || {};
							 | 
						|
								  Writable.call(this, options);
							 | 
						|
								
							 | 
						|
								  this.context = context;
							 | 
						|
								  this.context.ref();
							 | 
						|
								
							 | 
						|
								  this.start = options.start || 0;
							 | 
						|
								  this.endOffset = (options.end == null) ? Infinity : +options.end;
							 | 
						|
								  this.bytesWritten = 0;
							 | 
						|
								  this.pos = this.start;
							 | 
						|
								  this.destroyed = false;
							 | 
						|
								
							 | 
						|
								  this.on('finish', this.destroy.bind(this));
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								WriteStream.prototype._write = function(buffer, encoding, callback) {
							 | 
						|
								  var self = this;
							 | 
						|
								  if (self.destroyed) return;
							 | 
						|
								
							 | 
						|
								  if (self.pos + buffer.length > self.endOffset) {
							 | 
						|
								    var err = new Error("maximum file length exceeded");
							 | 
						|
								    err.code = 'ETOOBIG';
							 | 
						|
								    self.destroy();
							 | 
						|
								    callback(err);
							 | 
						|
								    return;
							 | 
						|
								  }
							 | 
						|
								  self.context.pend.go(function(cb) {
							 | 
						|
								    if (self.destroyed) return cb();
							 | 
						|
								    fs.write(self.context.fd, buffer, 0, buffer.length, self.pos, function(err, bytes) {
							 | 
						|
								      if (err) {
							 | 
						|
								        self.destroy();
							 | 
						|
								        cb();
							 | 
						|
								        callback(err);
							 | 
						|
								      } else {
							 | 
						|
								        self.bytesWritten += bytes;
							 | 
						|
								        self.pos += bytes;
							 | 
						|
								        self.emit('progress');
							 | 
						|
								        cb();
							 | 
						|
								        callback();
							 | 
						|
								      }
							 | 
						|
								    });
							 | 
						|
								  });
							 | 
						|
								};
							 | 
						|
								
							 | 
						|
								WriteStream.prototype.destroy = function() {
							 | 
						|
								  if (this.destroyed) return;
							 | 
						|
								  this.destroyed = true;
							 | 
						|
								  this.context.unref();
							 | 
						|
								};
							 | 
						|
								
							 | 
						|
								util.inherits(BufferSlicer, EventEmitter);
							 | 
						|
								function BufferSlicer(buffer, options) {
							 | 
						|
								  EventEmitter.call(this);
							 | 
						|
								
							 | 
						|
								  options = options || {};
							 | 
						|
								  this.refCount = 0;
							 | 
						|
								  this.buffer = buffer;
							 | 
						|
								  this.maxChunkSize = options.maxChunkSize || Number.MAX_SAFE_INTEGER;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								BufferSlicer.prototype.read = function(buffer, offset, length, position, callback) {
							 | 
						|
								  var end = position + length;
							 | 
						|
								  var delta = end - this.buffer.length;
							 | 
						|
								  var written = (delta > 0) ? delta : length;
							 | 
						|
								  this.buffer.copy(buffer, offset, position, end);
							 | 
						|
								  setImmediate(function() {
							 | 
						|
								    callback(null, written);
							 | 
						|
								  });
							 | 
						|
								};
							 | 
						|
								
							 | 
						|
								BufferSlicer.prototype.write = function(buffer, offset, length, position, callback) {
							 | 
						|
								  buffer.copy(this.buffer, position, offset, offset + length);
							 | 
						|
								  setImmediate(function() {
							 | 
						|
								    callback(null, length, buffer);
							 | 
						|
								  });
							 | 
						|
								};
							 | 
						|
								
							 | 
						|
								BufferSlicer.prototype.createReadStream = function(options) {
							 | 
						|
								  options = options || {};
							 | 
						|
								  var readStream = new PassThrough(options);
							 | 
						|
								  readStream.destroyed = false;
							 | 
						|
								  readStream.start = options.start || 0;
							 | 
						|
								  readStream.endOffset = options.end;
							 | 
						|
								  // by the time this function returns, we'll be done.
							 | 
						|
								  readStream.pos = readStream.endOffset || this.buffer.length;
							 | 
						|
								
							 | 
						|
								  // respect the maxChunkSize option to slice up the chunk into smaller pieces.
							 | 
						|
								  var entireSlice = this.buffer.slice(readStream.start, readStream.pos);
							 | 
						|
								  var offset = 0;
							 | 
						|
								  while (true) {
							 | 
						|
								    var nextOffset = offset + this.maxChunkSize;
							 | 
						|
								    if (nextOffset >= entireSlice.length) {
							 | 
						|
								      // last chunk
							 | 
						|
								      if (offset < entireSlice.length) {
							 | 
						|
								        readStream.write(entireSlice.slice(offset, entireSlice.length));
							 | 
						|
								      }
							 | 
						|
								      break;
							 | 
						|
								    }
							 | 
						|
								    readStream.write(entireSlice.slice(offset, nextOffset));
							 | 
						|
								    offset = nextOffset;
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  readStream.end();
							 | 
						|
								  readStream.destroy = function() {
							 | 
						|
								    readStream.destroyed = true;
							 | 
						|
								  };
							 | 
						|
								  return readStream;
							 | 
						|
								};
							 | 
						|
								
							 | 
						|
								BufferSlicer.prototype.createWriteStream = function(options) {
							 | 
						|
								  var bufferSlicer = this;
							 | 
						|
								  options = options || {};
							 | 
						|
								  var writeStream = new Writable(options);
							 | 
						|
								  writeStream.start = options.start || 0;
							 | 
						|
								  writeStream.endOffset = (options.end == null) ? this.buffer.length : +options.end;
							 | 
						|
								  writeStream.bytesWritten = 0;
							 | 
						|
								  writeStream.pos = writeStream.start;
							 | 
						|
								  writeStream.destroyed = false;
							 | 
						|
								  writeStream._write = function(buffer, encoding, callback) {
							 | 
						|
								    if (writeStream.destroyed) return;
							 | 
						|
								
							 | 
						|
								    var end = writeStream.pos + buffer.length;
							 | 
						|
								    if (end > writeStream.endOffset) {
							 | 
						|
								      var err = new Error("maximum file length exceeded");
							 | 
						|
								      err.code = 'ETOOBIG';
							 | 
						|
								      writeStream.destroyed = true;
							 | 
						|
								      callback(err);
							 | 
						|
								      return;
							 | 
						|
								    }
							 | 
						|
								    buffer.copy(bufferSlicer.buffer, writeStream.pos, 0, buffer.length);
							 | 
						|
								
							 | 
						|
								    writeStream.bytesWritten += buffer.length;
							 | 
						|
								    writeStream.pos = end;
							 | 
						|
								    writeStream.emit('progress');
							 | 
						|
								    callback();
							 | 
						|
								  };
							 | 
						|
								  writeStream.destroy = function() {
							 | 
						|
								    writeStream.destroyed = true;
							 | 
						|
								  };
							 | 
						|
								  return writeStream;
							 | 
						|
								};
							 | 
						|
								
							 | 
						|
								BufferSlicer.prototype.ref = function() {
							 | 
						|
								  this.refCount += 1;
							 | 
						|
								};
							 | 
						|
								
							 | 
						|
								BufferSlicer.prototype.unref = function() {
							 | 
						|
								  this.refCount -= 1;
							 | 
						|
								
							 | 
						|
								  if (this.refCount < 0) {
							 | 
						|
								    throw new Error("invalid unref");
							 | 
						|
								  }
							 | 
						|
								};
							 | 
						|
								
							 | 
						|
								function createFromBuffer(buffer, options) {
							 | 
						|
								  return new BufferSlicer(buffer, options);
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								function createFromFd(fd, options) {
							 | 
						|
								  return new FdSlicer(fd, options);
							 | 
						|
								}
							 | 
						|
								
							 |