UNPKG

nodulator

Version:

Complete NodeJS Framework for Restfull APIs

490 lines (442 loc) 17.6 kB
var BinaryParser = require('../bson/binary_parser').BinaryParser, Chunk = require('./chunk').Chunk, DbCommand = require('../commands/db_command').DbCommand, OrderedHash = require('../bson/collections').OrderedHash, Integer = require('../goog/math/integer').Integer, Long = require('../goog/math/long').Long, ObjectID = require('../bson/bson').ObjectID, Buffer = require('buffer').Buffer; var GridStore = exports.GridStore = function(db, filename, mode, options) { this.db = db; this.filename = filename; this.mode = mode == null ? "r" : mode; this.options = options == null ? {} : options; this.root = this.options['root'] == null ? exports.GridStore.DEFAULT_ROOT_COLLECTION : this.options['root']; this.position = 0; // Getters and Setters this.__defineGetter__("chunkSize", function() { return this.internalChunkSize; }); this.__defineSetter__("chunkSize", function(value) { if(!(this.mode[0] == "w" && this.position == 0 && this.uploadDate == null)) { this.internalChunkSize = this.internalChunkSize; } else { this.internalChunkSize = value; } }); this.__defineGetter__("md5", function() { return this.internalMd5; }); this.__defineSetter__("md5", function(value) {}); }; GridStore.prototype.open = function(callback) { var self = this; this.collection(function(err, collection) { collection.find({'filename':self.filename}, function(err, cursor) { cursor.nextObject(function(err, doc) { // Chek if the collection for the files exists otherwise prepare the new one if(doc != null) { self.fileId = doc._id; self.contentType = doc.contentType; self.internalChunkSize = doc.chunkSize; self.uploadDate = doc.uploadDate; self.aliases = doc.aliases; self.length = doc.length; self.metadata = doc.metadata; self.internalMd5 = doc.md5; } else { self.fileId = new ObjectID(); self.contentType = exports.GridStore.DEFAULT_CONTENT_TYPE; self.internalChunkSize = Chunk.DEFAULT_CHUNK_SIZE; self.length = 0; } // Process the mode of the object if(self.mode == "r") { self.nthChunk(0, function(err, chunk) { self.currentChunk = chunk; self.position = 0; callback(null, self); }); } else if(self.mode == "w") { self.chunkCollection(function(err, collection2) { // Create index for the chunks collection.createIndex([['files_id', 1], ['n', 1]], function(err, index) { // Delete any existing chunks self.deleteChunks(function(err, result) { self.currentChunk = new Chunk(self, {'n':0}); self.contentType = self.options['content_type'] == null ? self.contentType : self.options['content_type']; self.internalChunkSize = self.options['chunk_size'] == null ? self.internalChunkSize : self.options['chunk_size']; self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata']; self.position = 0; callback(null, self); }); }); }); } else if(self.mode == "w+") { self.chunkCollection(function(err, collection) { // Create index for the chunks collection.createIndex([['files_id', 1], ['n', 1]], function(err, index) { self.nthChunk(self.lastChunkNumber, function(err, chunk) { // Set the current chunk self.currentChunk = chunk == null ? new Chunk(self, {'n':0}) : chunk; self.currentChunk.position = self.currentChunk.data.length(); self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata']; self.position = self.length; callback(null, self); }); }); }); } else { callback(new Error("Illegal mode " + self.mode), null); } }); }); }); }; GridStore.prototype.write = function(string, close, callback) { if(typeof close === "function") { callback = close; close = null; } var self = this; var finalClose = close == null ? false : close; string = string instanceof Buffer ? string.toString() : string; if(self.mode[0] != "w") { callback(new Error(self.filename + " not opened for writing"), null); } else { if((self.currentChunk.position + string.length) > self.chunkSize) { var previousChunkNumber = self.currentChunk.chunkNumber; var leftOverDataSize = self.chunkSize - self.currentChunk.position; var previousChunkData = string.substr(0, leftOverDataSize); var leftOverData = string.substr(leftOverData, (string.length - leftOverDataSize)); // Let's finish the current chunk and then call write again for the remaining data self.currentChunk.write(previousChunkData, function(err, chunk) { chunk.save(function(err, result) { self.currentChunk = new Chunk(self, {'n': (previousChunkNumber + 1)}); self.position = self.position + leftOverDataSize; // Write the remaining data self.write(leftOverData, function(err, gridStore) { if(finalClose) { self.close(function(err, result) { callback(null, gridStore); }); } else { callback(null, gridStore); } }); }); }); } else { self.currentChunk.write(string, function(err, chunk) { self.position = self.position + string.length; if(finalClose) { self.close(function(err, result) { callback(null, self); }); } else { callback(null, self); } }); } } }; GridStore.prototype.buildMongoObject = function(callback) { // var mongoObject = new OrderedHash(); var length = this.currentChunk != null ? (this.currentChunk.chunkNumber * this.chunkSize + this.currentChunk.position) : 0; var mongoObject = { '_id': this.fileId, 'filename': this.filename, 'contentType': this.contentType, 'length': length < 0 ? 0 : length, 'chunkSize': this.chunkSize, 'uploadDate': this.uploadDate, 'aliases': this.aliases, 'metadata': this.metadata }; var md5Command = new OrderedHash(); md5Command.add('filemd5', this.fileId).add('root', this.root); this.db.command(md5Command, function(err, results) { mongoObject.md5 = results.md5; callback(mongoObject); }); }; GridStore.prototype.close = function(callback) { var self = this; if(self.mode[0] == "w") { if(self.currentChunk != null && self.currentChunk.position > 0) { self.currentChunk.save(function(err, chuck) { self.collection(function(err, files) { // Build the mongo object if(self.uploadDate != null) { files.remove({'_id':self.fileId}, function(err, collection) { self.buildMongoObject(function(mongoObject) { files.save(mongoObject, function(err, doc) { callback(err, doc); }); }); }); } else { self.uploadDate = new Date(); self.buildMongoObject(function(mongoObject) { files.save( mongoObject, function(err, doc) { callback(err, doc); }); }); } }); }); } else { self.collection(function(err, files) { self.uploadDate = new Date(); self.buildMongoObject(function(mongoObject) { files.save(mongoObject, function(err, doc) { callback(err, doc); }); }); }); } } else { callback(new Error("Illegal mode " + self.mode), null); } }; GridStore.prototype.nthChunk = function(chunkNumber, callback) { var self = this; self.chunkCollection(function(err, collection) { collection.find({'files_id':self.fileId, 'n':chunkNumber}, function(err, cursor) { cursor.nextObject(function(err, chunk) { var finalChunk = chunk == null ? {} : chunk; callback(null, new Chunk(self, finalChunk)); }); }); }); }; GridStore.prototype.lastChunkNumber = function() { return Integer.fromNumber((self.length/self.chunkSize)).toInt(); }; GridStore.prototype.chunkCollection = function(callback) { this.db.collection((this.root + ".chunks"), callback); }; GridStore.prototype.deleteChunks = function(callback) { var self = this; if(self.fileId != null) { self.chunkCollection(function(err, collection) { collection.remove({'files_id':self.fileId}, function(err, result) { callback(null, true); }); }); } else { callback(null, true); } }; GridStore.prototype.collection = function(callback) { this.db.collection(this.root + ".files", function(err, collection) { callback(err, collection); }); }; GridStore.prototype.readlines = function(separator, callback) { var args = Array.prototype.slice.call(arguments, 0); callback = args.pop(); separator = args.length ? args.shift() : null; this.read(function(err, data) { var items = data.split(separator); items = items.length > 0 ? items.splice(0, items.length - 1) : []; for(var i = 0; i < items.length; i++) { items[i] = items[i] + separator; } callback(null, items); }); }; GridStore.prototype.rewind = function(callback) { var self = this; if(this.currentChunk.chunkNumber != 0) { if(this.mode[0] == "w") { self.deleteChunks(function(err, gridStore) { self.currentChunk = new Chunk(self, {'n': 0}); self.position = 0; callback(null, self); }); } else { self.currentChunk(0, function(err, chunk) { self.currentChunk = chunk; self.currentChunk.rewind(); self.position = 0; callback(null, self); }); } } else { self.currentChunk.rewind(); self.position = 0; callback(null, self); } }; GridStore.prototype.read = function(length, buffer, callback) { var self = this; var args = Array.prototype.slice.call(arguments, 0); callback = args.pop(); length = args.length ? args.shift() : null; buffer = args.length ? args.shift() : null; // The data is a c-terminated string and thus the length - 1 var finalBuffer = buffer == null ? '' : buffer; var finalLength = length == null ? self.length - self.position : length; var numberToRead = finalLength; if((self.currentChunk.length() - self.currentChunk.position + 1 + finalBuffer.length) >= finalLength) { finalBuffer = finalBuffer + self.currentChunk.read(finalLength - finalBuffer.length); numberToRead = numberToRead - finalLength; self.position = finalBuffer.length; callback(null, finalBuffer); } else { finalBuffer = finalBuffer + self.currentChunk.read(self.currentChunk.length()); numberToRead = numberToRead - self.currentChunk.length(); // Load the next chunk and read some more self.nthChunk(self.currentChunk.chunkNumber + 1, function(err, chunk) { self.currentChunk = chunk; self.read(length, finalBuffer, callback); }); } }; GridStore.prototype.tell = function(callback) { callback(null, this.position); }; GridStore.prototype.seek = function(position, seekLocation, callback) { var self = this; var args = Array.prototype.slice.call(arguments, 1); callback = args.pop(); seekLocation = args.length ? args.shift() : null; var seekLocationFinal = seekLocation == null ? exports.GridStore.IO_SEEK_SET : seekLocation; var finalPosition = position; var targetPosition = 0; if(seekLocationFinal == exports.GridStore.IO_SEEK_CUR) { targetPosition = self.position + finalPosition; } else if(seekLocationFinal == exports.GridStore.IO_SEEK_END) { targetPosition = self.length + finalPosition; } else { targetPosition = finalPosition; } var newChunkNumber = Integer.fromNumber((targetPosition/self.chunkSize)).toInt(); if(newChunkNumber != self.currentChunk.chunkNumber) { if(self.mode[0] == 'w') { self.currentChunk.save(function(err, chunk) { self.nthChunk(newChunkNumber, function(err, chunk) { self.currentChunk = chunk; self.position = targetPosition; self.currentChunk.position = (self.position % self.chunkSize); callback(null, self); }); }); } } else { self.position = targetPosition; self.currentChunk.position = (self.position % self.chunkSize); callback(null, self); } }; GridStore.prototype.eof = function() { return this.position == this.length ? true : false; }; GridStore.prototype.getc = function(callback) { var self = this; if(self.eof()) { callback(null, null); } else if(self.currentChunk.eof()) { self.nthChunk(self.currentChunk.chunkNumber + 1, function(err, chunk) { self.currentChunk = chunk; self.position = self.position + 1; callback(null, self.currentChunk.getc()); }); } else { self.position = self.position + 1; callback(null, self.currentChunk.getc()); } }; GridStore.prototype.puts = function(string, callback) { var finalString = string.match(/\n$/) == null ? string + "\n" : string; this.write(finalString, callback); }; GridStore.DEFAULT_ROOT_COLLECTION = 'fs'; GridStore.DEFAULT_CONTENT_TYPE = 'text/plain'; GridStore.IO_SEEK_SET = 0; GridStore.IO_SEEK_CUR = 1; GridStore.IO_SEEK_END = 2; GridStore.exist = function(db, name, rootCollection, callback) { var args = Array.prototype.slice.call(arguments, 2); callback = args.pop(); rootCollection = args.length ? args.shift() : null; var rootCollectionFinal = rootCollection != null ? rootCollection : GridStore.DEFAULT_ROOT_COLLECTION; db.collection(rootCollectionFinal + ".files", function(err, collection) { collection.find({'filename':name}, function(err, cursor) { cursor.nextObject(function(err, item) { callback(null, item == null ? false : true); }); }); }); }; GridStore.list = function(db, rootCollection, callback) { var args = Array.prototype.slice.call(arguments, 1); callback = args.pop(); rootCollection = args.length ? args.shift() : null; var rootCollectionFinal = rootCollection != null ? rootCollection : GridStore.DEFAULT_ROOT_COLLECTION; var items = []; db.collection((rootCollectionFinal + ".files"), function(err, collection) { collection.find(function(err, cursor) { cursor.each(function(err, item) { if(item != null) { items.push(item.filename); } else { callback(null, items); } }); }); }); }; GridStore.read = function(db, name, length, offset, options, callback) { var args = Array.prototype.slice.call(arguments, 2); callback = args.pop(); length = args.length ? args.shift() : null; offset = args.length ? args.shift() : null; options = args.length ? args.shift() : null; new GridStore(db, name, "r", options).open(function(err, gridStore) { // Make sure we are not reading out of bounds if(offset && offset >= gridStore.length) return callback("offset larger than size of file", null); if(length && length > gridStore.length) return callback("length is larger than the size of the file", null); if(offset && length && (offset + length) > gridStore.length) return callback("offset and length is larger than the size of the file", null); if(offset != null) { gridStore.seek(offset, function(err, gridStore) { gridStore.read(length, function(err, data) { callback(err, data); }); }); } else { gridStore.read(length, function(err, data) { callback(err, data); }); } }); }; GridStore.readlines = function(db, name, separator, options, callback) { var args = Array.prototype.slice.call(arguments, 2); callback = args.pop(); separator = args.length ? args.shift() : null; options = args.length ? args.shift() : null; var finalSeperator = separator == null ? "\n" : separator; new GridStore(db, name, "r", options).open(function(err, gridStore) { gridStore.readlines(finalSeperator, function(err, lines) { callback(err, lines); }); }); }; GridStore.unlink = function(db, names, options, callback) { var self = this; var args = Array.prototype.slice.call(arguments, 2); callback = args.pop(); options = args.length ? args.shift() : null; if(names.constructor == Array) { for(var i = 0; i < names.length; i++) { self.unlink(function(result) { if(i == (names.length - 1)) callback(null, self); }, db, names[i]); } } else { new GridStore(db, names, "w", options).open(function(err, gridStore) { gridStore.deleteChunks(function(err, result) { gridStore.collection(function(err, collection) { collection.remove({'_id':gridStore.fileId}, function(err, collection) { callback(err, self); }); }); }); }); } };