chunk.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. 'use strict';
  2. var Binary = require('mongodb-core').BSON.Binary,
  3. ObjectID = require('mongodb-core').BSON.ObjectID;
  4. var Buffer = require('safe-buffer').Buffer;
  5. /**
  6. * Class for representing a single chunk in GridFS.
  7. *
  8. * @class
  9. *
  10. * @param file {GridStore} The {@link GridStore} object holding this chunk.
  11. * @param mongoObject {object} The mongo object representation of this chunk.
  12. *
  13. * @throws Error when the type of data field for {@link mongoObject} is not
  14. * supported. Currently supported types for data field are instances of
  15. * {@link String}, {@link Array}, {@link Binary} and {@link Binary}
  16. * from the bson module
  17. *
  18. * @see Chunk#buildMongoObject
  19. */
  20. var Chunk = function(file, mongoObject, writeConcern) {
  21. if (!(this instanceof Chunk)) return new Chunk(file, mongoObject);
  22. this.file = file;
  23. var mongoObjectFinal = mongoObject == null ? {} : mongoObject;
  24. this.writeConcern = writeConcern || { w: 1 };
  25. this.objectId = mongoObjectFinal._id == null ? new ObjectID() : mongoObjectFinal._id;
  26. this.chunkNumber = mongoObjectFinal.n == null ? 0 : mongoObjectFinal.n;
  27. this.data = new Binary();
  28. if (typeof mongoObjectFinal.data === 'string') {
  29. var buffer = Buffer.alloc(mongoObjectFinal.data.length);
  30. buffer.write(mongoObjectFinal.data, 0, mongoObjectFinal.data.length, 'binary');
  31. this.data = new Binary(buffer);
  32. } else if (Array.isArray(mongoObjectFinal.data)) {
  33. buffer = Buffer.alloc(mongoObjectFinal.data.length);
  34. var data = mongoObjectFinal.data.join('');
  35. buffer.write(data, 0, data.length, 'binary');
  36. this.data = new Binary(buffer);
  37. } else if (mongoObjectFinal.data && mongoObjectFinal.data._bsontype === 'Binary') {
  38. this.data = mongoObjectFinal.data;
  39. } else if (!Buffer.isBuffer(mongoObjectFinal.data) && !(mongoObjectFinal.data == null)) {
  40. throw Error('Illegal chunk format');
  41. }
  42. // Update position
  43. this.internalPosition = 0;
  44. };
  45. /**
  46. * Writes a data to this object and advance the read/write head.
  47. *
  48. * @param data {string} the data to write
  49. * @param callback {function(*, GridStore)} This will be called after executing
  50. * this method. The first parameter will contain null and the second one
  51. * will contain a reference to this object.
  52. */
  53. Chunk.prototype.write = function(data, callback) {
  54. this.data.write(data, this.internalPosition, data.length, 'binary');
  55. this.internalPosition = this.data.length();
  56. if (callback != null) return callback(null, this);
  57. return this;
  58. };
  59. /**
  60. * Reads data and advances the read/write head.
  61. *
  62. * @param length {number} The length of data to read.
  63. *
  64. * @return {string} The data read if the given length will not exceed the end of
  65. * the chunk. Returns an empty String otherwise.
  66. */
  67. Chunk.prototype.read = function(length) {
  68. // Default to full read if no index defined
  69. length = length == null || length === 0 ? this.length() : length;
  70. if (this.length() - this.internalPosition + 1 >= length) {
  71. var data = this.data.read(this.internalPosition, length);
  72. this.internalPosition = this.internalPosition + length;
  73. return data;
  74. } else {
  75. return '';
  76. }
  77. };
  78. Chunk.prototype.readSlice = function(length) {
  79. if (this.length() - this.internalPosition >= length) {
  80. var data = null;
  81. if (this.data.buffer != null) {
  82. //Pure BSON
  83. data = this.data.buffer.slice(this.internalPosition, this.internalPosition + length);
  84. } else {
  85. //Native BSON
  86. data = Buffer.alloc(length);
  87. length = this.data.readInto(data, this.internalPosition);
  88. }
  89. this.internalPosition = this.internalPosition + length;
  90. return data;
  91. } else {
  92. return null;
  93. }
  94. };
  95. /**
  96. * Checks if the read/write head is at the end.
  97. *
  98. * @return {boolean} Whether the read/write head has reached the end of this
  99. * chunk.
  100. */
  101. Chunk.prototype.eof = function() {
  102. return this.internalPosition === this.length() ? true : false;
  103. };
  104. /**
  105. * Reads one character from the data of this chunk and advances the read/write
  106. * head.
  107. *
  108. * @return {string} a single character data read if the the read/write head is
  109. * not at the end of the chunk. Returns an empty String otherwise.
  110. */
  111. Chunk.prototype.getc = function() {
  112. return this.read(1);
  113. };
  114. /**
  115. * Clears the contents of the data in this chunk and resets the read/write head
  116. * to the initial position.
  117. */
  118. Chunk.prototype.rewind = function() {
  119. this.internalPosition = 0;
  120. this.data = new Binary();
  121. };
  122. /**
  123. * Saves this chunk to the database. Also overwrites existing entries having the
  124. * same id as this chunk.
  125. *
  126. * @param callback {function(*, GridStore)} This will be called after executing
  127. * this method. The first parameter will contain null and the second one
  128. * will contain a reference to this object.
  129. */
  130. Chunk.prototype.save = function(options, callback) {
  131. var self = this;
  132. if (typeof options === 'function') {
  133. callback = options;
  134. options = {};
  135. }
  136. self.file.chunkCollection(function(err, collection) {
  137. if (err) return callback(err);
  138. // Merge the options
  139. var writeOptions = { upsert: true };
  140. for (var name in options) writeOptions[name] = options[name];
  141. for (name in self.writeConcern) writeOptions[name] = self.writeConcern[name];
  142. if (self.data.length() > 0) {
  143. self.buildMongoObject(function(mongoObject) {
  144. var options = { forceServerObjectId: true };
  145. for (var name in self.writeConcern) {
  146. options[name] = self.writeConcern[name];
  147. }
  148. collection.replaceOne({ _id: self.objectId }, mongoObject, writeOptions, function(err) {
  149. callback(err, self);
  150. });
  151. });
  152. } else {
  153. callback(null, self);
  154. }
  155. // });
  156. });
  157. };
  158. /**
  159. * Creates a mongoDB object representation of this chunk.
  160. *
  161. * @param callback {function(Object)} This will be called after executing this
  162. * method. The object will be passed to the first parameter and will have
  163. * the structure:
  164. *
  165. * <pre><code>
  166. * {
  167. * '_id' : , // {number} id for this chunk
  168. * 'files_id' : , // {number} foreign key to the file collection
  169. * 'n' : , // {number} chunk number
  170. * 'data' : , // {bson#Binary} the chunk data itself
  171. * }
  172. * </code></pre>
  173. *
  174. * @see <a href="http://www.mongodb.org/display/DOCS/GridFS+Specification#GridFSSpecification-{{chunks}}">MongoDB GridFS Chunk Object Structure</a>
  175. */
  176. Chunk.prototype.buildMongoObject = function(callback) {
  177. var mongoObject = {
  178. files_id: this.file.fileId,
  179. n: this.chunkNumber,
  180. data: this.data
  181. };
  182. // If we are saving using a specific ObjectId
  183. if (this.objectId != null) mongoObject._id = this.objectId;
  184. callback(mongoObject);
  185. };
  186. /**
  187. * @return {number} the length of the data
  188. */
  189. Chunk.prototype.length = function() {
  190. return this.data.length();
  191. };
  192. /**
  193. * The position of the read/write head
  194. * @name position
  195. * @lends Chunk#
  196. * @field
  197. */
  198. Object.defineProperty(Chunk.prototype, 'position', {
  199. enumerable: true,
  200. get: function() {
  201. return this.internalPosition;
  202. },
  203. set: function(value) {
  204. this.internalPosition = value;
  205. }
  206. });
  207. /**
  208. * The default chunk size
  209. * @constant
  210. */
  211. Chunk.DEFAULT_CHUNK_SIZE = 1024 * 255;
  212. module.exports = Chunk;