Source: lib/gridfs/grid_store.js

  1. 'use strict';
  2. /**
  3. * @fileOverview GridFS is a tool for MongoDB to store files to the database.
  4. * Because of the restrictions of the object size the database can hold, a
  5. * facility to split a file into several chunks is needed. The {@link GridStore}
  6. * class offers a simplified api to interact with files while managing the
  7. * chunks of split files behind the scenes. More information about GridFS can be
  8. * found <a href="http://www.mongodb.org/display/DOCS/GridFS">here</a>.
  9. *
  10. * @example
  11. * const MongoClient = require('mongodb').MongoClient;
  12. * const GridStore = require('mongodb').GridStore;
  13. * const ObjectID = require('mongodb').ObjectID;
  14. * const test = require('assert');
  15. * // Connection url
  16. * const url = 'mongodb://localhost:27017';
  17. * // Database Name
  18. * const dbName = 'test';
  19. * // Connect using MongoClient
  20. * MongoClient.connect(url, function(err, client) {
  21. * const db = client.db(dbName);
  22. * const gridStore = new GridStore(db, null, "w");
  23. * gridStore.open(function(err, gridStore) {
  24. * gridStore.write("hello world!", function(err, gridStore) {
  25. * gridStore.close(function(err, result) {
  26. * // Let's read the file using object Id
  27. * GridStore.read(db, result._id, function(err, data) {
  28. * test.equal('hello world!', data);
  29. * client.close();
  30. * test.done();
  31. * });
  32. * });
  33. * });
  34. * });
  35. * });
  36. */
  37. const Chunk = require('./chunk');
  38. const ObjectID = require('../core').BSON.ObjectID;
  39. const ReadPreference = require('../core').ReadPreference;
  40. const Buffer = require('safe-buffer').Buffer;
  41. const fs = require('fs');
  42. const f = require('util').format;
  43. const util = require('util');
  44. const MongoError = require('../core').MongoError;
  45. const inherits = util.inherits;
  46. const Duplex = require('stream').Duplex;
  47. const shallowClone = require('../utils').shallowClone;
  48. const executeLegacyOperation = require('../utils').executeLegacyOperation;
  49. const deprecate = require('util').deprecate;
  50. var REFERENCE_BY_FILENAME = 0,
  51. REFERENCE_BY_ID = 1;
  52. const deprecationFn = deprecate(() => {},
  53. 'GridStore is deprecated, and will be removed in a future version. Please use GridFSBucket instead');
  54. /**
  55. * Namespace provided by the core module
  56. * @external Duplex
  57. */
  58. /**
  59. * Create a new GridStore instance
  60. *
  61. * Modes
  62. * - **"r"** - read only. This is the default mode.
  63. * - **"w"** - write in truncate mode. Existing data will be overwritten.
  64. *
  65. * @class
  66. * @param {Db} db A database instance to interact with.
  67. * @param {object} [id] optional unique id for this file
  68. * @param {string} [filename] optional filename for this file, no unique constrain on the field
  69. * @param {string} mode set the mode for this file.
  70. * @param {object} [options] Optional settings.
  71. * @param {(number|string)} [options.w] The write concern.
  72. * @param {number} [options.wtimeout] The write concern timeout.
  73. * @param {boolean} [options.j=false] Specify a journal write concern.
  74. * @param {boolean} [options.fsync=false] Specify a file sync write concern.
  75. * @param {string} [options.root] Root collection to use. Defaults to **{GridStore.DEFAULT_ROOT_COLLECTION}**.
  76. * @param {string} [options.content_type] MIME type of the file. Defaults to **{GridStore.DEFAULT_CONTENT_TYPE}**.
  77. * @param {number} [options.chunk_size=261120] Size for the chunk. Defaults to **{Chunk.DEFAULT_CHUNK_SIZE}**.
  78. * @param {object} [options.metadata] Arbitrary data the user wants to store.
  79. * @param {object} [options.promiseLibrary] A Promise library class the application wishes to use such as Bluebird, must be ES6 compatible
  80. * @param {(ReadPreference|string)} [options.readPreference] The preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST).
  81. * @property {number} chunkSize Get the gridstore chunk size.
  82. * @property {number} md5 The md5 checksum for this file.
  83. * @property {number} chunkNumber The current chunk number the gridstore has materialized into memory
  84. * @return {GridStore} a GridStore instance.
  85. * @deprecated Use GridFSBucket API instead
  86. */
  87. var GridStore = function GridStore(db, id, filename, mode, options) {
  88. deprecationFn();
  89. if (!(this instanceof GridStore)) return new GridStore(db, id, filename, mode, options);
  90. this.db = db;
  91. // Handle options
  92. if (typeof options === 'undefined') options = {};
  93. // Handle mode
  94. if (typeof mode === 'undefined') {
  95. mode = filename;
  96. filename = undefined;
  97. } else if (typeof mode === 'object') {
  98. options = mode;
  99. mode = filename;
  100. filename = undefined;
  101. }
  102. if (id && id._bsontype === 'ObjectID') {
  103. this.referenceBy = REFERENCE_BY_ID;
  104. this.fileId = id;
  105. this.filename = filename;
  106. } else if (typeof filename === 'undefined') {
  107. this.referenceBy = REFERENCE_BY_FILENAME;
  108. this.filename = id;
  109. if (mode.indexOf('w') != null) {
  110. this.fileId = new ObjectID();
  111. }
  112. } else {
  113. this.referenceBy = REFERENCE_BY_ID;
  114. this.fileId = id;
  115. this.filename = filename;
  116. }
  117. // Set up the rest
  118. this.mode = mode == null ? 'r' : mode;
  119. this.options = options || {};
  120. // Opened
  121. this.isOpen = false;
  122. // Set the root if overridden
  123. this.root =
  124. this.options['root'] == null ? GridStore.DEFAULT_ROOT_COLLECTION : this.options['root'];
  125. this.position = 0;
  126. this.readPreference =
  127. this.options.readPreference || db.options.readPreference || ReadPreference.primary;
  128. this.writeConcern = _getWriteConcern(db, this.options);
  129. // Set default chunk size
  130. this.internalChunkSize =
  131. this.options['chunkSize'] == null ? Chunk.DEFAULT_CHUNK_SIZE : this.options['chunkSize'];
  132. // Get the promiseLibrary
  133. var promiseLibrary = this.options.promiseLibrary || Promise;
  134. // Set the promiseLibrary
  135. this.promiseLibrary = promiseLibrary;
  136. Object.defineProperty(this, 'chunkSize', {
  137. enumerable: true,
  138. get: function() {
  139. return this.internalChunkSize;
  140. },
  141. set: function(value) {
  142. if (!(this.mode[0] === 'w' && this.position === 0 && this.uploadDate == null)) {
  143. this.internalChunkSize = this.internalChunkSize;
  144. } else {
  145. this.internalChunkSize = value;
  146. }
  147. }
  148. });
  149. Object.defineProperty(this, 'md5', {
  150. enumerable: true,
  151. get: function() {
  152. return this.internalMd5;
  153. }
  154. });
  155. Object.defineProperty(this, 'chunkNumber', {
  156. enumerable: true,
  157. get: function() {
  158. return this.currentChunk && this.currentChunk.chunkNumber
  159. ? this.currentChunk.chunkNumber
  160. : null;
  161. }
  162. });
  163. };
  164. /**
  165. * The callback format for the Gridstore.open method
  166. * @callback GridStore~openCallback
  167. * @param {MongoError} error An error instance representing the error during the execution.
  168. * @param {GridStore} gridStore The GridStore instance if the open method was successful.
  169. */
  170. /**
  171. * Opens the file from the database and initialize this object. Also creates a
  172. * new one if file does not exist.
  173. *
  174. * @method
  175. * @param {object} [options] Optional settings
  176. * @param {ClientSession} [options.session] optional session to use for this operation
  177. * @param {GridStore~openCallback} [callback] this will be called after executing this method
  178. * @return {Promise} returns Promise if no callback passed
  179. * @deprecated Use GridFSBucket API instead
  180. */
  181. GridStore.prototype.open = function(options, callback) {
  182. if (typeof options === 'function') (callback = options), (options = {});
  183. options = options || {};
  184. if (this.mode !== 'w' && this.mode !== 'w+' && this.mode !== 'r') {
  185. throw MongoError.create({ message: 'Illegal mode ' + this.mode, driver: true });
  186. }
  187. return executeLegacyOperation(this.db.s.topology, open, [this, options, callback], {
  188. skipSessions: true
  189. });
  190. };
  191. var open = function(self, options, callback) {
  192. // Get the write concern
  193. var writeConcern = _getWriteConcern(self.db, self.options);
  194. // If we are writing we need to ensure we have the right indexes for md5's
  195. if (self.mode === 'w' || self.mode === 'w+') {
  196. // Get files collection
  197. var collection = self.collection();
  198. // Put index on filename
  199. collection.ensureIndex([['filename', 1]], writeConcern, function() {
  200. // Get chunk collection
  201. var chunkCollection = self.chunkCollection();
  202. // Make an unique index for compatibility with mongo-cxx-driver:legacy
  203. var chunkIndexOptions = shallowClone(writeConcern);
  204. chunkIndexOptions.unique = true;
  205. // Ensure index on chunk collection
  206. chunkCollection.ensureIndex(
  207. [
  208. ['files_id', 1],
  209. ['n', 1]
  210. ],
  211. chunkIndexOptions,
  212. function() {
  213. // Open the connection
  214. _open(self, writeConcern, function(err, r) {
  215. if (err) return callback(err);
  216. self.isOpen = true;
  217. callback(err, r);
  218. });
  219. }
  220. );
  221. });
  222. } else {
  223. // Open the gridstore
  224. _open(self, writeConcern, function(err, r) {
  225. if (err) return callback(err);
  226. self.isOpen = true;
  227. callback(err, r);
  228. });
  229. }
  230. };
  231. /**
  232. * Verify if the file is at EOF.
  233. *
  234. * @method
  235. * @return {boolean} true if the read/write head is at the end of this file.
  236. * @deprecated Use GridFSBucket API instead
  237. */
  238. GridStore.prototype.eof = function() {
  239. return this.position === this.length ? true : false;
  240. };
  241. /**
  242. * The callback result format.
  243. * @callback GridStore~resultCallback
  244. * @param {object} [options] Optional settings
  245. * @param {ClientSession} [options.session] optional session to use for this operation
  246. * @param {MongoError} error An error instance representing the error during the execution.
  247. * @param {object} result The result from the callback.
  248. */
  249. /**
  250. * Retrieves a single character from this file.
  251. *
  252. * @method
  253. * @param {GridStore~resultCallback} [callback] this gets called after this method is executed. Passes null to the first parameter and the character read to the second or null to the second if the read/write head is at the end of the file.
  254. * @return {Promise} returns Promise if no callback passed
  255. * @deprecated Use GridFSBucket API instead
  256. */
  257. GridStore.prototype.getc = function(options, callback) {
  258. if (typeof options === 'function') (callback = options), (options = {});
  259. options = options || {};
  260. return executeLegacyOperation(this.db.s.topology, getc, [this, options, callback], {
  261. skipSessions: true
  262. });
  263. };
  264. var getc = function(self, options, callback) {
  265. if (self.eof()) {
  266. callback(null, null);
  267. } else if (self.currentChunk.eof()) {
  268. nthChunk(self, self.currentChunk.chunkNumber + 1, function(err, chunk) {
  269. self.currentChunk = chunk;
  270. self.position = self.position + 1;
  271. callback(err, self.currentChunk.getc());
  272. });
  273. } else {
  274. self.position = self.position + 1;
  275. callback(null, self.currentChunk.getc());
  276. }
  277. };
  278. /**
  279. * Writes a string to the file with a newline character appended at the end if
  280. * the given string does not have one.
  281. *
  282. * @method
  283. * @param {string} string the string to write.
  284. * @param {object} [options] Optional settings
  285. * @param {ClientSession} [options.session] optional session to use for this operation
  286. * @param {GridStore~resultCallback} [callback] this will be called after executing this method. The first parameter will contain null and the second one will contain a reference to this object.
  287. * @return {Promise} returns Promise if no callback passed
  288. * @deprecated Use GridFSBucket API instead
  289. */
  290. GridStore.prototype.puts = function(string, options, callback) {
  291. if (typeof options === 'function') (callback = options), (options = {});
  292. options = options || {};
  293. var finalString = string.match(/\n$/) == null ? string + '\n' : string;
  294. return executeLegacyOperation(
  295. this.db.s.topology,
  296. this.write.bind(this),
  297. [finalString, options, callback],
  298. { skipSessions: true }
  299. );
  300. };
  301. /**
  302. * Return a modified Readable stream including a possible transform method.
  303. *
  304. * @method
  305. * @return {GridStoreStream}
  306. * @deprecated Use GridFSBucket API instead
  307. */
  308. GridStore.prototype.stream = function() {
  309. return new GridStoreStream(this);
  310. };
  311. /**
  312. * Writes some data. This method will work properly only if initialized with mode "w" or "w+".
  313. *
  314. * @method
  315. * @param {(string|Buffer)} data the data to write.
  316. * @param {boolean} [close] closes this file after writing if set to true.
  317. * @param {object} [options] Optional settings
  318. * @param {ClientSession} [options.session] optional session to use for this operation
  319. * @param {GridStore~resultCallback} [callback] this will be called after executing this method. The first parameter will contain null and the second one will contain a reference to this object.
  320. * @return {Promise} returns Promise if no callback passed
  321. * @deprecated Use GridFSBucket API instead
  322. */
  323. GridStore.prototype.write = function write(data, close, options, callback) {
  324. if (typeof options === 'function') (callback = options), (options = {});
  325. options = options || {};
  326. return executeLegacyOperation(
  327. this.db.s.topology,
  328. _writeNormal,
  329. [this, data, close, options, callback],
  330. { skipSessions: true }
  331. );
  332. };
  333. /**
  334. * Handles the destroy part of a stream
  335. *
  336. * @method
  337. * @result {null}
  338. * @deprecated Use GridFSBucket API instead
  339. */
  340. GridStore.prototype.destroy = function destroy() {
  341. // close and do not emit any more events. queued data is not sent.
  342. if (!this.writable) return;
  343. this.readable = false;
  344. if (this.writable) {
  345. this.writable = false;
  346. this._q.length = 0;
  347. this.emit('close');
  348. }
  349. };
  350. /**
  351. * Stores a file from the file system to the GridFS database.
  352. *
  353. * @method
  354. * @param {(string|Buffer|FileHandle)} file the file to store.
  355. * @param {object} [options] Optional settings
  356. * @param {ClientSession} [options.session] optional session to use for this operation
  357. * @param {GridStore~resultCallback} [callback] this will be called after executing this method. The first parameter will contain null and the second one will contain a reference to this object.
  358. * @return {Promise} returns Promise if no callback passed
  359. * @deprecated Use GridFSBucket API instead
  360. */
  361. GridStore.prototype.writeFile = function(file, options, callback) {
  362. if (typeof options === 'function') (callback = options), (options = {});
  363. options = options || {};
  364. return executeLegacyOperation(this.db.s.topology, writeFile, [this, file, options, callback], {
  365. skipSessions: true
  366. });
  367. };
  368. var writeFile = function(self, file, options, callback) {
  369. if (typeof file === 'string') {
  370. fs.open(file, 'r', function(err, fd) {
  371. if (err) return callback(err);
  372. self.writeFile(fd, callback);
  373. });
  374. return;
  375. }
  376. self.open(function(err, self) {
  377. if (err) return callback(err, self);
  378. fs.fstat(file, function(err, stats) {
  379. if (err) return callback(err, self);
  380. var offset = 0;
  381. var index = 0;
  382. // Write a chunk
  383. var writeChunk = function() {
  384. // Allocate the buffer
  385. var _buffer = Buffer.alloc(self.chunkSize);
  386. // Read the file
  387. fs.read(file, _buffer, 0, _buffer.length, offset, function(err, bytesRead, data) {
  388. if (err) return callback(err, self);
  389. offset = offset + bytesRead;
  390. // Create a new chunk for the data
  391. var chunk = new Chunk(self, { n: index++ }, self.writeConcern);
  392. chunk.write(data.slice(0, bytesRead), function(err, chunk) {
  393. if (err) return callback(err, self);
  394. chunk.save({}, function(err) {
  395. if (err) return callback(err, self);
  396. self.position = self.position + bytesRead;
  397. // Point to current chunk
  398. self.currentChunk = chunk;
  399. if (offset >= stats.size) {
  400. fs.close(file, function(err) {
  401. if (err) return callback(err);
  402. self.close(function(err) {
  403. if (err) return callback(err, self);
  404. return callback(null, self);
  405. });
  406. });
  407. } else {
  408. return process.nextTick(writeChunk);
  409. }
  410. });
  411. });
  412. });
  413. };
  414. // Process the first write
  415. process.nextTick(writeChunk);
  416. });
  417. });
  418. };
  419. /**
  420. * Saves this file to the database. This will overwrite the old entry if it
  421. * already exists. This will work properly only if mode was initialized to
  422. * "w" or "w+".
  423. *
  424. * @method
  425. * @param {object} [options] Optional settings
  426. * @param {ClientSession} [options.session] optional session to use for this operation
  427. * @param {GridStore~resultCallback} [callback] this will be called after executing this method. The first parameter will contain null and the second one will contain a reference to this object.
  428. * @return {Promise} returns Promise if no callback passed
  429. * @deprecated Use GridFSBucket API instead
  430. */
  431. GridStore.prototype.close = function(options, callback) {
  432. if (typeof options === 'function') (callback = options), (options = {});
  433. options = options || {};
  434. return executeLegacyOperation(this.db.s.topology, close, [this, options, callback], {
  435. skipSessions: true
  436. });
  437. };
  438. var close = function(self, options, callback) {
  439. if (self.mode[0] === 'w') {
  440. // Set up options
  441. options = Object.assign({}, self.writeConcern, options);
  442. if (self.currentChunk != null && self.currentChunk.position > 0) {
  443. self.currentChunk.save({}, function(err) {
  444. if (err && typeof callback === 'function') return callback(err);
  445. self.collection(function(err, files) {
  446. if (err && typeof callback === 'function') return callback(err);
  447. // Build the mongo object
  448. if (self.uploadDate != null) {
  449. buildMongoObject(self, function(err, mongoObject) {
  450. if (err) {
  451. if (typeof callback === 'function') return callback(err);
  452. else throw err;
  453. }
  454. files.save(mongoObject, options, function(err) {
  455. if (typeof callback === 'function') callback(err, mongoObject);
  456. });
  457. });
  458. } else {
  459. self.uploadDate = new Date();
  460. buildMongoObject(self, function(err, mongoObject) {
  461. if (err) {
  462. if (typeof callback === 'function') return callback(err);
  463. else throw err;
  464. }
  465. files.save(mongoObject, options, function(err) {
  466. if (typeof callback === 'function') callback(err, mongoObject);
  467. });
  468. });
  469. }
  470. });
  471. });
  472. } else {
  473. self.collection(function(err, files) {
  474. if (err && typeof callback === 'function') return callback(err);
  475. self.uploadDate = new Date();
  476. buildMongoObject(self, function(err, mongoObject) {
  477. if (err) {
  478. if (typeof callback === 'function') return callback(err);
  479. else throw err;
  480. }
  481. files.save(mongoObject, options, function(err) {
  482. if (typeof callback === 'function') callback(err, mongoObject);
  483. });
  484. });
  485. });
  486. }
  487. } else if (self.mode[0] === 'r') {
  488. if (typeof callback === 'function') callback(null, null);
  489. } else {
  490. if (typeof callback === 'function')
  491. callback(MongoError.create({ message: f('Illegal mode %s', self.mode), driver: true }));
  492. }
  493. };
  494. /**
  495. * The collection callback format.
  496. * @callback GridStore~collectionCallback
  497. * @param {MongoError} error An error instance representing the error during the execution.
  498. * @param {Collection} collection The collection from the command execution.
  499. */
  500. /**
  501. * Retrieve this file's chunks collection.
  502. *
  503. * @method
  504. * @param {GridStore~collectionCallback} callback the command callback.
  505. * @return {Collection}
  506. * @deprecated Use GridFSBucket API instead
  507. */
  508. GridStore.prototype.chunkCollection = function(callback) {
  509. if (typeof callback === 'function') return this.db.collection(this.root + '.chunks', callback);
  510. return this.db.collection(this.root + '.chunks');
  511. };
  512. /**
  513. * Deletes all the chunks of this file in the database.
  514. *
  515. * @method
  516. * @param {object} [options] Optional settings
  517. * @param {ClientSession} [options.session] optional session to use for this operation
  518. * @param {GridStore~resultCallback} [callback] the command callback.
  519. * @return {Promise} returns Promise if no callback passed
  520. * @deprecated Use GridFSBucket API instead
  521. */
  522. GridStore.prototype.unlink = function(options, callback) {
  523. if (typeof options === 'function') (callback = options), (options = {});
  524. options = options || {};
  525. return executeLegacyOperation(this.db.s.topology, unlink, [this, options, callback], {
  526. skipSessions: true
  527. });
  528. };
  529. var unlink = function(self, options, callback) {
  530. deleteChunks(self, function(err) {
  531. if (err !== null) {
  532. err.message = 'at deleteChunks: ' + err.message;
  533. return callback(err);
  534. }
  535. self.collection(function(err, collection) {
  536. if (err !== null) {
  537. err.message = 'at collection: ' + err.message;
  538. return callback(err);
  539. }
  540. collection.remove({ _id: self.fileId }, self.writeConcern, function(err) {
  541. callback(err, self);
  542. });
  543. });
  544. });
  545. };
  546. /**
  547. * Retrieves the file collection associated with this object.
  548. *
  549. * @method
  550. * @param {GridStore~collectionCallback} callback the command callback.
  551. * @return {Collection}
  552. * @deprecated Use GridFSBucket API instead
  553. */
  554. GridStore.prototype.collection = function(callback) {
  555. if (typeof callback === 'function') this.db.collection(this.root + '.files', callback);
  556. return this.db.collection(this.root + '.files');
  557. };
  558. /**
  559. * The readlines callback format.
  560. * @callback GridStore~readlinesCallback
  561. * @param {MongoError} error An error instance representing the error during the execution.
  562. * @param {string[]} strings The array of strings returned.
  563. */
  564. /**
  565. * Read the entire file as a list of strings splitting by the provided separator.
  566. *
  567. * @method
  568. * @param {string} [separator] The character to be recognized as the newline separator.
  569. * @param {object} [options] Optional settings
  570. * @param {ClientSession} [options.session] optional session to use for this operation
  571. * @param {GridStore~readlinesCallback} [callback] the command callback.
  572. * @return {Promise} returns Promise if no callback passed
  573. * @deprecated Use GridFSBucket API instead
  574. */
  575. GridStore.prototype.readlines = function(separator, options, callback) {
  576. var args = Array.prototype.slice.call(arguments, 0);
  577. callback = typeof args[args.length - 1] === 'function' ? args.pop() : undefined;
  578. separator = args.length ? args.shift() : '\n';
  579. separator = separator || '\n';
  580. options = args.length ? args.shift() : {};
  581. return executeLegacyOperation(
  582. this.db.s.topology,
  583. readlines,
  584. [this, separator, options, callback],
  585. { skipSessions: true }
  586. );
  587. };
  588. var readlines = function(self, separator, options, callback) {
  589. self.read(function(err, data) {
  590. if (err) return callback(err);
  591. var items = data.toString().split(separator);
  592. items = items.length > 0 ? items.splice(0, items.length - 1) : [];
  593. for (var i = 0; i < items.length; i++) {
  594. items[i] = items[i] + separator;
  595. }
  596. callback(null, items);
  597. });
  598. };
  599. /**
  600. * Deletes all the chunks of this file in the database if mode was set to "w" or
  601. * "w+" and resets the read/write head to the initial position.
  602. *
  603. * @method
  604. * @param {object} [options] Optional settings
  605. * @param {ClientSession} [options.session] optional session to use for this operation
  606. * @param {GridStore~resultCallback} [callback] this will be called after executing this method. The first parameter will contain null and the second one will contain a reference to this object.
  607. * @return {Promise} returns Promise if no callback passed
  608. * @deprecated Use GridFSBucket API instead
  609. */
  610. GridStore.prototype.rewind = function(options, callback) {
  611. if (typeof options === 'function') (callback = options), (options = {});
  612. options = options || {};
  613. return executeLegacyOperation(this.db.s.topology, rewind, [this, options, callback], {
  614. skipSessions: true
  615. });
  616. };
  617. var rewind = function(self, options, callback) {
  618. if (self.currentChunk.chunkNumber !== 0) {
  619. if (self.mode[0] === 'w') {
  620. deleteChunks(self, function(err) {
  621. if (err) return callback(err);
  622. self.currentChunk = new Chunk(self, { n: 0 }, self.writeConcern);
  623. self.position = 0;
  624. callback(null, self);
  625. });
  626. } else {
  627. self.currentChunk(0, function(err, chunk) {
  628. if (err) return callback(err);
  629. self.currentChunk = chunk;
  630. self.currentChunk.rewind();
  631. self.position = 0;
  632. callback(null, self);
  633. });
  634. }
  635. } else {
  636. self.currentChunk.rewind();
  637. self.position = 0;
  638. callback(null, self);
  639. }
  640. };
  641. /**
  642. * The read callback format.
  643. * @callback GridStore~readCallback
  644. * @param {MongoError} error An error instance representing the error during the execution.
  645. * @param {Buffer} data The data read from the GridStore object
  646. */
  647. /**
  648. * Retrieves the contents of this file and advances the read/write head. Works with Buffers only.
  649. *
  650. * There are 3 signatures for this method:
  651. *
  652. * (callback)
  653. * (length, callback)
  654. * (length, buffer, callback)
  655. *
  656. * @method
  657. * @param {number} [length] the number of characters to read. Reads all the characters from the read/write head to the EOF if not specified.
  658. * @param {(string|Buffer)} [buffer] a string to hold temporary data. This is used for storing the string data read so far when recursively calling this method.
  659. * @param {object} [options] Optional settings
  660. * @param {ClientSession} [options.session] optional session to use for this operation
  661. * @param {GridStore~readCallback} [callback] the command callback.
  662. * @return {Promise} returns Promise if no callback passed
  663. * @deprecated Use GridFSBucket API instead
  664. */
  665. GridStore.prototype.read = function(length, buffer, options, callback) {
  666. var args = Array.prototype.slice.call(arguments, 0);
  667. callback = typeof args[args.length - 1] === 'function' ? args.pop() : undefined;
  668. length = args.length ? args.shift() : null;
  669. buffer = args.length ? args.shift() : null;
  670. options = args.length ? args.shift() : {};
  671. return executeLegacyOperation(
  672. this.db.s.topology,
  673. read,
  674. [this, length, buffer, options, callback],
  675. { skipSessions: true }
  676. );
  677. };
  678. var read = function(self, length, buffer, options, callback) {
  679. // The data is a c-terminated string and thus the length - 1
  680. var finalLength = length == null ? self.length - self.position : length;
  681. var finalBuffer = buffer == null ? Buffer.alloc(finalLength) : buffer;
  682. // Add a index to buffer to keep track of writing position or apply current index
  683. finalBuffer._index = buffer != null && buffer._index != null ? buffer._index : 0;
  684. if (self.currentChunk.length() - self.currentChunk.position + finalBuffer._index >= finalLength) {
  685. var slice = self.currentChunk.readSlice(finalLength - finalBuffer._index);
  686. // Copy content to final buffer
  687. slice.copy(finalBuffer, finalBuffer._index);
  688. // Update internal position
  689. self.position = self.position + finalBuffer.length;
  690. // Check if we don't have a file at all
  691. if (finalLength === 0 && finalBuffer.length === 0)
  692. return callback(MongoError.create({ message: 'File does not exist', driver: true }), null);
  693. // Else return data
  694. return callback(null, finalBuffer);
  695. }
  696. // Read the next chunk
  697. slice = self.currentChunk.readSlice(self.currentChunk.length() - self.currentChunk.position);
  698. // Copy content to final buffer
  699. slice.copy(finalBuffer, finalBuffer._index);
  700. // Update index position
  701. finalBuffer._index += slice.length;
  702. // Load next chunk and read more
  703. nthChunk(self, self.currentChunk.chunkNumber + 1, function(err, chunk) {
  704. if (err) return callback(err);
  705. if (chunk.length() > 0) {
  706. self.currentChunk = chunk;
  707. self.read(length, finalBuffer, callback);
  708. } else {
  709. if (finalBuffer._index > 0) {
  710. callback(null, finalBuffer);
  711. } else {
  712. callback(
  713. MongoError.create({
  714. message: 'no chunks found for file, possibly corrupt',
  715. driver: true
  716. }),
  717. null
  718. );
  719. }
  720. }
  721. });
  722. };
  723. /**
  724. * The tell callback format.
  725. * @callback GridStore~tellCallback
  726. * @param {MongoError} error An error instance representing the error during the execution.
  727. * @param {number} position The current read position in the GridStore.
  728. */
  729. /**
  730. * Retrieves the position of the read/write head of this file.
  731. *
  732. * @method
  733. * @param {number} [length] the number of characters to read. Reads all the characters from the read/write head to the EOF if not specified.
  734. * @param {(string|Buffer)} [buffer] a string to hold temporary data. This is used for storing the string data read so far when recursively calling this method.
  735. * @param {object} [options] Optional settings
  736. * @param {ClientSession} [options.session] optional session to use for this operation
  737. * @param {GridStore~tellCallback} [callback] the command callback.
  738. * @return {Promise} returns Promise if no callback passed
  739. * @deprecated Use GridFSBucket API instead
  740. */
  741. GridStore.prototype.tell = function(callback) {
  742. var self = this;
  743. // We provided a callback leg
  744. if (typeof callback === 'function') return callback(null, this.position);
  745. // Return promise
  746. return new self.promiseLibrary(function(resolve) {
  747. resolve(self.position);
  748. });
  749. };
  750. /**
  751. * The tell callback format.
  752. * @callback GridStore~gridStoreCallback
  753. * @param {MongoError} error An error instance representing the error during the execution.
  754. * @param {GridStore} gridStore The gridStore.
  755. */
  756. /**
  757. * Moves the read/write head to a new location.
  758. *
  759. * There are 3 signatures for this method
  760. *
  761. * Seek Location Modes
  762. * - **GridStore.IO_SEEK_SET**, **(default)** set the position from the start of the file.
  763. * - **GridStore.IO_SEEK_CUR**, set the position from the current position in the file.
  764. * - **GridStore.IO_SEEK_END**, set the position from the end of the file.
  765. *
  766. * @method
  767. * @param {number} [position] the position to seek to
  768. * @param {number} [seekLocation] seek mode. Use one of the Seek Location modes.
  769. * @param {object} [options] Optional settings
  770. * @param {ClientSession} [options.session] optional session to use for this operation
  771. * @param {GridStore~gridStoreCallback} [callback] the command callback.
  772. * @return {Promise} returns Promise if no callback passed
  773. * @deprecated Use GridFSBucket API instead
  774. */
  775. GridStore.prototype.seek = function(position, seekLocation, options, callback) {
  776. var args = Array.prototype.slice.call(arguments, 1);
  777. callback = typeof args[args.length - 1] === 'function' ? args.pop() : undefined;
  778. seekLocation = args.length ? args.shift() : null;
  779. options = args.length ? args.shift() : {};
  780. return executeLegacyOperation(
  781. this.db.s.topology,
  782. seek,
  783. [this, position, seekLocation, options, callback],
  784. { skipSessions: true }
  785. );
  786. };
  787. var seek = function(self, position, seekLocation, options, callback) {
  788. // Seek only supports read mode
  789. if (self.mode !== 'r') {
  790. return callback(
  791. MongoError.create({ message: 'seek is only supported for mode r', driver: true })
  792. );
  793. }
  794. var seekLocationFinal = seekLocation == null ? GridStore.IO_SEEK_SET : seekLocation;
  795. var finalPosition = position;
  796. var targetPosition = 0;
  797. // Calculate the position
  798. if (seekLocationFinal === GridStore.IO_SEEK_CUR) {
  799. targetPosition = self.position + finalPosition;
  800. } else if (seekLocationFinal === GridStore.IO_SEEK_END) {
  801. targetPosition = self.length + finalPosition;
  802. } else {
  803. targetPosition = finalPosition;
  804. }
  805. // Get the chunk
  806. var newChunkNumber = Math.floor(targetPosition / self.chunkSize);
  807. var seekChunk = function() {
  808. nthChunk(self, newChunkNumber, function(err, chunk) {
  809. if (err) return callback(err, null);
  810. if (chunk == null) return callback(new Error('no chunk found'));
  811. // Set the current chunk
  812. self.currentChunk = chunk;
  813. self.position = targetPosition;
  814. self.currentChunk.position = self.position % self.chunkSize;
  815. callback(err, self);
  816. });
  817. };
  818. seekChunk();
  819. };
  820. /**
  821. * @ignore
  822. */
  823. var _open = function(self, options, callback) {
  824. var collection = self.collection();
  825. // Create the query
  826. var query =
  827. self.referenceBy === REFERENCE_BY_ID ? { _id: self.fileId } : { filename: self.filename };
  828. query = null == self.fileId && self.filename == null ? null : query;
  829. options.readPreference = self.readPreference;
  830. // Fetch the chunks
  831. if (query != null) {
  832. collection.findOne(query, options, function(err, doc) {
  833. if (err) {
  834. return error(err);
  835. }
  836. // Check if the collection for the files exists otherwise prepare the new one
  837. if (doc != null) {
  838. self.fileId = doc._id;
  839. // Prefer a new filename over the existing one if this is a write
  840. self.filename =
  841. self.mode === 'r' || self.filename === undefined ? doc.filename : self.filename;
  842. self.contentType = doc.contentType;
  843. self.internalChunkSize = doc.chunkSize;
  844. self.uploadDate = doc.uploadDate;
  845. self.aliases = doc.aliases;
  846. self.length = doc.length;
  847. self.metadata = doc.metadata;
  848. self.internalMd5 = doc.md5;
  849. } else if (self.mode !== 'r') {
  850. self.fileId = self.fileId == null ? new ObjectID() : self.fileId;
  851. self.contentType = GridStore.DEFAULT_CONTENT_TYPE;
  852. self.internalChunkSize =
  853. self.internalChunkSize == null ? Chunk.DEFAULT_CHUNK_SIZE : self.internalChunkSize;
  854. self.length = 0;
  855. } else {
  856. self.length = 0;
  857. var txtId = self.fileId._bsontype === 'ObjectID' ? self.fileId.toHexString() : self.fileId;
  858. return error(
  859. MongoError.create({
  860. message: f(
  861. 'file with id %s not opened for writing',
  862. self.referenceBy === REFERENCE_BY_ID ? txtId : self.filename
  863. ),
  864. driver: true
  865. }),
  866. self
  867. );
  868. }
  869. // Process the mode of the object
  870. if (self.mode === 'r') {
  871. nthChunk(self, 0, options, function(err, chunk) {
  872. if (err) return error(err);
  873. self.currentChunk = chunk;
  874. self.position = 0;
  875. callback(null, self);
  876. });
  877. } else if (self.mode === 'w' && doc) {
  878. // Delete any existing chunks
  879. deleteChunks(self, options, function(err) {
  880. if (err) return error(err);
  881. self.currentChunk = new Chunk(self, { n: 0 }, self.writeConcern);
  882. self.contentType =
  883. self.options['content_type'] == null ? self.contentType : self.options['content_type'];
  884. self.internalChunkSize =
  885. self.options['chunk_size'] == null
  886. ? self.internalChunkSize
  887. : self.options['chunk_size'];
  888. self.metadata =
  889. self.options['metadata'] == null ? self.metadata : self.options['metadata'];
  890. self.aliases = self.options['aliases'] == null ? self.aliases : self.options['aliases'];
  891. self.position = 0;
  892. callback(null, self);
  893. });
  894. } else if (self.mode === 'w') {
  895. self.currentChunk = new Chunk(self, { n: 0 }, self.writeConcern);
  896. self.contentType =
  897. self.options['content_type'] == null ? self.contentType : self.options['content_type'];
  898. self.internalChunkSize =
  899. self.options['chunk_size'] == null ? self.internalChunkSize : self.options['chunk_size'];
  900. self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata'];
  901. self.aliases = self.options['aliases'] == null ? self.aliases : self.options['aliases'];
  902. self.position = 0;
  903. callback(null, self);
  904. } else if (self.mode === 'w+') {
  905. nthChunk(self, lastChunkNumber(self), options, function(err, chunk) {
  906. if (err) return error(err);
  907. // Set the current chunk
  908. self.currentChunk = chunk == null ? new Chunk(self, { n: 0 }, self.writeConcern) : chunk;
  909. self.currentChunk.position = self.currentChunk.data.length();
  910. self.metadata =
  911. self.options['metadata'] == null ? self.metadata : self.options['metadata'];
  912. self.aliases = self.options['aliases'] == null ? self.aliases : self.options['aliases'];
  913. self.position = self.length;
  914. callback(null, self);
  915. });
  916. }
  917. });
  918. } else {
  919. // Write only mode
  920. self.fileId = null == self.fileId ? new ObjectID() : self.fileId;
  921. self.contentType = GridStore.DEFAULT_CONTENT_TYPE;
  922. self.internalChunkSize =
  923. self.internalChunkSize == null ? Chunk.DEFAULT_CHUNK_SIZE : self.internalChunkSize;
  924. self.length = 0;
  925. // No file exists set up write mode
  926. if (self.mode === 'w') {
  927. // Delete any existing chunks
  928. deleteChunks(self, options, function(err) {
  929. if (err) return error(err);
  930. self.currentChunk = new Chunk(self, { n: 0 }, self.writeConcern);
  931. self.contentType =
  932. self.options['content_type'] == null ? self.contentType : self.options['content_type'];
  933. self.internalChunkSize =
  934. self.options['chunk_size'] == null ? self.internalChunkSize : self.options['chunk_size'];
  935. self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata'];
  936. self.aliases = self.options['aliases'] == null ? self.aliases : self.options['aliases'];
  937. self.position = 0;
  938. callback(null, self);
  939. });
  940. } else if (self.mode === 'w+') {
  941. nthChunk(self, lastChunkNumber(self), options, function(err, chunk) {
  942. if (err) return error(err);
  943. // Set the current chunk
  944. self.currentChunk = chunk == null ? new Chunk(self, { n: 0 }, self.writeConcern) : chunk;
  945. self.currentChunk.position = self.currentChunk.data.length();
  946. self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata'];
  947. self.aliases = self.options['aliases'] == null ? self.aliases : self.options['aliases'];
  948. self.position = self.length;
  949. callback(null, self);
  950. });
  951. }
  952. }
  953. // only pass error to callback once
  954. function error(err) {
  955. if (error.err) return;
  956. callback((error.err = err));
  957. }
  958. };
  959. /**
  960. * @ignore
  961. */
  962. var writeBuffer = function(self, buffer, close, callback) {
  963. if (typeof close === 'function') {
  964. callback = close;
  965. close = null;
  966. }
  967. var finalClose = typeof close === 'boolean' ? close : false;
  968. if (self.mode !== 'w') {
  969. callback(
  970. MongoError.create({
  971. message: f(
  972. 'file with id %s not opened for writing',
  973. self.referenceBy === REFERENCE_BY_ID ? self.referenceBy : self.filename
  974. ),
  975. driver: true
  976. }),
  977. null
  978. );
  979. } else {
  980. if (self.currentChunk.position + buffer.length >= self.chunkSize) {
  981. // Write out the current Chunk and then keep writing until we have less data left than a chunkSize left
  982. // to a new chunk (recursively)
  983. var previousChunkNumber = self.currentChunk.chunkNumber;
  984. var leftOverDataSize = self.chunkSize - self.currentChunk.position;
  985. var firstChunkData = buffer.slice(0, leftOverDataSize);
  986. var leftOverData = buffer.slice(leftOverDataSize);
  987. // A list of chunks to write out
  988. var chunksToWrite = [self.currentChunk.write(firstChunkData)];
  989. // If we have more data left than the chunk size let's keep writing new chunks
  990. while (leftOverData.length >= self.chunkSize) {
  991. // Create a new chunk and write to it
  992. var newChunk = new Chunk(self, { n: previousChunkNumber + 1 }, self.writeConcern);
  993. firstChunkData = leftOverData.slice(0, self.chunkSize);
  994. leftOverData = leftOverData.slice(self.chunkSize);
  995. // Update chunk number
  996. previousChunkNumber = previousChunkNumber + 1;
  997. // Write data
  998. newChunk.write(firstChunkData);
  999. // Push chunk to save list
  1000. chunksToWrite.push(newChunk);
  1001. }
  1002. // Set current chunk with remaining data
  1003. self.currentChunk = new Chunk(self, { n: previousChunkNumber + 1 }, self.writeConcern);
  1004. // If we have left over data write it
  1005. if (leftOverData.length > 0) self.currentChunk.write(leftOverData);
  1006. // Update the position for the gridstore
  1007. self.position = self.position + buffer.length;
  1008. // Total number of chunks to write
  1009. var numberOfChunksToWrite = chunksToWrite.length;
  1010. for (var i = 0; i < chunksToWrite.length; i++) {
  1011. chunksToWrite[i].save({}, function(err) {
  1012. if (err) return callback(err);
  1013. numberOfChunksToWrite = numberOfChunksToWrite - 1;
  1014. if (numberOfChunksToWrite <= 0) {
  1015. // We care closing the file before returning
  1016. if (finalClose) {
  1017. return self.close(function(err) {
  1018. callback(err, self);
  1019. });
  1020. }
  1021. // Return normally
  1022. return callback(null, self);
  1023. }
  1024. });
  1025. }
  1026. } else {
  1027. // Update the position for the gridstore
  1028. self.position = self.position + buffer.length;
  1029. // We have less data than the chunk size just write it and callback
  1030. self.currentChunk.write(buffer);
  1031. // We care closing the file before returning
  1032. if (finalClose) {
  1033. return self.close(function(err) {
  1034. callback(err, self);
  1035. });
  1036. }
  1037. // Return normally
  1038. return callback(null, self);
  1039. }
  1040. }
  1041. };
  1042. /**
  1043. * Creates a mongoDB object representation of this object.
  1044. *
  1045. * <pre><code>
  1046. * {
  1047. * '_id' : , // {number} id for this file
  1048. * 'filename' : , // {string} name for this file
  1049. * 'contentType' : , // {string} mime type for this file
  1050. * 'length' : , // {number} size of this file?
  1051. * 'chunksize' : , // {number} chunk size used by this file
  1052. * 'uploadDate' : , // {Date}
  1053. * 'aliases' : , // {array of string}
  1054. * 'metadata' : , // {string}
  1055. * }
  1056. * </code></pre>
  1057. *
  1058. * @ignore
  1059. */
  1060. var buildMongoObject = function(self, callback) {
  1061. // Calcuate the length
  1062. var mongoObject = {
  1063. _id: self.fileId,
  1064. filename: self.filename,
  1065. contentType: self.contentType,
  1066. length: self.position ? self.position : 0,
  1067. chunkSize: self.chunkSize,
  1068. uploadDate: self.uploadDate,
  1069. aliases: self.aliases,
  1070. metadata: self.metadata
  1071. };
  1072. var md5Command = { filemd5: self.fileId, root: self.root };
  1073. self.db.command(md5Command, function(err, results) {
  1074. if (err) return callback(err);
  1075. mongoObject.md5 = results.md5;
  1076. callback(null, mongoObject);
  1077. });
  1078. };
  1079. /**
  1080. * Gets the nth chunk of this file.
  1081. * @ignore
  1082. */
  1083. var nthChunk = function(self, chunkNumber, options, callback) {
  1084. if (typeof options === 'function') {
  1085. callback = options;
  1086. options = {};
  1087. }
  1088. options = options || self.writeConcern;
  1089. options.readPreference = self.readPreference;
  1090. // Get the nth chunk
  1091. self
  1092. .chunkCollection()
  1093. .findOne({ files_id: self.fileId, n: chunkNumber }, options, function(err, chunk) {
  1094. if (err) return callback(err);
  1095. var finalChunk = chunk == null ? {} : chunk;
  1096. callback(null, new Chunk(self, finalChunk, self.writeConcern));
  1097. });
  1098. };
  1099. /**
  1100. * @ignore
  1101. */
  1102. var lastChunkNumber = function(self) {
  1103. return Math.floor((self.length ? self.length - 1 : 0) / self.chunkSize);
  1104. };
  1105. /**
  1106. * Deletes all the chunks of this file in the database.
  1107. *
  1108. * @ignore
  1109. */
  1110. var deleteChunks = function(self, options, callback) {
  1111. if (typeof options === 'function') {
  1112. callback = options;
  1113. options = {};
  1114. }
  1115. options = options || self.writeConcern;
  1116. if (self.fileId != null) {
  1117. self.chunkCollection().remove({ files_id: self.fileId }, options, function(err) {
  1118. if (err) return callback(err, false);
  1119. callback(null, true);
  1120. });
  1121. } else {
  1122. callback(null, true);
  1123. }
  1124. };
  1125. /**
  1126. * The collection to be used for holding the files and chunks collection.
  1127. *
  1128. * @classconstant DEFAULT_ROOT_COLLECTION
  1129. */
  1130. GridStore.DEFAULT_ROOT_COLLECTION = 'fs';
  1131. /**
  1132. * Default file mime type
  1133. *
  1134. * @classconstant DEFAULT_CONTENT_TYPE
  1135. */
  1136. GridStore.DEFAULT_CONTENT_TYPE = 'binary/octet-stream';
  1137. /**
  1138. * Seek mode where the given length is absolute.
  1139. *
  1140. * @classconstant IO_SEEK_SET
  1141. */
  1142. GridStore.IO_SEEK_SET = 0;
  1143. /**
  1144. * Seek mode where the given length is an offset to the current read/write head.
  1145. *
  1146. * @classconstant IO_SEEK_CUR
  1147. */
  1148. GridStore.IO_SEEK_CUR = 1;
  1149. /**
  1150. * Seek mode where the given length is an offset to the end of the file.
  1151. *
  1152. * @classconstant IO_SEEK_END
  1153. */
  1154. GridStore.IO_SEEK_END = 2;
  1155. /**
  1156. * Checks if a file exists in the database.
  1157. *
  1158. * @method
  1159. * @static
  1160. * @param {Db} db the database to query.
  1161. * @param {string} name The name of the file to look for.
  1162. * @param {string} [rootCollection] The root collection that holds the files and chunks collection. Defaults to **{GridStore.DEFAULT_ROOT_COLLECTION}**.
  1163. * @param {object} [options] Optional settings.
  1164. * @param {(ReadPreference|string)} [options.readPreference] The preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST).
  1165. * @param {object} [options.promiseLibrary] A Promise library class the application wishes to use such as Bluebird, must be ES6 compatible
  1166. * @param {ClientSession} [options.session] optional session to use for this operation
  1167. * @param {GridStore~resultCallback} [callback] result from exists.
  1168. * @return {Promise} returns Promise if no callback passed
  1169. * @deprecated Use GridFSBucket API instead
  1170. */
  1171. GridStore.exist = function(db, fileIdObject, rootCollection, options, callback) {
  1172. var args = Array.prototype.slice.call(arguments, 2);
  1173. callback = typeof args[args.length - 1] === 'function' ? args.pop() : undefined;
  1174. rootCollection = args.length ? args.shift() : null;
  1175. options = args.length ? args.shift() : {};
  1176. options = options || {};
  1177. return executeLegacyOperation(
  1178. db.s.topology,
  1179. exists,
  1180. [db, fileIdObject, rootCollection, options, callback],
  1181. { skipSessions: true }
  1182. );
  1183. };
  1184. var exists = function(db, fileIdObject, rootCollection, options, callback) {
  1185. // Establish read preference
  1186. var readPreference = options.readPreference || ReadPreference.PRIMARY;
  1187. // Fetch collection
  1188. var rootCollectionFinal =
  1189. rootCollection != null ? rootCollection : GridStore.DEFAULT_ROOT_COLLECTION;
  1190. db.collection(rootCollectionFinal + '.files', function(err, collection) {
  1191. if (err) return callback(err);
  1192. // Build query
  1193. var query =
  1194. typeof fileIdObject === 'string' ||
  1195. Object.prototype.toString.call(fileIdObject) === '[object RegExp]'
  1196. ? { filename: fileIdObject }
  1197. : { _id: fileIdObject }; // Attempt to locate file
  1198. // We have a specific query
  1199. if (
  1200. fileIdObject != null &&
  1201. typeof fileIdObject === 'object' &&
  1202. Object.prototype.toString.call(fileIdObject) !== '[object RegExp]'
  1203. ) {
  1204. query = fileIdObject;
  1205. }
  1206. // Check if the entry exists
  1207. collection.findOne(query, { readPreference: readPreference }, function(err, item) {
  1208. if (err) return callback(err);
  1209. callback(null, item == null ? false : true);
  1210. });
  1211. });
  1212. };
  1213. /**
  1214. * Gets the list of files stored in the GridFS.
  1215. *
  1216. * @method
  1217. * @static
  1218. * @param {Db} db the database to query.
  1219. * @param {string} [rootCollection] The root collection that holds the files and chunks collection. Defaults to **{GridStore.DEFAULT_ROOT_COLLECTION}**.
  1220. * @param {object} [options] Optional settings.
  1221. * @param {(ReadPreference|string)} [options.readPreference] The preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST).
  1222. * @param {object} [options.promiseLibrary] A Promise library class the application wishes to use such as Bluebird, must be ES6 compatible
  1223. * @param {ClientSession} [options.session] optional session to use for this operation
  1224. * @param {GridStore~resultCallback} [callback] result from exists.
  1225. * @return {Promise} returns Promise if no callback passed
  1226. * @deprecated Use GridFSBucket API instead
  1227. */
  1228. GridStore.list = function(db, rootCollection, options, callback) {
  1229. var args = Array.prototype.slice.call(arguments, 1);
  1230. callback = typeof args[args.length - 1] === 'function' ? args.pop() : undefined;
  1231. rootCollection = args.length ? args.shift() : null;
  1232. options = args.length ? args.shift() : {};
  1233. options = options || {};
  1234. return executeLegacyOperation(db.s.topology, list, [db, rootCollection, options, callback], {
  1235. skipSessions: true
  1236. });
  1237. };
  1238. var list = function(db, rootCollection, options, callback) {
  1239. // Ensure we have correct values
  1240. if (rootCollection != null && typeof rootCollection === 'object') {
  1241. options = rootCollection;
  1242. rootCollection = null;
  1243. }
  1244. // Establish read preference
  1245. var readPreference = options.readPreference || ReadPreference.primary;
  1246. // Check if we are returning by id not filename
  1247. var byId = options['id'] != null ? options['id'] : false;
  1248. // Fetch item
  1249. var rootCollectionFinal =
  1250. rootCollection != null ? rootCollection : GridStore.DEFAULT_ROOT_COLLECTION;
  1251. var items = [];
  1252. db.collection(rootCollectionFinal + '.files', function(err, collection) {
  1253. if (err) return callback(err);
  1254. collection.find({}, { readPreference: readPreference }, function(err, cursor) {
  1255. if (err) return callback(err);
  1256. cursor.each(function(err, item) {
  1257. if (item != null) {
  1258. items.push(byId ? item._id : item.filename);
  1259. } else {
  1260. callback(err, items);
  1261. }
  1262. });
  1263. });
  1264. });
  1265. };
  1266. /**
  1267. * Reads the contents of a file.
  1268. *
  1269. * This method has the following signatures
  1270. *
  1271. * (db, name, callback)
  1272. * (db, name, length, callback)
  1273. * (db, name, length, offset, callback)
  1274. * (db, name, length, offset, options, callback)
  1275. *
  1276. * @method
  1277. * @static
  1278. * @param {Db} db the database to query.
  1279. * @param {string} name The name of the file.
  1280. * @param {number} [length] The size of data to read.
  1281. * @param {number} [offset] The offset from the head of the file of which to start reading from.
  1282. * @param {object} [options] Optional settings.
  1283. * @param {(ReadPreference|string)} [options.readPreference] The preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST).
  1284. * @param {object} [options.promiseLibrary] A Promise library class the application wishes to use such as Bluebird, must be ES6 compatible
  1285. * @param {ClientSession} [options.session] optional session to use for this operation
  1286. * @param {GridStore~readCallback} [callback] the command callback.
  1287. * @return {Promise} returns Promise if no callback passed
  1288. * @deprecated Use GridFSBucket API instead
  1289. */
  1290. GridStore.read = function(db, name, length, offset, options, callback) {
  1291. var args = Array.prototype.slice.call(arguments, 2);
  1292. callback = typeof args[args.length - 1] === 'function' ? args.pop() : undefined;
  1293. length = args.length ? args.shift() : null;
  1294. offset = args.length ? args.shift() : null;
  1295. options = args.length ? args.shift() : null;
  1296. options = options || {};
  1297. return executeLegacyOperation(
  1298. db.s.topology,
  1299. readStatic,
  1300. [db, name, length, offset, options, callback],
  1301. { skipSessions: true }
  1302. );
  1303. };
  1304. var readStatic = function(db, name, length, offset, options, callback) {
  1305. new GridStore(db, name, 'r', options).open(function(err, gridStore) {
  1306. if (err) return callback(err);
  1307. // Make sure we are not reading out of bounds
  1308. if (offset && offset >= gridStore.length)
  1309. return callback('offset larger than size of file', null);
  1310. if (length && length > gridStore.length)
  1311. return callback('length is larger than the size of the file', null);
  1312. if (offset && length && offset + length > gridStore.length)
  1313. return callback('offset and length is larger than the size of the file', null);
  1314. if (offset != null) {
  1315. gridStore.seek(offset, function(err, gridStore) {
  1316. if (err) return callback(err);
  1317. gridStore.read(length, callback);
  1318. });
  1319. } else {
  1320. gridStore.read(length, callback);
  1321. }
  1322. });
  1323. };
  1324. /**
  1325. * Read the entire file as a list of strings splitting by the provided separator.
  1326. *
  1327. * @method
  1328. * @static
  1329. * @param {Db} db the database to query.
  1330. * @param {(String|object)} name the name of the file.
  1331. * @param {string} [separator] The character to be recognized as the newline separator.
  1332. * @param {object} [options] Optional settings.
  1333. * @param {(ReadPreference|string)} [options.readPreference] The preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST).
  1334. * @param {object} [options.promiseLibrary] A Promise library class the application wishes to use such as Bluebird, must be ES6 compatible
  1335. * @param {ClientSession} [options.session] optional session to use for this operation
  1336. * @param {GridStore~readlinesCallback} [callback] the command callback.
  1337. * @return {Promise} returns Promise if no callback passed
  1338. * @deprecated Use GridFSBucket API instead
  1339. */
  1340. GridStore.readlines = function(db, name, separator, options, callback) {
  1341. var args = Array.prototype.slice.call(arguments, 2);
  1342. callback = typeof args[args.length - 1] === 'function' ? args.pop() : undefined;
  1343. separator = args.length ? args.shift() : null;
  1344. options = args.length ? args.shift() : null;
  1345. options = options || {};
  1346. return executeLegacyOperation(
  1347. db.s.topology,
  1348. readlinesStatic,
  1349. [db, name, separator, options, callback],
  1350. { skipSessions: true }
  1351. );
  1352. };
  1353. var readlinesStatic = function(db, name, separator, options, callback) {
  1354. var finalSeperator = separator == null ? '\n' : separator;
  1355. new GridStore(db, name, 'r', options).open(function(err, gridStore) {
  1356. if (err) return callback(err);
  1357. gridStore.readlines(finalSeperator, callback);
  1358. });
  1359. };
  1360. /**
  1361. * Deletes the chunks and metadata information of a file from GridFS.
  1362. *
  1363. * @method
  1364. * @static
  1365. * @param {Db} db The database to query.
  1366. * @param {(string|array)} names The name/names of the files to delete.
  1367. * @param {object} [options] Optional settings.
  1368. * @param {object} [options.promiseLibrary] A Promise library class the application wishes to use such as Bluebird, must be ES6 compatible
  1369. * @param {ClientSession} [options.session] optional session to use for this operation
  1370. * @param {GridStore~resultCallback} [callback] the command callback.
  1371. * @return {Promise} returns Promise if no callback passed
  1372. * @deprecated Use GridFSBucket API instead
  1373. */
  1374. GridStore.unlink = function(db, names, options, callback) {
  1375. var args = Array.prototype.slice.call(arguments, 2);
  1376. callback = typeof args[args.length - 1] === 'function' ? args.pop() : undefined;
  1377. options = args.length ? args.shift() : {};
  1378. options = options || {};
  1379. return executeLegacyOperation(db.s.topology, unlinkStatic, [this, db, names, options, callback], {
  1380. skipSessions: true
  1381. });
  1382. };
  1383. var unlinkStatic = function(self, db, names, options, callback) {
  1384. // Get the write concern
  1385. var writeConcern = _getWriteConcern(db, options);
  1386. // List of names
  1387. if (names.constructor === Array) {
  1388. var tc = 0;
  1389. for (var i = 0; i < names.length; i++) {
  1390. ++tc;
  1391. GridStore.unlink(db, names[i], options, function() {
  1392. if (--tc === 0) {
  1393. callback(null, self);
  1394. }
  1395. });
  1396. }
  1397. } else {
  1398. new GridStore(db, names, 'w', options).open(function(err, gridStore) {
  1399. if (err) return callback(err);
  1400. deleteChunks(gridStore, function(err) {
  1401. if (err) return callback(err);
  1402. gridStore.collection(function(err, collection) {
  1403. if (err) return callback(err);
  1404. collection.remove({ _id: gridStore.fileId }, writeConcern, function(err) {
  1405. callback(err, self);
  1406. });
  1407. });
  1408. });
  1409. });
  1410. }
  1411. };
  1412. /**
  1413. * @ignore
  1414. */
  1415. var _writeNormal = function(self, data, close, options, callback) {
  1416. // If we have a buffer write it using the writeBuffer method
  1417. if (Buffer.isBuffer(data)) {
  1418. return writeBuffer(self, data, close, callback);
  1419. } else {
  1420. return writeBuffer(self, Buffer.from(data, 'binary'), close, callback);
  1421. }
  1422. };
  1423. /**
  1424. * @ignore
  1425. */
  1426. var _setWriteConcernHash = function(options) {
  1427. var finalOptions = {};
  1428. if (options.w != null) finalOptions.w = options.w;
  1429. if (options.journal === true) finalOptions.j = options.journal;
  1430. if (options.j === true) finalOptions.j = options.j;
  1431. if (options.fsync === true) finalOptions.fsync = options.fsync;
  1432. if (options.wtimeout != null) finalOptions.wtimeout = options.wtimeout;
  1433. return finalOptions;
  1434. };
  1435. /**
  1436. * @ignore
  1437. */
  1438. var _getWriteConcern = function(self, options) {
  1439. // Final options
  1440. var finalOptions = { w: 1 };
  1441. options = options || {};
  1442. // Local options verification
  1443. if (
  1444. options.w != null ||
  1445. typeof options.j === 'boolean' ||
  1446. typeof options.journal === 'boolean' ||
  1447. typeof options.fsync === 'boolean'
  1448. ) {
  1449. finalOptions = _setWriteConcernHash(options);
  1450. } else if (options.safe != null && typeof options.safe === 'object') {
  1451. finalOptions = _setWriteConcernHash(options.safe);
  1452. } else if (typeof options.safe === 'boolean') {
  1453. finalOptions = { w: options.safe ? 1 : 0 };
  1454. } else if (
  1455. self.options.w != null ||
  1456. typeof self.options.j === 'boolean' ||
  1457. typeof self.options.journal === 'boolean' ||
  1458. typeof self.options.fsync === 'boolean'
  1459. ) {
  1460. finalOptions = _setWriteConcernHash(self.options);
  1461. } else if (
  1462. self.safe &&
  1463. (self.safe.w != null ||
  1464. typeof self.safe.j === 'boolean' ||
  1465. typeof self.safe.journal === 'boolean' ||
  1466. typeof self.safe.fsync === 'boolean')
  1467. ) {
  1468. finalOptions = _setWriteConcernHash(self.safe);
  1469. } else if (typeof self.safe === 'boolean') {
  1470. finalOptions = { w: self.safe ? 1 : 0 };
  1471. }
  1472. // Ensure we don't have an invalid combination of write concerns
  1473. if (
  1474. finalOptions.w < 1 &&
  1475. (finalOptions.journal === true || finalOptions.j === true || finalOptions.fsync === true)
  1476. )
  1477. throw MongoError.create({
  1478. message: 'No acknowledgement using w < 1 cannot be combined with journal:true or fsync:true',
  1479. driver: true
  1480. });
  1481. // Return the options
  1482. return finalOptions;
  1483. };
  1484. /**
  1485. * Create a new GridStoreStream instance (INTERNAL TYPE, do not instantiate directly)
  1486. *
  1487. * @class
  1488. * @extends external:Duplex
  1489. * @return {GridStoreStream} a GridStoreStream instance.
  1490. * @deprecated Use GridFSBucket API instead
  1491. */
  1492. var GridStoreStream = function(gs) {
  1493. // Initialize the duplex stream
  1494. Duplex.call(this);
  1495. // Get the gridstore
  1496. this.gs = gs;
  1497. // End called
  1498. this.endCalled = false;
  1499. // If we have a seek
  1500. this.totalBytesToRead = this.gs.length - this.gs.position;
  1501. this.seekPosition = this.gs.position;
  1502. };
  1503. //
  1504. // Inherit duplex
  1505. inherits(GridStoreStream, Duplex);
  1506. GridStoreStream.prototype._pipe = GridStoreStream.prototype.pipe;
  1507. // Set up override
  1508. GridStoreStream.prototype.pipe = function(destination) {
  1509. var self = this;
  1510. // Only open gridstore if not already open
  1511. if (!self.gs.isOpen) {
  1512. self.gs.open(function(err) {
  1513. if (err) return self.emit('error', err);
  1514. self.totalBytesToRead = self.gs.length - self.gs.position;
  1515. self._pipe.apply(self, [destination]);
  1516. });
  1517. } else {
  1518. self.totalBytesToRead = self.gs.length - self.gs.position;
  1519. self._pipe.apply(self, [destination]);
  1520. }
  1521. return destination;
  1522. };
  1523. // Called by stream
  1524. GridStoreStream.prototype._read = function() {
  1525. var self = this;
  1526. var read = function() {
  1527. // Read data
  1528. self.gs.read(length, function(err, buffer) {
  1529. if (err && !self.endCalled) return self.emit('error', err);
  1530. // Stream is closed
  1531. if (self.endCalled || buffer == null) return self.push(null);
  1532. // Remove bytes read
  1533. if (buffer.length <= self.totalBytesToRead) {
  1534. self.totalBytesToRead = self.totalBytesToRead - buffer.length;
  1535. self.push(buffer);
  1536. } else if (buffer.length > self.totalBytesToRead) {
  1537. self.totalBytesToRead = self.totalBytesToRead - buffer._index;
  1538. self.push(buffer.slice(0, buffer._index));
  1539. }
  1540. // Finished reading
  1541. if (self.totalBytesToRead <= 0) {
  1542. self.endCalled = true;
  1543. }
  1544. });
  1545. };
  1546. // Set read length
  1547. var length =
  1548. self.gs.length < self.gs.chunkSize ? self.gs.length - self.seekPosition : self.gs.chunkSize;
  1549. if (!self.gs.isOpen) {
  1550. self.gs.open(function(err) {
  1551. self.totalBytesToRead = self.gs.length - self.gs.position;
  1552. if (err) return self.emit('error', err);
  1553. read();
  1554. });
  1555. } else {
  1556. read();
  1557. }
  1558. };
  1559. GridStoreStream.prototype.destroy = function() {
  1560. this.pause();
  1561. this.endCalled = true;
  1562. this.gs.close();
  1563. this.emit('end');
  1564. };
  1565. GridStoreStream.prototype.write = function(chunk) {
  1566. var self = this;
  1567. if (self.endCalled)
  1568. return self.emit(
  1569. 'error',
  1570. MongoError.create({ message: 'attempting to write to stream after end called', driver: true })
  1571. );
  1572. // Do we have to open the gridstore
  1573. if (!self.gs.isOpen) {
  1574. self.gs.open(function() {
  1575. self.gs.isOpen = true;
  1576. self.gs.write(chunk, function() {
  1577. process.nextTick(function() {
  1578. self.emit('drain');
  1579. });
  1580. });
  1581. });
  1582. return false;
  1583. } else {
  1584. self.gs.write(chunk, function() {
  1585. self.emit('drain');
  1586. });
  1587. return true;
  1588. }
  1589. };
  1590. GridStoreStream.prototype.end = function(chunk, encoding, callback) {
  1591. var self = this;
  1592. var args = Array.prototype.slice.call(arguments, 0);
  1593. callback = typeof args[args.length - 1] === 'function' ? args.pop() : undefined;
  1594. chunk = args.length ? args.shift() : null;
  1595. encoding = args.length ? args.shift() : null;
  1596. self.endCalled = true;
  1597. if (chunk) {
  1598. self.gs.write(chunk, function() {
  1599. self.gs.close(function() {
  1600. if (typeof callback === 'function') callback();
  1601. self.emit('end');
  1602. });
  1603. });
  1604. }
  1605. self.gs.close(function() {
  1606. if (typeof callback === 'function') callback();
  1607. self.emit('end');
  1608. });
  1609. };
  1610. /**
  1611. * The read() method pulls some data out of the internal buffer and returns it. If there is no data available, then it will return null.
  1612. * @function external:Duplex#read
  1613. * @param {number} size Optional argument to specify how much data to read.
  1614. * @return {(String | Buffer | null)}
  1615. */
  1616. /**
  1617. * Call this function to cause the stream to return strings of the specified encoding instead of Buffer objects.
  1618. * @function external:Duplex#setEncoding
  1619. * @param {string} encoding The encoding to use.
  1620. * @return {null}
  1621. */
  1622. /**
  1623. * This method will cause the readable stream to resume emitting data events.
  1624. * @function external:Duplex#resume
  1625. * @return {null}
  1626. */
  1627. /**
  1628. * This method will cause a stream in flowing-mode to stop emitting data events. Any data that becomes available will remain in the internal buffer.
  1629. * @function external:Duplex#pause
  1630. * @return {null}
  1631. */
  1632. /**
  1633. * This method pulls all the data out of a readable stream, and writes it to the supplied destination, automatically managing the flow so that the destination is not overwhelmed by a fast readable stream.
  1634. * @function external:Duplex#pipe
  1635. * @param {Writable} destination The destination for writing data
  1636. * @param {object} [options] Pipe options
  1637. * @return {null}
  1638. */
  1639. /**
  1640. * This method will remove the hooks set up for a previous pipe() call.
  1641. * @function external:Duplex#unpipe
  1642. * @param {Writable} [destination] The destination for writing data
  1643. * @return {null}
  1644. */
  1645. /**
  1646. * This is useful in certain cases where a stream is being consumed by a parser, which needs to "un-consume" some data that it has optimistically pulled out of the source, so that the stream can be passed on to some other party.
  1647. * @function external:Duplex#unshift
  1648. * @param {(Buffer|string)} chunk Chunk of data to unshift onto the read queue.
  1649. * @return {null}
  1650. */
  1651. /**
  1652. * Versions of Node prior to v0.10 had streams that did not implement the entire Streams API as it is today. (See "Compatibility" below for more information.)
  1653. * @function external:Duplex#wrap
  1654. * @param {Stream} stream An "old style" readable stream.
  1655. * @return {null}
  1656. */
  1657. /**
  1658. * This method writes some data to the underlying system, and calls the supplied callback once the data has been fully handled.
  1659. * @function external:Duplex#write
  1660. * @param {(string|Buffer)} chunk The data to write
  1661. * @param {string} encoding The encoding, if chunk is a String
  1662. * @param {function} callback Callback for when this chunk of data is flushed
  1663. * @return {boolean}
  1664. */
  1665. /**
  1666. * Call this method when no more data will be written to the stream. If supplied, the callback is attached as a listener on the finish event.
  1667. * @function external:Duplex#end
  1668. * @param {(string|Buffer)} chunk The data to write
  1669. * @param {string} encoding The encoding, if chunk is a String
  1670. * @param {function} callback Callback for when this chunk of data is flushed
  1671. * @return {null}
  1672. */
  1673. /**
  1674. * GridStoreStream stream data event, fired for each document in the cursor.
  1675. *
  1676. * @event GridStoreStream#data
  1677. * @type {object}
  1678. */
  1679. /**
  1680. * GridStoreStream stream end event
  1681. *
  1682. * @event GridStoreStream#end
  1683. * @type {null}
  1684. */
  1685. /**
  1686. * GridStoreStream stream close event
  1687. *
  1688. * @event GridStoreStream#close
  1689. * @type {null}
  1690. */
  1691. /**
  1692. * GridStoreStream stream readable event
  1693. *
  1694. * @event GridStoreStream#readable
  1695. * @type {null}
  1696. */
  1697. /**
  1698. * GridStoreStream stream drain event
  1699. *
  1700. * @event GridStoreStream#drain
  1701. * @type {null}
  1702. */
  1703. /**
  1704. * GridStoreStream stream finish event
  1705. *
  1706. * @event GridStoreStream#finish
  1707. * @type {null}
  1708. */
  1709. /**
  1710. * GridStoreStream stream pipe event
  1711. *
  1712. * @event GridStoreStream#pipe
  1713. * @type {null}
  1714. */
  1715. /**
  1716. * GridStoreStream stream unpipe event
  1717. *
  1718. * @event GridStoreStream#unpipe
  1719. * @type {null}
  1720. */
  1721. /**
  1722. * GridStoreStream stream error event
  1723. *
  1724. * @event GridStoreStream#error
  1725. * @type {null}
  1726. */
  1727. /**
  1728. * @ignore
  1729. */
  1730. module.exports = GridStore;