index.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. 'use strict';
  2. var Emitter = require('events').EventEmitter;
  3. var GridFSBucketReadStream = require('./download');
  4. var GridFSBucketWriteStream = require('./upload');
  5. var shallowClone = require('../utils').shallowClone;
  6. var toError = require('../utils').toError;
  7. var util = require('util');
  8. var executeOperation = require('../utils').executeOperation;
  9. var DEFAULT_GRIDFS_BUCKET_OPTIONS = {
  10. bucketName: 'fs',
  11. chunkSizeBytes: 255 * 1024
  12. };
  13. module.exports = GridFSBucket;
  14. /**
  15. * Constructor for a streaming GridFS interface
  16. * @class
  17. * @param {Db} db A db handle
  18. * @param {object} [options] Optional settings.
  19. * @param {string} [options.bucketName="fs"] The 'files' and 'chunks' collections will be prefixed with the bucket name followed by a dot.
  20. * @param {number} [options.chunkSizeBytes=255 * 1024] Number of bytes stored in each chunk. Defaults to 255KB
  21. * @param {object} [options.writeConcern] Optional write concern to be passed to write operations, for instance `{ w: 1 }`
  22. * @param {object} [options.readPreference] Optional read preference to be passed to read operations
  23. * @fires GridFSBucketWriteStream#index
  24. * @return {GridFSBucket}
  25. */
  26. function GridFSBucket(db, options) {
  27. Emitter.apply(this);
  28. this.setMaxListeners(0);
  29. if (options && typeof options === 'object') {
  30. options = shallowClone(options);
  31. var keys = Object.keys(DEFAULT_GRIDFS_BUCKET_OPTIONS);
  32. for (var i = 0; i < keys.length; ++i) {
  33. if (!options[keys[i]]) {
  34. options[keys[i]] = DEFAULT_GRIDFS_BUCKET_OPTIONS[keys[i]];
  35. }
  36. }
  37. } else {
  38. options = DEFAULT_GRIDFS_BUCKET_OPTIONS;
  39. }
  40. this.s = {
  41. db: db,
  42. options: options,
  43. _chunksCollection: db.collection(options.bucketName + '.chunks'),
  44. _filesCollection: db.collection(options.bucketName + '.files'),
  45. checkedIndexes: false,
  46. calledOpenUploadStream: false,
  47. promiseLibrary: db.s.promiseLibrary || Promise
  48. };
  49. }
  50. util.inherits(GridFSBucket, Emitter);
  51. /**
  52. * When the first call to openUploadStream is made, the upload stream will
  53. * check to see if it needs to create the proper indexes on the chunks and
  54. * files collections. This event is fired either when 1) it determines that
  55. * no index creation is necessary, 2) when it successfully creates the
  56. * necessary indexes.
  57. *
  58. * @event GridFSBucket#index
  59. * @type {Error}
  60. */
  61. /**
  62. * Returns a writable stream (GridFSBucketWriteStream) for writing
  63. * buffers to GridFS. The stream's 'id' property contains the resulting
  64. * file's id.
  65. * @method
  66. * @param {string} filename The value of the 'filename' key in the files doc
  67. * @param {object} [options] Optional settings.
  68. * @param {number} [options.chunkSizeBytes] Optional overwrite this bucket's chunkSizeBytes for this file
  69. * @param {object} [options.metadata] Optional object to store in the file document's `metadata` field
  70. * @param {string} [options.contentType] Optional string to store in the file document's `contentType` field
  71. * @param {array} [options.aliases] Optional array of strings to store in the file document's `aliases` field
  72. * @param {boolean} [options.disableMD5=false] If true, disables adding an md5 field to file data
  73. * @return {GridFSBucketWriteStream}
  74. */
  75. GridFSBucket.prototype.openUploadStream = function(filename, options) {
  76. if (options) {
  77. options = shallowClone(options);
  78. } else {
  79. options = {};
  80. }
  81. if (!options.chunkSizeBytes) {
  82. options.chunkSizeBytes = this.s.options.chunkSizeBytes;
  83. }
  84. return new GridFSBucketWriteStream(this, filename, options);
  85. };
  86. /**
  87. * Returns a writable stream (GridFSBucketWriteStream) for writing
  88. * buffers to GridFS for a custom file id. The stream's 'id' property contains the resulting
  89. * file's id.
  90. * @method
  91. * @param {string|number|object} id A custom id used to identify the file
  92. * @param {string} filename The value of the 'filename' key in the files doc
  93. * @param {object} [options] Optional settings.
  94. * @param {number} [options.chunkSizeBytes] Optional overwrite this bucket's chunkSizeBytes for this file
  95. * @param {object} [options.metadata] Optional object to store in the file document's `metadata` field
  96. * @param {string} [options.contentType] Optional string to store in the file document's `contentType` field
  97. * @param {array} [options.aliases] Optional array of strings to store in the file document's `aliases` field
  98. * @param {boolean} [options.disableMD5=false] If true, disables adding an md5 field to file data
  99. * @return {GridFSBucketWriteStream}
  100. */
  101. GridFSBucket.prototype.openUploadStreamWithId = function(id, filename, options) {
  102. if (options) {
  103. options = shallowClone(options);
  104. } else {
  105. options = {};
  106. }
  107. if (!options.chunkSizeBytes) {
  108. options.chunkSizeBytes = this.s.options.chunkSizeBytes;
  109. }
  110. options.id = id;
  111. return new GridFSBucketWriteStream(this, filename, options);
  112. };
  113. /**
  114. * Returns a readable stream (GridFSBucketReadStream) for streaming file
  115. * data from GridFS.
  116. * @method
  117. * @param {ObjectId} id The id of the file doc
  118. * @param {Object} [options] Optional settings.
  119. * @param {Number} [options.start] Optional 0-based offset in bytes to start streaming from
  120. * @param {Number} [options.end] Optional 0-based offset in bytes to stop streaming before
  121. * @return {GridFSBucketReadStream}
  122. */
  123. GridFSBucket.prototype.openDownloadStream = function(id, options) {
  124. var filter = { _id: id };
  125. options = {
  126. start: options && options.start,
  127. end: options && options.end
  128. };
  129. return new GridFSBucketReadStream(
  130. this.s._chunksCollection,
  131. this.s._filesCollection,
  132. this.s.options.readPreference,
  133. filter,
  134. options
  135. );
  136. };
  137. /**
  138. * Deletes a file with the given id
  139. * @method
  140. * @param {ObjectId} id The id of the file doc
  141. * @param {GridFSBucket~errorCallback} [callback]
  142. */
  143. GridFSBucket.prototype.delete = function(id, callback) {
  144. return executeOperation(this.s.db.s.topology, _delete, [this, id, callback], {
  145. skipSessions: true
  146. });
  147. };
  148. /**
  149. * @ignore
  150. */
  151. function _delete(_this, id, callback) {
  152. _this.s._filesCollection.deleteOne({ _id: id }, function(error, res) {
  153. if (error) {
  154. return callback(error);
  155. }
  156. _this.s._chunksCollection.deleteMany({ files_id: id }, function(error) {
  157. if (error) {
  158. return callback(error);
  159. }
  160. // Delete orphaned chunks before returning FileNotFound
  161. if (!res.result.n) {
  162. var errmsg = 'FileNotFound: no file with id ' + id + ' found';
  163. return callback(new Error(errmsg));
  164. }
  165. callback();
  166. });
  167. });
  168. }
  169. /**
  170. * Convenience wrapper around find on the files collection
  171. * @method
  172. * @param {Object} filter
  173. * @param {Object} [options] Optional settings for cursor
  174. * @param {number} [options.batchSize] Optional batch size for cursor
  175. * @param {number} [options.limit] Optional limit for cursor
  176. * @param {number} [options.maxTimeMS] Optional maxTimeMS for cursor
  177. * @param {boolean} [options.noCursorTimeout] Optionally set cursor's `noCursorTimeout` flag
  178. * @param {number} [options.skip] Optional skip for cursor
  179. * @param {object} [options.sort] Optional sort for cursor
  180. * @return {Cursor}
  181. */
  182. GridFSBucket.prototype.find = function(filter, options) {
  183. filter = filter || {};
  184. options = options || {};
  185. var cursor = this.s._filesCollection.find(filter);
  186. if (options.batchSize != null) {
  187. cursor.batchSize(options.batchSize);
  188. }
  189. if (options.limit != null) {
  190. cursor.limit(options.limit);
  191. }
  192. if (options.maxTimeMS != null) {
  193. cursor.maxTimeMS(options.maxTimeMS);
  194. }
  195. if (options.noCursorTimeout != null) {
  196. cursor.addCursorFlag('noCursorTimeout', options.noCursorTimeout);
  197. }
  198. if (options.skip != null) {
  199. cursor.skip(options.skip);
  200. }
  201. if (options.sort != null) {
  202. cursor.sort(options.sort);
  203. }
  204. return cursor;
  205. };
  206. /**
  207. * Returns a readable stream (GridFSBucketReadStream) for streaming the
  208. * file with the given name from GridFS. If there are multiple files with
  209. * the same name, this will stream the most recent file with the given name
  210. * (as determined by the `uploadDate` field). You can set the `revision`
  211. * option to change this behavior.
  212. * @method
  213. * @param {String} filename The name of the file to stream
  214. * @param {Object} [options] Optional settings
  215. * @param {number} [options.revision=-1] The revision number relative to the oldest file with the given filename. 0 gets you the oldest file, 1 gets you the 2nd oldest, -1 gets you the newest.
  216. * @param {Number} [options.start] Optional 0-based offset in bytes to start streaming from
  217. * @param {Number} [options.end] Optional 0-based offset in bytes to stop streaming before
  218. * @return {GridFSBucketReadStream}
  219. */
  220. GridFSBucket.prototype.openDownloadStreamByName = function(filename, options) {
  221. var sort = { uploadDate: -1 };
  222. var skip = null;
  223. if (options && options.revision != null) {
  224. if (options.revision >= 0) {
  225. sort = { uploadDate: 1 };
  226. skip = options.revision;
  227. } else {
  228. skip = -options.revision - 1;
  229. }
  230. }
  231. var filter = { filename: filename };
  232. options = {
  233. sort: sort,
  234. skip: skip,
  235. start: options && options.start,
  236. end: options && options.end
  237. };
  238. return new GridFSBucketReadStream(
  239. this.s._chunksCollection,
  240. this.s._filesCollection,
  241. this.s.options.readPreference,
  242. filter,
  243. options
  244. );
  245. };
  246. /**
  247. * Renames the file with the given _id to the given string
  248. * @method
  249. * @param {ObjectId} id the id of the file to rename
  250. * @param {String} filename new name for the file
  251. * @param {GridFSBucket~errorCallback} [callback]
  252. */
  253. GridFSBucket.prototype.rename = function(id, filename, callback) {
  254. return executeOperation(this.s.db.s.topology, _rename, [this, id, filename, callback], {
  255. skipSessions: true
  256. });
  257. };
  258. /**
  259. * @ignore
  260. */
  261. function _rename(_this, id, filename, callback) {
  262. var filter = { _id: id };
  263. var update = { $set: { filename: filename } };
  264. _this.s._filesCollection.updateOne(filter, update, function(error, res) {
  265. if (error) {
  266. return callback(error);
  267. }
  268. if (!res.result.n) {
  269. return callback(toError('File with id ' + id + ' not found'));
  270. }
  271. callback();
  272. });
  273. }
  274. /**
  275. * Removes this bucket's files collection, followed by its chunks collection.
  276. * @method
  277. * @param {GridFSBucket~errorCallback} [callback]
  278. */
  279. GridFSBucket.prototype.drop = function(callback) {
  280. return executeOperation(this.s.db.s.topology, _drop, [this, callback], {
  281. skipSessions: true
  282. });
  283. };
  284. /**
  285. * Return the db logger
  286. * @method
  287. * @return {Logger} return the db logger
  288. * @ignore
  289. */
  290. GridFSBucket.prototype.getLogger = function() {
  291. return this.s.db.s.logger;
  292. };
  293. /**
  294. * @ignore
  295. */
  296. function _drop(_this, callback) {
  297. _this.s._filesCollection.drop(function(error) {
  298. if (error) {
  299. return callback(error);
  300. }
  301. _this.s._chunksCollection.drop(function(error) {
  302. if (error) {
  303. return callback(error);
  304. }
  305. return callback();
  306. });
  307. });
  308. }
  309. /**
  310. * Callback format for all GridFSBucket methods that can accept a callback.
  311. * @callback GridFSBucket~errorCallback
  312. * @param {MongoError} error An error instance representing any errors that occurred
  313. */