pixl-server-storage 3.0.8 → 3.0.12
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/README.md +1 -1
- package/docs/API.md +44 -3
- package/engines/Couchbase.js +16 -2
- package/engines/Filesystem.js +24 -3
- package/engines/Redis.js +16 -2
- package/engines/RedisCluster.js +16 -2
- package/engines/S3.js +49 -26
- package/package.json +2 -3
- package/test/test-main.js +146 -2
- package/transaction.js +1 -1
package/README.md
CHANGED
|
@@ -846,7 +846,7 @@ storage.getStream( 'test1.gif', function(err, readStream) {
|
|
|
846
846
|
} );
|
|
847
847
|
```
|
|
848
848
|
|
|
849
|
-
Please note that not all the storage engines support streams natively, so the content may actually be loaded into RAM in the background. Namely, as of this writing, the Couchbase and Redis APIs
|
|
849
|
+
Please note that not all the storage engines support streams natively, so the content may actually be loaded into RAM in the background. Namely, as of this writing, the Couchbase and Redis APIs do not support streams, so they are currently simulated in those engines. Streams *are* supported natively in both the Filesystem and Amazon S3 engines.
|
|
850
850
|
|
|
851
851
|
# Expiring Data
|
|
852
852
|
|
package/docs/API.md
CHANGED
|
@@ -18,6 +18,7 @@ var storage = server.Storage;
|
|
|
18
18
|
* [get](#get)
|
|
19
19
|
* [getMulti](#getmulti)
|
|
20
20
|
* [getStream](#getstream)
|
|
21
|
+
* [getStreamRange](#getstreamrange)
|
|
21
22
|
* [head](#head)
|
|
22
23
|
* [headMulti](#headmulti)
|
|
23
24
|
* [delete](#delete)
|
|
@@ -126,6 +127,8 @@ storage.putMulti( records, function(err) {
|
|
|
126
127
|
} );
|
|
127
128
|
```
|
|
128
129
|
|
|
130
|
+
Note that if any of the individual put operations fail, the entire `putMulti()` function is aborted, and the first error is passed to your callback. At this point the operation may have been partially successful, with some records written, and others not. Due to this uncertainty, you may want to use this method inside of a [transaction](./Transactions.md), which can be safely rolled back upon error.
|
|
131
|
+
|
|
129
132
|
## putStream
|
|
130
133
|
|
|
131
134
|
```javascript
|
|
@@ -142,6 +145,8 @@ storage.putStream( 'test1.gif', stream, function(err) {
|
|
|
142
145
|
} );
|
|
143
146
|
```
|
|
144
147
|
|
|
148
|
+
Please note that as of this writing, the `Couchbase`, `Redis` and `RedisCluster` engines have no native stream API, so the `putStream()` method has to load the entire record into memory.
|
|
149
|
+
|
|
145
150
|
## get
|
|
146
151
|
|
|
147
152
|
```javascript
|
|
@@ -173,19 +178,46 @@ storage.getMulti( ['test1', 'test2', 'test3'], function(err, values) {
|
|
|
173
178
|
} );
|
|
174
179
|
```
|
|
175
180
|
|
|
181
|
+
Note that if *any* of the records fail, the entire operation fails, and the first error is passed to your callback.
|
|
182
|
+
|
|
176
183
|
## getStream
|
|
177
184
|
|
|
178
185
|
```javascript
|
|
179
186
|
storage.getStream( KEY, CALLBACK );
|
|
180
187
|
```
|
|
181
188
|
|
|
182
|
-
The `getStream()` method retrieves a [readable stream](https://nodejs.org/api/stream.html#
|
|
189
|
+
The `getStream()` method retrieves a [readable stream](https://nodejs.org/api/stream.html#class-streamreadable) to a given record's data, so it can be read or piped to a writable stream. This is for very large records, so nothing is loaded into memory. Example of spooling to a local file:
|
|
190
|
+
|
|
191
|
+
```javascript
|
|
192
|
+
var fs = require('fs');
|
|
193
|
+
var writeStream = fs.createWriteStream('/var/tmp/downloaded.gif');
|
|
194
|
+
|
|
195
|
+
storage.getStream( 'test1.gif', function(err, readStream, info) {
|
|
196
|
+
if (err) throw err;
|
|
197
|
+
writeStream.on('finish', function() {
|
|
198
|
+
// data is completely written
|
|
199
|
+
} );
|
|
200
|
+
readStream.pipe( writeStream );
|
|
201
|
+
} );
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
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`).
|
|
205
|
+
|
|
206
|
+
Please note that as of this writing, the `Couchbase`, `Redis` and `RedisCluster` engines have no native stream API, so the `getStream()` method has to load the entire record into memory.
|
|
207
|
+
|
|
208
|
+
## getStreamRange
|
|
209
|
+
|
|
210
|
+
```javascript
|
|
211
|
+
storage.getStreamRange( KEY, START, END, CALLBACK );
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The `getStreamRange()` method retrieves a [readable stream](https://nodejs.org/api/stream.html#class-streamreadable) to a specific slice of a record's data, so it can be read or piped to a writable stream. The `start` and `end` arguments should be set to the starting and ending byte offset of the slice you want. Both values are *inclusive*. Example of spooling bytes `0-99` of a record to a local file:
|
|
183
215
|
|
|
184
216
|
```javascript
|
|
185
217
|
var fs = require('fs');
|
|
186
218
|
var writeStream = fs.createWriteStream('/var/tmp/downloaded.gif');
|
|
187
219
|
|
|
188
|
-
storage.
|
|
220
|
+
storage.getStreamRange( 'test1.gif', 0, 99, function(err, readStream, info) {
|
|
189
221
|
if (err) throw err;
|
|
190
222
|
writeStream.on('finish', function() {
|
|
191
223
|
// data is completely written
|
|
@@ -194,6 +226,15 @@ storage.getStream( 'test1.gif', function(err, readStream) {
|
|
|
194
226
|
} );
|
|
195
227
|
```
|
|
196
228
|
|
|
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.
|
|
235
|
+
|
|
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.
|
|
237
|
+
|
|
197
238
|
## head
|
|
198
239
|
|
|
199
240
|
```javascript
|
|
@@ -217,7 +258,7 @@ storage.head( 'test1', function(err, data) {
|
|
|
217
258
|
} );
|
|
218
259
|
```
|
|
219
260
|
|
|
220
|
-
Please note that as of this writing, the `Couchbase`
|
|
261
|
+
Please note that as of this writing, the `Couchbase`, `Redis` and `RedisCluster` engines have no native head API, so the `head()` method has to load the entire record. It does return the record size in to the `len` property, but there is no way to retrieve the last modified date.
|
|
221
262
|
|
|
222
263
|
## headMulti
|
|
223
264
|
|
package/engines/Couchbase.js
CHANGED
|
@@ -224,7 +224,7 @@ module.exports = Class.create({
|
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
var stream = new BufferStream(buf);
|
|
227
|
-
callback(null, stream);
|
|
227
|
+
callback(null, stream, { mod: 1, len: buf.length });
|
|
228
228
|
} );
|
|
229
229
|
},
|
|
230
230
|
|
|
@@ -248,9 +248,23 @@ module.exports = Class.create({
|
|
|
248
248
|
return callback( err, null );
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
+
// validate byte range, now that we have the head info
|
|
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 ((start < 0) || (start >= buf.length) || (end < start) || (end >= buf.length)) {
|
|
260
|
+
download.destroy();
|
|
261
|
+
callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + buf.length + ")"), null );
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
251
265
|
var range = buf.slice(start, end + 1);
|
|
252
266
|
var stream = new BufferStream(range);
|
|
253
|
-
callback(null, stream);
|
|
267
|
+
callback(null, stream, { mod: 1, len: buf.length });
|
|
254
268
|
} );
|
|
255
269
|
},
|
|
256
270
|
|
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',
|
|
@@ -449,7 +450,10 @@ module.exports = Class.create({
|
|
|
449
450
|
// create read stream
|
|
450
451
|
var inp = fs.createReadStream( file );
|
|
451
452
|
|
|
452
|
-
callback( null, inp
|
|
453
|
+
callback( null, inp, {
|
|
454
|
+
mod: Math.floor(stats.mtime.getTime() / 1000),
|
|
455
|
+
len: stats.size
|
|
456
|
+
} );
|
|
453
457
|
} );
|
|
454
458
|
},
|
|
455
459
|
|
|
@@ -476,10 +480,27 @@ module.exports = Class.create({
|
|
|
476
480
|
return callback( err, null );
|
|
477
481
|
}
|
|
478
482
|
|
|
483
|
+
// validate byte range, now that we have the head info
|
|
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 ((start < 0) || (start >= stats.size) || (end < start) || (end >= stats.size)) {
|
|
492
|
+
download.destroy();
|
|
493
|
+
callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + stats.size + ")"), null );
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
|
|
479
497
|
// create read stream
|
|
480
498
|
var inp = fs.createReadStream( file, { start, end } );
|
|
481
499
|
|
|
482
|
-
callback( null, inp
|
|
500
|
+
callback( null, inp, {
|
|
501
|
+
mod: Math.floor(stats.mtime.getTime() / 1000),
|
|
502
|
+
len: stats.size
|
|
503
|
+
} );
|
|
483
504
|
} );
|
|
484
505
|
},
|
|
485
506
|
|
package/engines/Redis.js
CHANGED
|
@@ -227,7 +227,7 @@ module.exports = Class.create({
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
var stream = new BufferStream(buf);
|
|
230
|
-
callback(null, stream);
|
|
230
|
+
callback(null, stream, { mod: 1, len: buf.length });
|
|
231
231
|
} );
|
|
232
232
|
},
|
|
233
233
|
|
|
@@ -252,9 +252,23 @@ module.exports = Class.create({
|
|
|
252
252
|
return callback( err, null );
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
+
// validate byte range, now that we have the head info
|
|
256
|
+
if (isNaN(start) && !isNaN(end)) {
|
|
257
|
+
start = buf.length - end;
|
|
258
|
+
end = buf.length ? buf.length - 1 : 0;
|
|
259
|
+
}
|
|
260
|
+
else if (!isNaN(start) && isNaN(end)) {
|
|
261
|
+
end = buf.length ? buf.length - 1 : 0;
|
|
262
|
+
}
|
|
263
|
+
if ((start < 0) || (start >= buf.length) || (end < start) || (end >= buf.length)) {
|
|
264
|
+
download.destroy();
|
|
265
|
+
callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + buf.length + ")"), null );
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
255
269
|
var range = buf.slice(start, end + 1);
|
|
256
270
|
var stream = new BufferStream(range);
|
|
257
|
-
callback(null, stream);
|
|
271
|
+
callback(null, stream, { mod: 1, len: buf.length });
|
|
258
272
|
} );
|
|
259
273
|
},
|
|
260
274
|
|
package/engines/RedisCluster.js
CHANGED
|
@@ -244,7 +244,7 @@ module.exports = Class.create({
|
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
var stream = new BufferStream(buf);
|
|
247
|
-
callback(null, stream);
|
|
247
|
+
callback(null, stream, { mod: 1, len: buf.length });
|
|
248
248
|
} );
|
|
249
249
|
},
|
|
250
250
|
|
|
@@ -269,9 +269,23 @@ module.exports = Class.create({
|
|
|
269
269
|
return callback( err, null );
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
+
// validate byte range, now that we have the head info
|
|
273
|
+
if (isNaN(start) && !isNaN(end)) {
|
|
274
|
+
start = buf.length - end;
|
|
275
|
+
end = buf.length ? buf.length - 1 : 0;
|
|
276
|
+
}
|
|
277
|
+
else if (!isNaN(start) && isNaN(end)) {
|
|
278
|
+
end = buf.length ? buf.length - 1 : 0;
|
|
279
|
+
}
|
|
280
|
+
if ((start < 0) || (start >= buf.length) || (end < start) || (end >= buf.length)) {
|
|
281
|
+
download.destroy();
|
|
282
|
+
callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + buf.length + ")"), null );
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
272
286
|
var range = buf.slice(start, end + 1);
|
|
273
287
|
var stream = new BufferStream(range);
|
|
274
|
-
callback(null, stream);
|
|
288
|
+
callback(null, stream, { mod: 1, len: buf.length });
|
|
275
289
|
} );
|
|
276
290
|
},
|
|
277
291
|
|
package/engines/S3.js
CHANGED
|
@@ -294,33 +294,38 @@ module.exports = Class.create({
|
|
|
294
294
|
this.logDebug(9, "Fetching S3 Stream: " + key);
|
|
295
295
|
|
|
296
296
|
var params = { Key: this.extKey(key, orig_key) };
|
|
297
|
+
var download = self.s3.getObject(params).createReadStream();
|
|
298
|
+
var proceed = false;
|
|
299
|
+
|
|
300
|
+
download.on('error', function(err) {
|
|
301
|
+
if (proceed) self.logError('s3', "Failed to download key: " + key + ": " + err.message);
|
|
302
|
+
});
|
|
303
|
+
download.once('end', function() {
|
|
304
|
+
self.logDebug(9, "S3 stream download complete: " + key);
|
|
305
|
+
} );
|
|
306
|
+
download.once('close', function() {
|
|
307
|
+
self.logDebug(9, "S3 stream download closed: " + key);
|
|
308
|
+
} );
|
|
297
309
|
|
|
298
310
|
// double-callback protection (bug in aws-sdk)
|
|
299
311
|
var done = false;
|
|
300
|
-
this.s3.headObject( params, function(err) {
|
|
312
|
+
this.s3.headObject( params, function(err, data) {
|
|
301
313
|
if (done) return; else done = true;
|
|
302
314
|
|
|
303
315
|
if (err) {
|
|
304
316
|
if (err.code != 'NoSuchKey') {
|
|
305
317
|
self.logError('s3', "Failed to head key: " + key + ": " + err.message);
|
|
306
318
|
}
|
|
319
|
+
download.destroy();
|
|
307
320
|
callback( err, null );
|
|
308
321
|
return;
|
|
309
322
|
}
|
|
310
323
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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);
|
|
324
|
+
proceed = true;
|
|
325
|
+
callback( null, download, {
|
|
326
|
+
mod: Math.floor((new Date(data.LastModified)).getTime() / 1000),
|
|
327
|
+
len: data.ContentLength
|
|
321
328
|
} );
|
|
322
|
-
|
|
323
|
-
callback( null, download );
|
|
324
329
|
}); // headObject
|
|
325
330
|
},
|
|
326
331
|
|
|
@@ -333,34 +338,52 @@ module.exports = Class.create({
|
|
|
333
338
|
this.logDebug(9, "Fetching ranged S3 stream: " + key, { start, end });
|
|
334
339
|
|
|
335
340
|
var params = { Key: this.extKey(key, orig_key) };
|
|
341
|
+
var download = self.s3.getObject( Tools.mergeHashes(params, { Range: "bytes=" + start + "-" + end }) ).createReadStream();
|
|
342
|
+
var proceed = false;
|
|
343
|
+
|
|
344
|
+
download.on('error', function(err) {
|
|
345
|
+
if (proceed) self.logError('s3', "Failed to download key: " + key + ": " + err.message);
|
|
346
|
+
});
|
|
347
|
+
download.once('end', function() {
|
|
348
|
+
self.logDebug(9, "S3 stream download complete: " + key);
|
|
349
|
+
} );
|
|
350
|
+
download.once('close', function() {
|
|
351
|
+
self.logDebug(9, "S3 stream download closed: " + key);
|
|
352
|
+
} );
|
|
336
353
|
|
|
337
354
|
// double-callback protection (bug in aws-sdk)
|
|
338
355
|
var done = false;
|
|
339
|
-
this.s3.headObject( params, function(err) {
|
|
356
|
+
this.s3.headObject( params, function(err, data) {
|
|
340
357
|
if (done) return; else done = true;
|
|
341
358
|
|
|
342
359
|
if (err) {
|
|
343
360
|
if (err.code != 'NoSuchKey') {
|
|
344
361
|
self.logError('s3', "Failed to head key: " + key + ": " + err.message);
|
|
345
362
|
}
|
|
363
|
+
download.destroy();
|
|
346
364
|
callback( err, null );
|
|
347
365
|
return;
|
|
348
366
|
}
|
|
349
367
|
|
|
350
|
-
|
|
351
|
-
|
|
368
|
+
// validate byte range, now that we have the head info
|
|
369
|
+
if (isNaN(start) && !isNaN(end)) {
|
|
370
|
+
start = data.ContentLength - end;
|
|
371
|
+
end = data.ContentLength ? data.ContentLength - 1 : 0;
|
|
372
|
+
}
|
|
373
|
+
else if (!isNaN(start) && isNaN(end)) {
|
|
374
|
+
end = data.ContentLength ? data.ContentLength - 1 : 0;
|
|
375
|
+
}
|
|
376
|
+
if ((start < 0) || (start >= data.ContentLength) || (end < start) || (end >= data.ContentLength)) {
|
|
377
|
+
download.destroy();
|
|
378
|
+
callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + data.ContentLength + ")"), null );
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
352
381
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
self.logDebug(9, "S3 stream download complete: " + key);
|
|
358
|
-
} );
|
|
359
|
-
download.once('close', function() {
|
|
360
|
-
self.logDebug(9, "S3 stream download closed: " + key);
|
|
382
|
+
proceed = true;
|
|
383
|
+
callback( null, download, {
|
|
384
|
+
mod: Math.floor((new Date(data.LastModified)).getTime() / 1000),
|
|
385
|
+
len: data.ContentLength
|
|
361
386
|
} );
|
|
362
|
-
|
|
363
|
-
callback( null, download );
|
|
364
387
|
}); // headObject
|
|
365
388
|
},
|
|
366
389
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixl-server-storage",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.12",
|
|
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
|
@@ -375,7 +375,7 @@ module.exports = {
|
|
|
375
375
|
},
|
|
376
376
|
|
|
377
377
|
function testStream(test) {
|
|
378
|
-
test.expect(
|
|
378
|
+
test.expect(14);
|
|
379
379
|
var self = this;
|
|
380
380
|
|
|
381
381
|
var key = 'spacer-stream.gif';
|
|
@@ -395,10 +395,13 @@ module.exports = {
|
|
|
395
395
|
var tempFile = __dirname + '/' + filename + '.streamtemp';
|
|
396
396
|
var outStream = fs.createWriteStream( tempFile );
|
|
397
397
|
|
|
398
|
-
self.storage.getStream( key, function(err, storageStream) {
|
|
398
|
+
self.storage.getStream( key, function(err, storageStream, streamInfo) {
|
|
399
399
|
test.ok( !err, "No error fetching stream: " + err );
|
|
400
400
|
test.ok( !!storageStream, "Got storage stream as 2nd arg");
|
|
401
401
|
test.ok( !!storageStream.pipe, "Storage stream has a pipe");
|
|
402
|
+
test.ok( !!streamInfo, "Info was provided as the 3rd arg");
|
|
403
|
+
test.ok( streamInfo.len == 43, "Info has correct data length");
|
|
404
|
+
test.ok( streamInfo.mod > 0, "Info has a non-zero mod date");
|
|
402
405
|
|
|
403
406
|
outStream.on('finish', function() {
|
|
404
407
|
var newSpacerBuf = fs.readFileSync( tempFile );
|
|
@@ -420,6 +423,147 @@ module.exports = {
|
|
|
420
423
|
} ); // putStream
|
|
421
424
|
},
|
|
422
425
|
|
|
426
|
+
function testStreamRange(test) {
|
|
427
|
+
// grab a range from within a stream, with both start and end specified
|
|
428
|
+
test.expect(14);
|
|
429
|
+
var self = this;
|
|
430
|
+
|
|
431
|
+
var key = 'spacer-stream.gif';
|
|
432
|
+
var filename = 'spacer.gif';
|
|
433
|
+
var spacerBuf = fs.readFileSync( __dirname + '/' + filename );
|
|
434
|
+
var spacerStream = fs.createReadStream( __dirname + '/' + filename );
|
|
435
|
+
|
|
436
|
+
test.ok( !!spacerBuf, "Got buffer from file" );
|
|
437
|
+
test.ok( typeof(spacerBuf) == 'object', "Buffer is an object" );
|
|
438
|
+
test.ok( spacerBuf.length > 0, "Buffer has size" );
|
|
439
|
+
test.ok( !!spacerStream, "Got read stream" );
|
|
440
|
+
|
|
441
|
+
this.storage.putStream( key, spacerStream, function(err) {
|
|
442
|
+
test.ok( !err, "No error creating stream: " + err );
|
|
443
|
+
|
|
444
|
+
var tempFile = __dirname + '/' + filename + '.streamtemp';
|
|
445
|
+
var outStream = fs.createWriteStream( tempFile );
|
|
446
|
+
|
|
447
|
+
self.storage.getStreamRange( key, 0, 5, function(err, storageStream, streamInfo) {
|
|
448
|
+
test.ok( !err, "No error fetching stream: " + err );
|
|
449
|
+
test.ok( !!storageStream, "Got storage stream as 2nd arg");
|
|
450
|
+
test.ok( !!storageStream.pipe, "Storage stream has a pipe");
|
|
451
|
+
test.ok( !!streamInfo, "Info was provided as the 3rd arg");
|
|
452
|
+
test.ok( streamInfo.len == 43, "Info has correct data length");
|
|
453
|
+
test.ok( streamInfo.mod > 0, "Info has a non-zero mod date");
|
|
454
|
+
|
|
455
|
+
outStream.on('finish', function() {
|
|
456
|
+
var newSpacerBuf = fs.readFileSync( tempFile );
|
|
457
|
+
test.ok( newSpacerBuf.length == 6, "Stream length is correct" );
|
|
458
|
+
test.ok( newSpacerBuf.toString() == "GIF89a", "Range buffer content is correct" );
|
|
459
|
+
|
|
460
|
+
self.storage.delete( key, function(err) {
|
|
461
|
+
test.ok( !err, "No error deleting stream key: " + err );
|
|
462
|
+
fs.unlinkSync( tempFile );
|
|
463
|
+
test.done();
|
|
464
|
+
} ); // delete
|
|
465
|
+
} ); // stream finish
|
|
466
|
+
|
|
467
|
+
storageStream.pipe( outStream );
|
|
468
|
+
|
|
469
|
+
} ); // getStream
|
|
470
|
+
} ); // putStream
|
|
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
|
+
|
|
423
567
|
function testPutMulti(test) {
|
|
424
568
|
// test storing multiple keys at once
|
|
425
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:
|