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 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
 
@@ -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 < 0) || (start >= buf.length) || (end < start) || (end >= buf.length)) {
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;
@@ -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 < 0) || (start >= stats.size) || (end < start) || (end >= stats.size)) {
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 < 0) || (start >= buf.length) || (end < start) || (end >= buf.length)) {
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;
@@ -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 < 0) || (start >= buf.length) || (end < start) || (end >= buf.length)) {
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
- var download = self.s3.getObject( Tools.mergeHashes(params, { Range: "bytes=" + start + "-" + end }) ).createReadStream();
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 < 0) || (start >= data.ContentLength) || (end < start) || (end >= data.ContentLength)) {
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.10",
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
- "mkdirp": "0.5.1",
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: