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