123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- var path = Npm.require('path');
- var mongodb = Npm.require('mongodb');
- var ObjectID = Npm.require('mongodb').ObjectID;
- var Grid = Npm.require('gridfs-stream');
- //var Grid = Npm.require('gridfs-locking-stream');
- var chunkSize = 1024*1024*2; // 256k is default GridFS chunk size, but performs terribly for largish files
- /**
- * @public
- * @constructor
- * @param {String} name - The store name
- * @param {Object} options
- * @param {Function} [options.beforeSave] - Function to run before saving a file from the server. The context of the function will be the `FS.File` instance we're saving. The function may alter its properties.
- * @param {Number} [options.maxTries=5] - Max times to attempt saving a file
- * @returns {FS.StorageAdapter} An instance of FS.StorageAdapter.
- *
- * Creates a GridFS store instance on the server. Inherits from FS.StorageAdapter
- * type.
- */
- FS.Store.GridFS = function(name, options) {
- var self = this;
- options = options || {};
- var gridfsName = name;
- var mongoOptions = options.mongoOptions || {};
- if (!(self instanceof FS.Store.GridFS))
- throw new Error('FS.Store.GridFS missing keyword "new"');
- if (!options.mongoUrl) {
- options.mongoUrl = process.env.MONGO_URL;
- // When using a Meteor MongoDB instance, preface name with "cfs_gridfs."
- gridfsName = "cfs_gridfs." + name;
- }
- if (!options.mongoOptions) {
- options.mongoOptions = { db: { native_parser: true }, server: { auto_reconnect: true }};
- }
- if (options.chunkSize) {
- chunkSize = options.chunkSize;
- }
- return new FS.StorageAdapter(name, options, {
- typeName: 'storage.gridfs',
- fileKey: function(fileObj) {
- // We should not have to mount the file here - We assume its taken
- // care of - Otherwise we create new files instead of overwriting
- var key = {
- _id: null,
- filename: null
- };
- // If we're passed a fileObj, we retrieve the _id and filename from it.
- if (fileObj) {
- var info = fileObj._getInfo(name, {updateFileRecordFirst: false});
- key._id = info.key || null;
- key.filename = info.name || fileObj.name({updateFileRecordFirst: false}) || (fileObj.collectionName + '-' + fileObj._id);
- }
- // If key._id is null at this point, createWriteStream will let GridFS generate a new ID
- return key;
- },
- createReadStream: function(fileKey, options) {
- options = options || {};
- // Init GridFS
- var gfs = new Grid(self.db, mongodb);
- // Set the default streamning settings
- var settings = {
- _id: new ObjectID(fileKey._id),
- root: gridfsName
- };
- // Check if this should be a partial read
- if (typeof options.start !== 'undefined' && typeof options.end !== 'undefined' ) {
- // Add partial info
- settings.range = {
- startPos: options.start,
- endPos: options.end
- };
- }
- FS.debug && console.log('GRIDFS', settings);
- return gfs.createReadStream(settings);
- },
- createWriteStream: function(fileKey, options) {
- options = options || {};
- // Init GridFS
- var gfs = new Grid(self.db, mongodb);
- var opts = {
- filename: fileKey.filename,
- mode: 'w',
- root: gridfsName,
- chunk_size: options.chunk_size || chunkSize,
- // We allow aliases, metadata and contentType to be passed in via
- // options
- aliases: options.aliases || [],
- metadata: options.metadata || null,
- content_type: options.contentType || 'application/octet-stream'
- };
- if (fileKey._id) {
- opts._id = new ObjectID(fileKey._id);
- }
- var writeStream = gfs.createWriteStream(opts);
- writeStream.on('close', function(file) {
- if (!file) {
- // gridfs-stream will emit "close" without passing a file
- // if there is an error. We can simply exit here because
- // the "error" listener will also be called in this case.
- return;
- }
- if (FS.debug) console.log('SA GridFS - DONE!');
- // Emit end and return the fileKey, size, and updated date
- writeStream.emit('stored', {
- // Set the generated _id so that we know it for future reads and writes.
- // We store the _id as a string and only convert to ObjectID right before
- // reading, writing, or deleting. If we store the ObjectID itself,
- // Meteor (EJSON?) seems to convert it to a LocalCollection.ObjectID,
- // which GFS doesn't understand.
- fileKey: file._id.toString(),
- size: file.length,
- storedAt: file.uploadDate || new Date()
- });
- });
- writeStream.on('error', function(error) {
- console.log('SA GridFS - ERROR!', error);
- });
- return writeStream;
- },
- remove: function(fileKey, callback) {
- // Init GridFS
- var gfs = new Grid(self.db, mongodb);
- try {
- gfs.remove({ _id: new ObjectID(fileKey._id), root: gridfsName }, callback);
- } catch(err) {
- callback(err);
- }
- },
- // Not implemented
- watch: function() {
- throw new Error("GridFS storage adapter does not support the sync option");
- },
- init: function(callback) {
- mongodb.MongoClient.connect(options.mongoUrl, mongoOptions, function (err, db) {
- if (err) { return callback(err); }
- self.db = db;
-
- // ensure that indexes are added as otherwise CollectionFS fails for Mongo >= 3.0
- var collection = new Mongo.Collection(gridfsName);
- collection.rawCollection().ensureIndex({ "files_id": 1, "n": 1});
-
- callback(null);
- });
- }
- });
- };
|