gridfs.server.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. var path = Npm.require('path');
  2. var mongodb = Npm.require('mongodb');
  3. var ObjectID = Npm.require('mongodb').ObjectID;
  4. var Grid = Npm.require('gridfs-stream');
  5. //var Grid = Npm.require('gridfs-locking-stream');
  6. var chunkSize = 1024*1024*2; // 256k is default GridFS chunk size, but performs terribly for largish files
  7. /**
  8. * @public
  9. * @constructor
  10. * @param {String} name - The store name
  11. * @param {Object} options
  12. * @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.
  13. * @param {Number} [options.maxTries=5] - Max times to attempt saving a file
  14. * @returns {FS.StorageAdapter} An instance of FS.StorageAdapter.
  15. *
  16. * Creates a GridFS store instance on the server. Inherits from FS.StorageAdapter
  17. * type.
  18. */
  19. FS.Store.GridFS = function(name, options) {
  20. var self = this;
  21. options = options || {};
  22. var gridfsName = name;
  23. var mongoOptions = options.mongoOptions || {};
  24. if (!(self instanceof FS.Store.GridFS))
  25. throw new Error('FS.Store.GridFS missing keyword "new"');
  26. if (!options.mongoUrl) {
  27. options.mongoUrl = process.env.MONGO_URL;
  28. // When using a Meteor MongoDB instance, preface name with "cfs_gridfs."
  29. gridfsName = "cfs_gridfs." + name;
  30. }
  31. if (!options.mongoOptions) {
  32. options.mongoOptions = { db: { native_parser: true }, server: { auto_reconnect: true }};
  33. }
  34. if (options.chunkSize) {
  35. chunkSize = options.chunkSize;
  36. }
  37. return new FS.StorageAdapter(name, options, {
  38. typeName: 'storage.gridfs',
  39. fileKey: function(fileObj) {
  40. // We should not have to mount the file here - We assume its taken
  41. // care of - Otherwise we create new files instead of overwriting
  42. var key = {
  43. _id: null,
  44. filename: null
  45. };
  46. // If we're passed a fileObj, we retrieve the _id and filename from it.
  47. if (fileObj) {
  48. var info = fileObj._getInfo(name, {updateFileRecordFirst: false});
  49. key._id = info.key || null;
  50. key.filename = info.name || fileObj.name({updateFileRecordFirst: false}) || (fileObj.collectionName + '-' + fileObj._id);
  51. }
  52. // If key._id is null at this point, createWriteStream will let GridFS generate a new ID
  53. return key;
  54. },
  55. createReadStream: function(fileKey, options) {
  56. options = options || {};
  57. // Init GridFS
  58. var gfs = new Grid(self.db, mongodb);
  59. // Set the default streamning settings
  60. var settings = {
  61. _id: new ObjectID(fileKey._id),
  62. root: gridfsName
  63. };
  64. // Check if this should be a partial read
  65. if (typeof options.start !== 'undefined' && typeof options.end !== 'undefined' ) {
  66. // Add partial info
  67. settings.range = {
  68. startPos: options.start,
  69. endPos: options.end
  70. };
  71. }
  72. FS.debug && console.log('GRIDFS', settings);
  73. return gfs.createReadStream(settings);
  74. },
  75. createWriteStream: function(fileKey, options) {
  76. options = options || {};
  77. // Init GridFS
  78. var gfs = new Grid(self.db, mongodb);
  79. var opts = {
  80. filename: fileKey.filename,
  81. mode: 'w',
  82. root: gridfsName,
  83. chunk_size: options.chunk_size || chunkSize,
  84. // We allow aliases, metadata and contentType to be passed in via
  85. // options
  86. aliases: options.aliases || [],
  87. metadata: options.metadata || null,
  88. content_type: options.contentType || 'application/octet-stream'
  89. };
  90. if (fileKey._id) {
  91. opts._id = new ObjectID(fileKey._id);
  92. }
  93. var writeStream = gfs.createWriteStream(opts);
  94. writeStream.on('close', function(file) {
  95. if (!file) {
  96. // gridfs-stream will emit "close" without passing a file
  97. // if there is an error. We can simply exit here because
  98. // the "error" listener will also be called in this case.
  99. return;
  100. }
  101. if (FS.debug) console.log('SA GridFS - DONE!');
  102. // Emit end and return the fileKey, size, and updated date
  103. writeStream.emit('stored', {
  104. // Set the generated _id so that we know it for future reads and writes.
  105. // We store the _id as a string and only convert to ObjectID right before
  106. // reading, writing, or deleting. If we store the ObjectID itself,
  107. // Meteor (EJSON?) seems to convert it to a LocalCollection.ObjectID,
  108. // which GFS doesn't understand.
  109. fileKey: file._id.toString(),
  110. size: file.length,
  111. storedAt: file.uploadDate || new Date()
  112. });
  113. });
  114. writeStream.on('error', function(error) {
  115. console.log('SA GridFS - ERROR!', error);
  116. });
  117. return writeStream;
  118. },
  119. remove: function(fileKey, callback) {
  120. // Init GridFS
  121. var gfs = new Grid(self.db, mongodb);
  122. try {
  123. gfs.remove({ _id: new ObjectID(fileKey._id), root: gridfsName }, callback);
  124. } catch(err) {
  125. callback(err);
  126. }
  127. },
  128. // Not implemented
  129. watch: function() {
  130. throw new Error("GridFS storage adapter does not support the sync option");
  131. },
  132. init: function(callback) {
  133. mongodb.MongoClient.connect(options.mongoUrl, mongoOptions, function (err, db) {
  134. if (err) { return callback(err); }
  135. self.db = db;
  136. // ensure that indexes are added as otherwise CollectionFS fails for Mongo >= 3.0
  137. var collection = new Mongo.Collection(gridfsName);
  138. collection.rawCollection().ensureIndex({ "files_id": 1, "n": 1});
  139. callback(null);
  140. });
  141. }
  142. });
  143. };