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