chunk.js 7.0 KB

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