azure-storage
Version:
Microsoft Azure Storage Client Library for Node.js
262 lines (225 loc) • 6.07 kB
JavaScript
//
// Copyright (c) Microsoft and contributors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
var stream = require('stream');
var util = require('util');
var fs = require('fs');
var validator = require('validator');
var Md5Wrapper = require('../md5-wrapper');
var Constants = require('../util/constants');
var bufferSize = Constants.BlobConstants.DEFAULT_WRITE_BLOCK_SIZE_IN_BYTES;
var EventEmitter = require('events').EventEmitter;
/**
* File read stream
* 1. Calculate md5
* 2. Track reading offset
* 3. Work with customize memory allocator
* 4. Buffer data from stream.
* @param {object} options stream.Readable options
*/
function FileReadStream(path, options) {
stream.Stream.call(this);
this.readable = true;
if(!options) {
options = {};
}
this._destroyed = false;
this._streamEnded = false;
this._fd = null;
this._fileName = undefined;
this._highWaterMark = options.highWaterMark || bufferSize;
this._offset = 0;
this._paused = undefined;
this._allocator = options.allocator;
this._fileName = path;
this._md5hash = null;
this._md5sum = undefined;
if (options.calcContentMd5) {
this._md5hash = new Md5Wrapper().createMd5Hash();
}
this._open();
}
util.inherits(FileReadStream, stream.Stream);
/**
* Open file
*/
FileReadStream.prototype._open = function () {
var flags = 'r';
var self = this;
fs.open(this._fileName, flags, function(error, fd) {
if (error) {
self.emit('error', error);
} else {
self._fd = fd;
self.emit('open', fd);
}
});
};
/**
* Add event listener
*/
FileReadStream.prototype.on = function(event, listener) {
if (event === 'data' && this._paused === undefined) {
this._paused = false;
this._emitData();
}
return EventEmitter.prototype.on.call(this, event, listener);
};
/**
* Set memory allocator
*/
FileReadStream.prototype.setMemoryAllocator = function(allocator) {
this._allocator = allocator;
};
/**
* Get buffer
*/
FileReadStream.prototype._getBuffer = function(size) {
if(this._allocator && this._allocator.getBuffer) {
return this._allocator.getBuffer(size);
} else {
var buffer = new Buffer(size);
return buffer;
}
};
/**
* Release buffer
*/
FileReadStream.prototype._releaseBuffer = function(buffer) {
if(this._allocator && this._allocator.releaseBuffer) {
this._allocator.releaseBuffer(buffer);
}
};
/**
* Emit the data from file
*/
FileReadStream.prototype._emitData = function() {
var self = this;
if(!this._fd) {
this.once('open', function() {
self._emitData();
});
return;
}
if (this._paused || this._streamEnded) {
return;
}
var buffer = this._getBuffer(this._highWaterMark);
fs.read(this._fd, buffer, 0, this._highWaterMark, this._offset, function(error, bytesRead, readBuffer) {
if (error) {
self.emit('error', error);
return;
}
if(bytesRead === 0) {
if(!self._streamEnded) {
self._streamEnded = true;
self.emit('end');
}
return;
}
var range = {
start : self._offset,
end : self._offset + bytesRead - 1,
size : bytesRead
};
var data;
if(bytesRead == self._highWaterMark) {
data = readBuffer;
} else {
data = readBuffer.slice(0, bytesRead);
//Release the current buffer since we created a new one
self._releaseBuffer(readBuffer);
}
if(self._md5hash) {
self._md5hash.update(data);
}
self.emit('data', data, range);
// cleanup
self._offset += bytesRead;
buffer = readBuffer = data = null;
self._emitData();
});
};
/**
* Get file content md5 when read completely.
*/
FileReadStream.prototype.getContentMd5 = function(encoding) {
if (!encoding) encoding = 'base64';
if(!this._md5hash) {
throw new Error('Can\'t get content md5, please set the calcContentMd5 option for FileReadStream.');
} else {
if (this._streamEnded) {
if (!this._md5sum) {
this._md5sum = this._md5hash.digest(encoding);
}
return this._md5sum;
} else {
throw new Error('FileReadStream has not ended.');
}
}
};
/**
* Pause chunk stream
*/
FileReadStream.prototype.pause = function() {
this._paused = true;
};
/**
* Resume read stream
*/
FileReadStream.prototype.resume = function() {
var previousState = this._paused;
if (this._paused) {
this._paused = false;
if(previousState === true) {
//Only start to emit data when it's in pause state
this._emitData();
}
}
};
FileReadStream.prototype.finish = function () {
this.destroy();
};
FileReadStream.prototype.destroy = function () {
if (this._destroyed) {
return;
}
var self = this;
this.readable = false;
function close(fd) {
fs.close(fd || self._fd, function(err) {
if (err) {
self.emit('error', err);
}
else {
self.emit('close');
}
});
self._fd = null;
self._destroyed = true;
}
// when the stream is closed immediately after creating it
if (!validator.isInt(this._fd)) {
this.once('open', close);
return;
}
close();
};
FileReadStream.prototype.stop = function () {
this.destroy();
this._streamEnded = true;
this.emit('end');
};
module.exports = FileReadStream;