pixl-server-storage 3.0.10 → 3.0.14
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/docs/API.md +6 -1
- package/engines/Couchbase.js +8 -1
- package/engines/Filesystem.js +10 -2
- package/engines/Redis.js +9 -4
- package/engines/RedisCluster.js +8 -3
- package/engines/S3.js +17 -7
- package/package.json +2 -3
- package/test/test-main.js +95 -0
- package/transaction.js +1 -1
package/docs/API.md
CHANGED
|
@@ -226,7 +226,12 @@ storage.getStreamRange( 'test1.gif', 0, 99, function(err, readStream, info) {
|
|
|
226
226
|
} );
|
|
227
227
|
```
|
|
228
228
|
|
|
229
|
-
As you can see above, your callback is also passed a 3rd argument, which is an object containing the record's full byte length and modification date. These properties match those returned when you call [head()](#head) (i.e. `len` and `mod`). The `len` is not affected by the `start` and `end` range -- it is always the full byte length of the record.
|
|
229
|
+
As you can see in the example above, your callback is also passed a 3rd argument, which is an object containing the record's full byte length and modification date. These properties match those returned when you call [head()](#head) (i.e. `len` and `mod`). The `len` is not affected by the `start` and `end` range -- it is always the full byte length of the record.
|
|
230
|
+
|
|
231
|
+
The API supports two special range cases:
|
|
232
|
+
|
|
233
|
+
- If `start` is valid but `end` is `NaN`, it is assumed you want the entire record starting at `start` offset.
|
|
234
|
+
- If `start` is `NaN` but `end` is valid, it is assumed you want `end` bytes from the end of the record.
|
|
230
235
|
|
|
231
236
|
Please note that as of this writing, the `Couchbase`, `Redis` and `RedisCluster` engines have no native stream API, so the `getStreamRange()` method has to load the entire record into memory.
|
|
232
237
|
|
package/engines/Couchbase.js
CHANGED
|
@@ -249,7 +249,14 @@ module.exports = Class.create({
|
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
// validate byte range, now that we have the head info
|
|
252
|
-
if ((start
|
|
252
|
+
if (isNaN(start) && !isNaN(end)) {
|
|
253
|
+
start = buf.length - end;
|
|
254
|
+
end = buf.length ? buf.length - 1 : 0;
|
|
255
|
+
}
|
|
256
|
+
else if (!isNaN(start) && isNaN(end)) {
|
|
257
|
+
end = buf.length ? buf.length - 1 : 0;
|
|
258
|
+
}
|
|
259
|
+
if (isNaN(start) || isNaN(end) || (start < 0) || (start >= buf.length) || (end < start) || (end >= buf.length)) {
|
|
253
260
|
download.destroy();
|
|
254
261
|
callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + buf.length + ")"), null );
|
|
255
262
|
return;
|
package/engines/Filesystem.js
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
var path = require('path');
|
|
6
6
|
var fs = require('fs');
|
|
7
7
|
var async = require('async');
|
|
8
|
-
var mkdirp = require('mkdirp');
|
|
9
8
|
var crypto = require('crypto');
|
|
10
9
|
|
|
11
10
|
var Class = require("pixl-class");
|
|
@@ -13,6 +12,8 @@ var Component = require("pixl-server/component");
|
|
|
13
12
|
var Tools = require("pixl-tools");
|
|
14
13
|
var Cache = require("pixl-cache");
|
|
15
14
|
|
|
15
|
+
var mkdirp = Tools.mkdirp;
|
|
16
|
+
|
|
16
17
|
module.exports = Class.create({
|
|
17
18
|
|
|
18
19
|
__name: 'Filesystem',
|
|
@@ -480,7 +481,14 @@ module.exports = Class.create({
|
|
|
480
481
|
}
|
|
481
482
|
|
|
482
483
|
// validate byte range, now that we have the head info
|
|
483
|
-
if ((start
|
|
484
|
+
if (isNaN(start) && !isNaN(end)) {
|
|
485
|
+
start = stats.size - end;
|
|
486
|
+
end = stats.size ? stats.size - 1 : 0;
|
|
487
|
+
}
|
|
488
|
+
else if (!isNaN(start) && isNaN(end)) {
|
|
489
|
+
end = stats.size ? stats.size - 1 : 0;
|
|
490
|
+
}
|
|
491
|
+
if (isNaN(start) || isNaN(end) || (start < 0) || (start >= stats.size) || (end < start) || (end >= stats.size)) {
|
|
484
492
|
download.destroy();
|
|
485
493
|
callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + stats.size + ")"), null );
|
|
486
494
|
return;
|
package/engines/Redis.js
CHANGED
|
@@ -39,10 +39,8 @@ module.exports = Class.create({
|
|
|
39
39
|
|
|
40
40
|
this.keyPrefix = (r_config.keyPrefix || '').replace(/^\//, '');
|
|
41
41
|
if (this.keyPrefix && !this.keyPrefix.match(/\/$/)) this.keyPrefix += '/';
|
|
42
|
-
delete r_config.keyPrefix;
|
|
43
42
|
|
|
44
43
|
this.keyTemplate = (r_config.keyTemplate || '').replace(/^\//, '').replace(/\/$/, '');
|
|
45
|
-
delete r_config.keyTemplate;
|
|
46
44
|
|
|
47
45
|
r_config.return_buffers = true;
|
|
48
46
|
r_config.retry_strategy = function(opts) {
|
|
@@ -50,7 +48,7 @@ module.exports = Class.create({
|
|
|
50
48
|
return Math.min(opts.attempt * 100, 3000);
|
|
51
49
|
};
|
|
52
50
|
|
|
53
|
-
this.redis = Redis.createClient(r_config);
|
|
51
|
+
this.redis = Redis.createClient( Tools.copyHashRemoveKeys(r_config, { keyPrefix:1, keyTemplate:1 }) );
|
|
54
52
|
|
|
55
53
|
this.redis.on('error', function(err) {
|
|
56
54
|
if (!self.storage.started) return callback(err);
|
|
@@ -253,7 +251,14 @@ module.exports = Class.create({
|
|
|
253
251
|
}
|
|
254
252
|
|
|
255
253
|
// validate byte range, now that we have the head info
|
|
256
|
-
if ((start
|
|
254
|
+
if (isNaN(start) && !isNaN(end)) {
|
|
255
|
+
start = buf.length - end;
|
|
256
|
+
end = buf.length ? buf.length - 1 : 0;
|
|
257
|
+
}
|
|
258
|
+
else if (!isNaN(start) && isNaN(end)) {
|
|
259
|
+
end = buf.length ? buf.length - 1 : 0;
|
|
260
|
+
}
|
|
261
|
+
if (isNaN(start) || isNaN(end) || (start < 0) || (start >= buf.length) || (end < start) || (end >= buf.length)) {
|
|
257
262
|
download.destroy();
|
|
258
263
|
callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + buf.length + ")"), null );
|
|
259
264
|
return;
|
package/engines/RedisCluster.js
CHANGED
|
@@ -44,10 +44,8 @@ module.exports = Class.create({
|
|
|
44
44
|
|
|
45
45
|
this.keyPrefix = (r_config.keyPrefix || '').replace(/^\//, '');
|
|
46
46
|
if (this.keyPrefix && !this.keyPrefix.match(/\/$/)) this.keyPrefix += '/';
|
|
47
|
-
delete r_config.keyPrefix;
|
|
48
47
|
|
|
49
48
|
this.keyTemplate = (r_config.keyTemplate || '').replace(/^\//, '').replace(/\/$/, '');
|
|
50
|
-
delete r_config.keyTemplate;
|
|
51
49
|
|
|
52
50
|
r_config.clusterOpts.clusterRetryStrategy = function(attempts) {
|
|
53
51
|
if (attempts > r_config.connectRetries) return false;
|
|
@@ -270,7 +268,14 @@ module.exports = Class.create({
|
|
|
270
268
|
}
|
|
271
269
|
|
|
272
270
|
// validate byte range, now that we have the head info
|
|
273
|
-
if ((start
|
|
271
|
+
if (isNaN(start) && !isNaN(end)) {
|
|
272
|
+
start = buf.length - end;
|
|
273
|
+
end = buf.length ? buf.length - 1 : 0;
|
|
274
|
+
}
|
|
275
|
+
else if (!isNaN(start) && isNaN(end)) {
|
|
276
|
+
end = buf.length ? buf.length - 1 : 0;
|
|
277
|
+
}
|
|
278
|
+
if (isNaN(start) || isNaN(end) || (start < 0) || (start >= buf.length) || (end < start) || (end >= buf.length)) {
|
|
274
279
|
download.destroy();
|
|
275
280
|
callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + buf.length + ")"), null );
|
|
276
281
|
return;
|
package/engines/S3.js
CHANGED
|
@@ -37,13 +37,9 @@ module.exports = Class.create({
|
|
|
37
37
|
|
|
38
38
|
this.keyPrefix = (s3_config.keyPrefix || '').replace(/^\//, '');
|
|
39
39
|
if (this.keyPrefix && !this.keyPrefix.match(/\/$/)) this.keyPrefix += '/';
|
|
40
|
-
delete s3_config.keyPrefix;
|
|
41
40
|
|
|
42
41
|
this.keyTemplate = (s3_config.keyTemplate || '').replace(/^\//, '').replace(/\/$/, '');
|
|
43
|
-
delete s3_config.keyTemplate;
|
|
44
|
-
|
|
45
42
|
this.fileExtensions = !!s3_config.fileExtensions;
|
|
46
|
-
delete s3_config.fileExtensions;
|
|
47
43
|
|
|
48
44
|
if (this.debugLevel(10)) {
|
|
49
45
|
// S3 has a logger API but it's extremely verbose -- restrict to level 10 only
|
|
@@ -70,7 +66,7 @@ module.exports = Class.create({
|
|
|
70
66
|
delete s3_config.cache;
|
|
71
67
|
|
|
72
68
|
AWS.config.update( aws_config );
|
|
73
|
-
this.s3 = new AWS.S3( s3_config );
|
|
69
|
+
this.s3 = new AWS.S3( Tools.copyHashRemoveKeys(s3_config, { keyPrefix:1, keyTemplate:1, fileExtensions:1, cache:1 }) );
|
|
74
70
|
},
|
|
75
71
|
|
|
76
72
|
prepKey: function(key) {
|
|
@@ -338,7 +334,14 @@ module.exports = Class.create({
|
|
|
338
334
|
this.logDebug(9, "Fetching ranged S3 stream: " + key, { start, end });
|
|
339
335
|
|
|
340
336
|
var params = { Key: this.extKey(key, orig_key) };
|
|
341
|
-
|
|
337
|
+
|
|
338
|
+
// convert start/end to HTTP range header string
|
|
339
|
+
var range = "bytes=";
|
|
340
|
+
if (!isNaN(start)) range += start;
|
|
341
|
+
range += '-';
|
|
342
|
+
if (!isNaN(end)) range += end;
|
|
343
|
+
|
|
344
|
+
var download = self.s3.getObject( Tools.mergeHashes(params, { Range: range }) ).createReadStream();
|
|
342
345
|
var proceed = false;
|
|
343
346
|
|
|
344
347
|
download.on('error', function(err) {
|
|
@@ -366,7 +369,14 @@ module.exports = Class.create({
|
|
|
366
369
|
}
|
|
367
370
|
|
|
368
371
|
// validate byte range, now that we have the head info
|
|
369
|
-
if ((start
|
|
372
|
+
if (isNaN(start) && !isNaN(end)) {
|
|
373
|
+
start = data.ContentLength - end;
|
|
374
|
+
end = data.ContentLength ? data.ContentLength - 1 : 0;
|
|
375
|
+
}
|
|
376
|
+
else if (!isNaN(start) && isNaN(end)) {
|
|
377
|
+
end = data.ContentLength ? data.ContentLength - 1 : 0;
|
|
378
|
+
}
|
|
379
|
+
if (isNaN(start) || isNaN(end) || (start < 0) || (start >= data.ContentLength) || (end < start) || (end >= data.ContentLength)) {
|
|
370
380
|
download.destroy();
|
|
371
381
|
callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + data.ContentLength + ")"), null );
|
|
372
382
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixl-server-storage",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.14",
|
|
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",
|
|
@@ -27,8 +27,7 @@
|
|
|
27
27
|
"pixl-perf": "^1.0.0",
|
|
28
28
|
"pixl-cache": "^1.0.1",
|
|
29
29
|
"async": "2.6.0",
|
|
30
|
-
"
|
|
31
|
-
"nearley": "2.11.0",
|
|
30
|
+
"nearley": "2.20.1",
|
|
32
31
|
"moo": "0.4.3",
|
|
33
32
|
"porter-stemmer": "0.9.1",
|
|
34
33
|
"unidecode": "0.1.8",
|
package/test/test-main.js
CHANGED
|
@@ -424,6 +424,7 @@ module.exports = {
|
|
|
424
424
|
},
|
|
425
425
|
|
|
426
426
|
function testStreamRange(test) {
|
|
427
|
+
// grab a range from within a stream, with both start and end specified
|
|
427
428
|
test.expect(14);
|
|
428
429
|
var self = this;
|
|
429
430
|
|
|
@@ -469,6 +470,100 @@ module.exports = {
|
|
|
469
470
|
} ); // putStream
|
|
470
471
|
},
|
|
471
472
|
|
|
473
|
+
function testStreamRangeStart(test) {
|
|
474
|
+
// grab a range from within a stream, with end missing
|
|
475
|
+
test.expect(14);
|
|
476
|
+
var self = this;
|
|
477
|
+
|
|
478
|
+
var key = 'spacer-stream.gif';
|
|
479
|
+
var filename = 'spacer.gif';
|
|
480
|
+
var spacerBuf = fs.readFileSync( __dirname + '/' + filename );
|
|
481
|
+
var spacerStream = fs.createReadStream( __dirname + '/' + filename );
|
|
482
|
+
|
|
483
|
+
test.ok( !!spacerBuf, "Got buffer from file" );
|
|
484
|
+
test.ok( typeof(spacerBuf) == 'object', "Buffer is an object" );
|
|
485
|
+
test.ok( spacerBuf.length > 0, "Buffer has size" );
|
|
486
|
+
test.ok( !!spacerStream, "Got read stream" );
|
|
487
|
+
|
|
488
|
+
this.storage.putStream( key, spacerStream, function(err) {
|
|
489
|
+
test.ok( !err, "No error creating stream: " + err );
|
|
490
|
+
|
|
491
|
+
var tempFile = __dirname + '/' + filename + '.streamtemp';
|
|
492
|
+
var outStream = fs.createWriteStream( tempFile );
|
|
493
|
+
|
|
494
|
+
self.storage.getStreamRange( key, 20, NaN, function(err, storageStream, streamInfo) {
|
|
495
|
+
test.ok( !err, "No error fetching stream: " + err );
|
|
496
|
+
test.ok( !!storageStream, "Got storage stream as 2nd arg");
|
|
497
|
+
test.ok( !!storageStream.pipe, "Storage stream has a pipe");
|
|
498
|
+
test.ok( !!streamInfo, "Info was provided as the 3rd arg");
|
|
499
|
+
test.ok( streamInfo.len == 43, "Info has correct data length");
|
|
500
|
+
test.ok( streamInfo.mod > 0, "Info has a non-zero mod date");
|
|
501
|
+
|
|
502
|
+
outStream.on('finish', function() {
|
|
503
|
+
var newSpacerBuf = fs.readFileSync( tempFile );
|
|
504
|
+
test.ok( newSpacerBuf.length == 23, "Stream range length is correct" );
|
|
505
|
+
test.ok( newSpacerBuf.equals( spacerBuf.slice(20) ), "Range buffer content is correct" );
|
|
506
|
+
|
|
507
|
+
self.storage.delete( key, function(err) {
|
|
508
|
+
test.ok( !err, "No error deleting stream key: " + err );
|
|
509
|
+
fs.unlinkSync( tempFile );
|
|
510
|
+
test.done();
|
|
511
|
+
} ); // delete
|
|
512
|
+
} ); // stream finish
|
|
513
|
+
|
|
514
|
+
storageStream.pipe( outStream );
|
|
515
|
+
|
|
516
|
+
} ); // getStream
|
|
517
|
+
} ); // putStream
|
|
518
|
+
},
|
|
519
|
+
|
|
520
|
+
function testStreamRangeEnd(test) {
|
|
521
|
+
// grab a range from within a stream, with start missing
|
|
522
|
+
test.expect(14);
|
|
523
|
+
var self = this;
|
|
524
|
+
|
|
525
|
+
var key = 'spacer-stream.gif';
|
|
526
|
+
var filename = 'spacer.gif';
|
|
527
|
+
var spacerBuf = fs.readFileSync( __dirname + '/' + filename );
|
|
528
|
+
var spacerStream = fs.createReadStream( __dirname + '/' + filename );
|
|
529
|
+
|
|
530
|
+
test.ok( !!spacerBuf, "Got buffer from file" );
|
|
531
|
+
test.ok( typeof(spacerBuf) == 'object', "Buffer is an object" );
|
|
532
|
+
test.ok( spacerBuf.length > 0, "Buffer has size" );
|
|
533
|
+
test.ok( !!spacerStream, "Got read stream" );
|
|
534
|
+
|
|
535
|
+
this.storage.putStream( key, spacerStream, function(err) {
|
|
536
|
+
test.ok( !err, "No error creating stream: " + err );
|
|
537
|
+
|
|
538
|
+
var tempFile = __dirname + '/' + filename + '.streamtemp';
|
|
539
|
+
var outStream = fs.createWriteStream( tempFile );
|
|
540
|
+
|
|
541
|
+
self.storage.getStreamRange( key, NaN, 10, function(err, storageStream, streamInfo) {
|
|
542
|
+
test.ok( !err, "No error fetching stream: " + err );
|
|
543
|
+
test.ok( !!storageStream, "Got storage stream as 2nd arg");
|
|
544
|
+
test.ok( !!storageStream.pipe, "Storage stream has a pipe");
|
|
545
|
+
test.ok( !!streamInfo, "Info was provided as the 3rd arg");
|
|
546
|
+
test.ok( streamInfo.len == 43, "Info has correct data length");
|
|
547
|
+
test.ok( streamInfo.mod > 0, "Info has a non-zero mod date");
|
|
548
|
+
|
|
549
|
+
outStream.on('finish', function() {
|
|
550
|
+
var newSpacerBuf = fs.readFileSync( tempFile );
|
|
551
|
+
test.ok( newSpacerBuf.length == 10, "Stream range length is correct" );
|
|
552
|
+
test.ok( newSpacerBuf.equals( spacerBuf.slice(43 - 10) ), "Range buffer content is correct" );
|
|
553
|
+
|
|
554
|
+
self.storage.delete( key, function(err) {
|
|
555
|
+
test.ok( !err, "No error deleting stream key: " + err );
|
|
556
|
+
fs.unlinkSync( tempFile );
|
|
557
|
+
test.done();
|
|
558
|
+
} ); // delete
|
|
559
|
+
} ); // stream finish
|
|
560
|
+
|
|
561
|
+
storageStream.pipe( outStream );
|
|
562
|
+
|
|
563
|
+
} ); // getStream
|
|
564
|
+
} ); // putStream
|
|
565
|
+
},
|
|
566
|
+
|
|
472
567
|
function testPutMulti(test) {
|
|
473
568
|
// test storing multiple keys at once
|
|
474
569
|
test.expect(1);
|
package/transaction.js
CHANGED
|
@@ -8,9 +8,9 @@ var Path = require("path");
|
|
|
8
8
|
var cp = require("child_process");
|
|
9
9
|
var os = require("os");
|
|
10
10
|
var async = require('async');
|
|
11
|
-
var mkdirp = require('mkdirp');
|
|
12
11
|
var Class = require("pixl-class");
|
|
13
12
|
var Tools = require("pixl-tools");
|
|
13
|
+
var mkdirp = Tools.mkdirp;
|
|
14
14
|
|
|
15
15
|
// Transaction support is implemented as a mixin to Storage
|
|
16
16
|
// Config Keys:
|