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.
- package/engines/Couchbase.js +26 -0
- package/engines/Filesystem.js +30 -0
- package/engines/Hybrid.js +10 -2
- package/engines/Redis.js +27 -0
- package/engines/RedisCluster.js +27 -0
- package/engines/S3.js +67 -3
- package/package.json +1 -1
- package/storage.js +15 -0
- package/transaction.js +9 -36
package/engines/Couchbase.js
CHANGED
|
@@ -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
|
package/engines/Filesystem.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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;
|
package/engines/RedisCluster.js
CHANGED
|
@@ -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.
|
|
353
|
+
download.once('error', function(err) {
|
|
294
354
|
self.logError('s3', "Failed to download key: " + key + ": " + err.message);
|
|
295
355
|
});
|
|
296
|
-
download.
|
|
356
|
+
download.once('end', function() {
|
|
297
357
|
self.logDebug(9, "S3 stream download complete: " + key);
|
|
298
358
|
} );
|
|
299
|
-
download.
|
|
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.
|
|
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
|
-
//
|
|
649
|
-
|
|
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
|
-
|
|
652
|
-
|
|
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
|