Skip to content

Commit a7d564d

Browse files
authored
Merge pull request #63 from runk/temp-files
Temp file to handle concurrent/cancelled downloads more gracefully
2 parents d64b173 + f82e6a0 commit a7d564d

5 files changed

Lines changed: 67 additions & 30 deletions

File tree

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
language: node_js
22
node_js:
3-
- "4.4"
3+
- "4.8"
44
- "6.2"
55
- "7.6"

lib/cache.js

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
'use strict';
22
var path = require('path'),
33
fs = require('fs'),
4+
os = require('os'),
45
crypto = require('crypto'),
56
mkdirp = require('mkdirp');
67

7-
88
function Cache(opts) {
99
this.opts = opts || {};
1010
this.opts.ttl = (opts.ttl || 1800) * 1000;
@@ -52,8 +52,9 @@ function Cache(opts) {
5252

5353

5454
this.read = function(key) {
55-
var path = this.getPath(key),
56-
file = fs.createReadStream(path.full);
55+
var pathInfo = this.getPath(key),
56+
file = fs.createReadStream(pathInfo.full);
57+
5758
file.on('finish', function() {
5859
file.close(nop);
5960
});
@@ -62,23 +63,32 @@ function Cache(opts) {
6263
};
6364

6465

65-
this.write = function(key) {
66+
this.write = function(key, readStream, cb) {
6667
var locks = this.locks,
67-
path = this.getPath(key);
68+
pathInfo = this.getPath(key),
69+
self = this;
6870

69-
// create lock
71+
// Create a lock
7072
locks[key] = true;
7173

72-
mkdirp.sync(path.dir, 511); // 511 is decimal equvivalent of 0777
74+
mkdirp.sync(pathInfo.dir, 511); // 511 is decimal equvivalent of 0777
7375

74-
var file = fs.createWriteStream(path.full);
75-
file.on('finish', function() {
76-
// release lock
76+
// On top of locking mechanism, doing write to a temp location, and
77+
// when it's finish moving the data file to its final destination.
78+
var tmpPath = path.join(os.tmpdir(), pathInfo.file + '-' + Math.round(Math.random() * 1e9).toString(36));
79+
80+
var writeStream = fs.createWriteStream(tmpPath);
81+
readStream.pipe(writeStream);
82+
83+
writeStream.on('finish', function() {
84+
writeStream.close(nop);
85+
86+
// Release the lock, move data file to final destination.
7787
delete (locks[key]);
78-
file.close(nop);
79-
});
88+
fs.renameSync(tmpPath, pathInfo.full);
8089

81-
return file;
90+
self.meta(key, cb);
91+
});
8292
};
8393

8494

@@ -90,7 +100,7 @@ function Cache(opts) {
90100
file = path.basename(key);
91101
// Cut the version suffix and file extension; only module name
92102
// should make the directory, make sure that there is no dot as
93-
// directory name coming from the first characters of the fike name
103+
// directory name coming from the first characters of the file name
94104
base = file.replace(/(-\d\.\d.\d)?\.tgz/, '').replace(/\./g, '-');
95105
} else {
96106
file = crypto.createHash('md5').update(key).digest('hex')

lib/proxy.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -137,14 +137,13 @@ exports.httpHandler = function(req, res) {
137137
var onResponse = function(err, response) {
138138
// don't save responses with codes other than 200
139139
if (!err && response.statusCode === 200) {
140-
var file = cache.write(dest);
141-
r.pipe(file).on('finish', function() {
142-
cache.meta(dest, function(err, meta) {
143-
if (err)
144-
throw err;
145-
respondWithCache(dest, cache, meta, res);
146-
});
140+
cache.write(dest, r, function(err, meta) {
141+
if (err)
142+
throw err;
143+
144+
respondWithCache(dest, cache, meta, res);
147145
});
146+
148147
} else {
149148
// serve expired cache if user wants so
150149
if (exports.opts.expired && meta.status === Cache.EXPIRED)

test/cache.js

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
'use strict';
22
var assert = require('assert'),
33
path = require('path'),
4+
fs = require('fs'),
45
rimraf = require('rimraf'),
56
Cache = require('../lib/cache');
67

78
describe('cache', function() {
89

910
var opts;
1011
var filepath = path.join(__dirname, '/cache');
12+
13+
var dummy = 'Lorem ipsum dolor sit amet ...\n';
14+
1115
beforeEach(function() {
12-
opts = {path: filepath, ttl: 10};
16+
opts = { path: filepath, ttl: 10 };
1317
});
1418

1519
before(function(done) {
@@ -29,24 +33,47 @@ describe('cache', function() {
2933
});
3034

3135

32-
describe('set()', function() {
33-
it('should create new write stream', function() {
36+
describe('write()', function() {
37+
var readStream = fs.createReadStream(path.join(__dirname, 'dummy.data'));
38+
39+
it('should write the file', function(done) {
3440
var cache = new Cache(opts);
35-
var file = cache.write('/-/foo/bar.dat');
36-
file.end(new Buffer('This is a test'));
41+
var key = '/-/foo/bar.dat';
42+
var pathInfo = cache.getPath(key);
43+
cache.write(key, readStream, function(err, meta) {
44+
assert.equal(meta.size, 31);
45+
assert.equal(meta.status, 4);
46+
assert.equal(fs.readFileSync(pathInfo.full, 'utf8'), dummy);
47+
done();
48+
});
49+
});
50+
51+
it('should handle locks', function(done) {
52+
var cache = new Cache(opts);
53+
var readStream = fs.createReadStream(path.join(__dirname, 'dummy.data'));
54+
var key = '/-/foo/baz.dat';
55+
var pathInfo = cache.getPath(key);
56+
cache.write(key, readStream, function(err, meta) {
57+
assert(!cache.locks[key], 'Lock should be released');
58+
assert(fs.existsSync(pathInfo.full));
59+
done();
60+
});
61+
62+
assert(cache.locks[key], 'Lock should be set');
63+
assert(!fs.existsSync(pathInfo.full));
3764
});
3865
});
3966

4067

41-
describe('get()', function() {
68+
describe('read()', function() {
4269
it('should create new read stream', function(done) {
4370
var cache = new Cache(opts);
4471
var readable = cache.read('/-/foo/bar.dat');
4572

4673
readable.setEncoding('utf8');
4774
readable.on('data', function(data) {
4875
assert.equal(typeof data, 'string');
49-
assert.equal(data.toString(), 'This is a test');
76+
assert.equal(data.toString(), dummy);
5077
done();
5178
});
5279

@@ -60,7 +87,7 @@ describe('cache', function() {
6087
var cache = new Cache(opts);
6188
cache.meta('/-/foo/bar.dat', function(err, meta) {
6289
if (err) return done(err);
63-
assert.equal(meta.size, 14);
90+
assert.equal(meta.size, 31);
6491
assert.equal(meta.type, 'application/octet-stream');
6592
assert.equal(meta.status, Cache.FRESH);
6693
done();

test/dummy.data

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lorem ipsum dolor sit amet ...

0 commit comments

Comments
 (0)