pixl-server-storage 3.0.4 → 3.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -228,6 +228,32 @@ module.exports = Class.create({
228
228
  } );
229
229
  },
230
230
 
231
+ getStreamRange: function(key, start, end, callback) {
232
+ // get readable stream to record value given key and range
233
+ var self = this;
234
+
235
+ // The Couchbase Node.JS 2.0 API has no stream support.
236
+ // So, we have to do this the RAM-hard way...
237
+ this.get( key, function(err, buf) {
238
+ if (err && (err.code != CouchbaseAPI.errors.keyNotFound)) {
239
+ // some other error
240
+ err.message = "Failed to fetch key: " + key + ": " + err.message;
241
+ self.logError('couchbase', err.message);
242
+ return callback(err);
243
+ }
244
+ else if (!buf) {
245
+ // record not found
246
+ var err = new Error("Failed to fetch key: " + key + ": Not found");
247
+ err.code = "NoSuchKey";
248
+ return callback( err, null );
249
+ }
250
+
251
+ var range = buf.slice(start, end + 1);
252
+ var stream = new BufferStream(range);
253
+ callback(null, stream);
254
+ } );
255
+ },
256
+
231
257
  delete: function(key, callback) {
232
258
  // delete Couchbase key given key
233
259
  // Example CB error message: The key does not exist on the server
@@ -453,6 +453,36 @@ module.exports = Class.create({
453
453
  } );
454
454
  },
455
455
 
456
+ getStreamRange: function(key, start, end, callback) {
457
+ // get readable stream to record value given key and byte range
458
+ var self = this;
459
+ var file = this.getFilePath(key);
460
+
461
+ this.logDebug(9, "Fetching ranged binary stream: " + key, { file, start, end } );
462
+
463
+ // make sure record exists
464
+ fs.stat(file, function(err, stats) {
465
+ if (err) {
466
+ if (err.message.match(/ENOENT/)) {
467
+ err.message = "File not found";
468
+ err.code = "NoSuchKey";
469
+ }
470
+ else {
471
+ // log fs errors that aren't simple missing files (i.e. I/O errors)
472
+ self.logError('file', "Failed to stat file: " + key + ": " + file + ": " + err.message);
473
+ }
474
+
475
+ err.message = "Failed to head key: " + key + ": " + err.message;
476
+ return callback( err, null );
477
+ }
478
+
479
+ // create read stream
480
+ var inp = fs.createReadStream( file, { start, end } );
481
+
482
+ callback( null, inp );
483
+ } );
484
+ },
485
+
456
486
  delete: function(key, callback) {
457
487
  // delete key given key
458
488
  var self = this;
package/engines/Hybrid.js CHANGED
@@ -21,11 +21,13 @@ module.exports = Class.create({
21
21
  // setup multiple sub-engines
22
22
  this.logDebug(2, "Setting up hybrid engine", this.config.get() );
23
23
 
24
- this.binaryEngine = new require( './' + this.config.get('binaryEngine') + '.js' )();
24
+ var binaryClass = require( './' + this.config.get('binaryEngine') + '.js' );
25
+ this.binaryEngine = new binaryClass();
25
26
  this.binaryEngine.storage = this.storage;
26
27
  this.binaryEngine.init( this.server, this.storage.config.getSub( this.config.get('binaryEngine') ) );
27
28
 
28
- this.docEngine = new require( './' + this.config.get('docEngine') + '.js' )();
29
+ var docClass = require( './' + this.config.get('docEngine') + '.js' );
30
+ this.docEngine = new docClass();
29
31
  this.docEngine.storage = this.storage;
30
32
  this.docEngine.init( this.server, this.storage.config.getSub( this.config.get('docEngine') ) );
31
33
 
@@ -77,6 +79,12 @@ module.exports = Class.create({
77
79
  this.binaryEngine.getStream( key, callback );
78
80
  },
79
81
 
82
+ getStreamRange: function(key, start, end, callback) {
83
+ // get readable stream to record value given key and range
84
+ // streams are binary only!
85
+ this.binaryEngine.getStreamRange( key, start, end, callback );
86
+ },
87
+
80
88
  delete: function(key, callback) {
81
89
  // delete hybrid key given key
82
90
  if (this.storage.isBinaryKey(key)) {
package/engines/Redis.js CHANGED
@@ -231,6 +231,33 @@ module.exports = Class.create({
231
231
  } );
232
232
  },
233
233
 
234
+ getStreamRange: function(key, start, end, callback) {
235
+ // get readable stream to record value given key and range
236
+ var self = this;
237
+
238
+ // The Redis API has no stream support.
239
+ // So, we have to do this the RAM-hard way...
240
+
241
+ this.get( key, function(err, buf) {
242
+ if (err) {
243
+ // an actual error
244
+ err.message = "Failed to fetch key: " + key + ": " + err;
245
+ self.logError('redis', ''+err);
246
+ return callback(err);
247
+ }
248
+ else if (!buf) {
249
+ // record not found
250
+ var err = new Error("Failed to fetch key: " + key + ": Not found");
251
+ err.code = "NoSuchKey";
252
+ return callback( err, null );
253
+ }
254
+
255
+ var range = buf.slice(start, end + 1);
256
+ var stream = new BufferStream(range);
257
+ callback(null, stream);
258
+ } );
259
+ },
260
+
234
261
  delete: function(key, callback) {
235
262
  // delete Redis key given key
236
263
  var self = this;
@@ -248,6 +248,33 @@ module.exports = Class.create({
248
248
  } );
249
249
  },
250
250
 
251
+ getStreamRange: function(key, start, end, callback) {
252
+ // get readable stream to record value given key and range
253
+ var self = this;
254
+
255
+ // The Redis API has no stream support.
256
+ // So, we have to do this the RAM-hard way...
257
+
258
+ this.get( key, function(err, buf) {
259
+ if (err) {
260
+ // an actual error
261
+ err.message = "Failed to fetch key: " + key + ": " + err;
262
+ self.logError('redis', ''+err);
263
+ return callback(err);
264
+ }
265
+ else if (!buf) {
266
+ // record not found
267
+ var err = new Error("Failed to fetch key: " + key + ": Not found");
268
+ err.code = "NoSuchKey";
269
+ return callback( err, null );
270
+ }
271
+
272
+ var range = buf.slice(start, end + 1);
273
+ var stream = new BufferStream(range);
274
+ callback(null, stream);
275
+ } );
276
+ },
277
+
251
278
  delete: function(key, callback) {
252
279
  // delete Redis key given key
253
280
  var self = this;
package/engines/S3.js CHANGED
@@ -125,7 +125,11 @@ module.exports = Class.create({
125
125
  params.ContentType = 'application/json';
126
126
  }
127
127
 
128
+ // double-callback protection (bug in aws-sdk)
129
+ var done = false;
128
130
  this.s3.putObject( params, function(err, data) {
131
+ if (done) return; else done = true;
132
+
129
133
  if (err) {
130
134
  self.logError('s3', "Failed to store object: " + key + ": " + err.message);
131
135
  }
@@ -152,7 +156,11 @@ module.exports = Class.create({
152
156
 
153
157
  this.logDebug(9, "Storing S3 Binary Stream: " + key);
154
158
 
159
+ // double-callback protection (bug in aws-sdk)
160
+ var done = false;
155
161
  this.s3.upload(params, function(err, data) {
162
+ if (done) return; else done = true;
163
+
156
164
  if (err) {
157
165
  self.logError('s3', "Failed to store stream: " + key + ": " + err.message);
158
166
  }
@@ -183,7 +191,11 @@ module.exports = Class.create({
183
191
  return;
184
192
  } // cache
185
193
 
194
+ // double-callback protection (bug in aws-sdk)
195
+ var done = false;
186
196
  this.s3.headObject( { Key: this.extKey(key, orig_key) }, function(err, data) {
197
+ if (done) return; else done = true;
198
+
187
199
  if (err) {
188
200
  if (err.code != 'NoSuchKey') {
189
201
  self.logError('s3', "Failed to head key: " + key + ": " + err.message);
@@ -227,7 +239,11 @@ module.exports = Class.create({
227
239
  return;
228
240
  } // cache
229
241
 
242
+ // double-callback protection (bug in aws-sdk)
243
+ var done = false;
230
244
  this.s3.getObject( { Key: this.extKey(key, orig_key) }, function(err, data) {
245
+ if (done) return; else done = true;
246
+
231
247
  if (err) {
232
248
  if (err.code == 'NoSuchKey') {
233
249
  // key not found, special case, don't log an error
@@ -279,7 +295,50 @@ module.exports = Class.create({
279
295
 
280
296
  var params = { Key: this.extKey(key, orig_key) };
281
297
 
298
+ // double-callback protection (bug in aws-sdk)
299
+ var done = false;
300
+ this.s3.headObject( params, function(err) {
301
+ if (done) return; else done = true;
302
+
303
+ if (err) {
304
+ if (err.code != 'NoSuchKey') {
305
+ self.logError('s3', "Failed to head key: " + key + ": " + err.message);
306
+ }
307
+ callback( err, null );
308
+ return;
309
+ }
310
+
311
+ var download = self.s3.getObject(params).createReadStream();
312
+
313
+ download.once('error', function(err) {
314
+ self.logError('s3', "Failed to download key: " + key + ": " + err.message);
315
+ });
316
+ download.once('end', function() {
317
+ self.logDebug(9, "S3 stream download complete: " + key);
318
+ } );
319
+ download.once('close', function() {
320
+ self.logDebug(9, "S3 stream download closed: " + key);
321
+ } );
322
+
323
+ callback( null, download );
324
+ }); // headObject
325
+ },
326
+
327
+ getStreamRange: function(key, start, end, callback) {
328
+ // get readable stream to record value given key and range
329
+ var self = this;
330
+ var orig_key = key;
331
+ key = this.prepKey(key);
332
+
333
+ this.logDebug(9, "Fetching ranged S3 stream: " + key, { start, end });
334
+
335
+ var params = { Key: this.extKey(key, orig_key) };
336
+
337
+ // double-callback protection (bug in aws-sdk)
338
+ var done = false;
282
339
  this.s3.headObject( params, function(err) {
340
+ if (done) return; else done = true;
341
+
283
342
  if (err) {
284
343
  if (err.code != 'NoSuchKey') {
285
344
  self.logError('s3', "Failed to head key: " + key + ": " + err.message);
@@ -288,15 +347,16 @@ module.exports = Class.create({
288
347
  return;
289
348
  }
290
349
 
350
+ params.Range = "bytes=" + start + "-" + end;
291
351
  var download = self.s3.getObject(params).createReadStream();
292
352
 
293
- download.on('error', function(err) {
353
+ download.once('error', function(err) {
294
354
  self.logError('s3', "Failed to download key: " + key + ": " + err.message);
295
355
  });
296
- download.on('end', function() {
356
+ download.once('end', function() {
297
357
  self.logDebug(9, "S3 stream download complete: " + key);
298
358
  } );
299
- download.on('close', function() {
359
+ download.once('close', function() {
300
360
  self.logDebug(9, "S3 stream download closed: " + key);
301
361
  } );
302
362
 
@@ -312,7 +372,11 @@ module.exports = Class.create({
312
372
 
313
373
  this.logDebug(9, "Deleting S3 Object: " + key);
314
374
 
375
+ // double-callback protection (bug in aws-sdk)
376
+ var done = false;
315
377
  this.s3.deleteObject( { Key: this.extKey(key, orig_key) }, function(err, data) {
378
+ if (done) return; else done = true;
379
+
316
380
  if (err) {
317
381
  self.logError('s3', "Failed to delete object: " + key + ": " + err.message);
318
382
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixl-server-storage",
3
- "version": "3.0.4",
3
+ "version": "3.0.8",
4
4
  "description": "A key/value/list storage component for the pixl-server framework.",
5
5
  "author": "Joseph Huckaby <jhuckaby@gmail.com>",
6
6
  "homepage": "https://github.com/jhuckaby/pixl-server-storage",
package/storage.js CHANGED
@@ -354,6 +354,21 @@ module.exports = Class.create({
354
354
  this.engine.getStream( key, callback );
355
355
  },
356
356
 
357
+ getStreamRange: function(key, start, end, callback) {
358
+ // fetch value via stream pipe and range
359
+ // start and end are both inclusive
360
+ var self = this;
361
+ if (!this.started) return callback( new Error("Storage has not completed startup.") );
362
+
363
+ key = this.normalizeKey( key );
364
+
365
+ if (!this.isBinaryKey(key)) {
366
+ return callback( new Error("Stream values are only allowed with keys containing file extensions, e.g. " + key + ".bin") );
367
+ }
368
+
369
+ this.engine.getStreamRange( key, start, end, callback );
370
+ },
371
+
357
372
  getMulti: function(keys, callback) {
358
373
  // fetch multiple records at once, given array of keys
359
374
  // callback is provided an array of values in matching order to keys
package/transaction.js CHANGED
@@ -27,14 +27,6 @@ var TransStorageFunctions = {
27
27
  this.tempFileCounter = 1;
28
28
  },
29
29
 
30
- getFilePath: function(key) {
31
- // get local path to file given storage key
32
- var trans = this.transactions[ this.currentTransactionPath ];
33
- var key_id = Tools.digestHex( key );
34
- var file = Path.join( this.transDir, "data", trans.id + '-' + key_id + '.json' );
35
- return file;
36
- },
37
-
38
30
  put: function(key, value, callback) {
39
31
  // store key+value pair in transaction
40
32
  var self = this;
@@ -645,35 +637,16 @@ module.exports = Class.create({
645
637
  self.logError('rollback', "Unable to delete rollback log: " + trans.log + ": " + err);
646
638
  }
647
639
 
648
- // also delete any files written to temp area
649
- if (!trans.keys) trans.keys = {};
640
+ // complete, unlock and remove transaction from memory
641
+ self.unlock( 'C|'+path );
642
+ self._transUnlock(path);
643
+ self.transactions[path].keys = {}; // release memory
644
+ self.transactions[path].values = {}; // release memory
645
+ self.transactions[path].queue = []; // release memory
646
+ delete self.transactions[path];
650
647
 
651
- async.forEachOfLimit( trans.keys, self.concurrency,
652
- function(value, key, callback) {
653
- // delete temp key file
654
- var key_id = Tools.digestHex( key );
655
- var file = Path.join( self.transDir, "data", trans.id + '-' + key_id + '.json' );
656
-
657
- fs.unlink( file, function(err) {
658
- if (err && !err.message.match(/ENOENT/)) {
659
- self.logError('rollback', "Unable to delete transaction file: " + file + ": " + err);
660
- }
661
- callback();
662
- });
663
- },
664
- function(err) {
665
- // complete, unlock and remove transaction from memory
666
- self.unlock( 'C|'+path );
667
- self._transUnlock(path);
668
- self.transactions[path].keys = {}; // release memory
669
- self.transactions[path].values = {}; // release memory
670
- self.transactions[path].queue = []; // release memory
671
- delete self.transactions[path];
672
-
673
- self.logDebug(3, "Transaction rollback complete: " + trans.id, { path: path });
674
- callback();
675
- }
676
- ); // forEachOfLimit
648
+ self.logDebug(3, "Transaction rollback complete: " + trans.id, { path: path });
649
+ callback();
677
650
  }); // fs.unlink
678
651
  } // done with log
679
652
  ); // fileEachLine