Skip to content

Commit 3793739

Browse files
vladimyragnivade
authored andcommitted
Use random temp folder for remote zip extraction (#210)
* Use random temp folder for remote zip extraction - generating random folder inside `/tmp/tldr/` - removing temp folders after cache has been updated
1 parent 7f72b57 commit 3793739

File tree

4 files changed

+110
-43
lines changed

4 files changed

+110
-43
lines changed

lib/cache.js

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
'use strict';
22

33
const fs = require('fs-extra');
4+
const os = require('os');
45
const path = require('path');
56
const config = require('./config');
67
const remote = require('./remote');
78
const platform = require('./platform');
89
const index = require('./index');
10+
const utils = require('./utils');
911

1012
const CACHE_FOLDER = path.join(config.get().cache, 'cache');
13+
const TEMP_FOLDER = path.join(os.tmpdir(), 'tldr');
14+
15+
exports.CACHE_FOLDER = CACHE_FOLDER;
1116

1217
exports.lastUpdated = () => {
1318
return fs.stat(CACHE_FOLDER);
@@ -33,15 +38,32 @@ exports.clear = () => {
3338
};
3439

3540
exports.update = () => {
41+
// Temporary folder path: /tmp/tldr/{randomName}
42+
let tempFolder = path.join(TEMP_FOLDER, utils.uniqueId());
43+
3644
// Downloading fresh copy
37-
return fs.ensureDir(CACHE_FOLDER)
45+
return Promise.all([
46+
// Create new temporary folder
47+
fs.ensureDir(tempFolder),
48+
fs.ensureDir(CACHE_FOLDER)
49+
])
3850
.then(() => {
39-
return remote.download();
51+
// Download and extract cache data to temporary folder
52+
return remote.download(tempFolder);
4053
})
41-
.then((tempFolder) => {
54+
.then(() => {
55+
// Copy data to cache folder
4256
return fs.copy(tempFolder, CACHE_FOLDER);
4357
})
4458
.then(() => {
45-
return index.rebuildPagesIndex();
59+
return Promise.all([
60+
// Remove temporary folder
61+
fs.remove(tempFolder),
62+
index.rebuildPagesIndex()
63+
]);
64+
})
65+
// eslint-disable-next-line no-unused-vars
66+
.then(([_, shortIndex]) => {
67+
return shortIndex;
4668
});
4769
};

lib/remote.js

Lines changed: 32 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,39 @@
11
'use strict';
22

3-
const path = require('path');
4-
const os = require('os');
5-
const fs = require('fs-extra');
3+
const unzip = require('unzip2');
64
const config = require('./config');
75

8-
// Downloads the zip file from github and extracts it to /tmp/tldr
9-
exports.download = () => {
10-
let request = require('request');
11-
let unzip = require('unzip2');
6+
let request = require('request');
7+
8+
// Downloads the zip file from github and extracts it to folder
9+
exports.download = (path) => {
1210
let url = config.get().repository;
13-
let target = path.join(os.tmpdir(), 'tldr');
14-
15-
// Empty the tmp dir
16-
return fs.emptyDir(target)
17-
.then(() => {
18-
// Creating the extractor
19-
let extractor = unzip.Extract({ path: target });
20-
21-
// Setting the proxy if set by config
22-
if (config.get().proxy) {
23-
request = request.defaults({ proxy: config.proxy });
24-
}
25-
26-
// Creating the request and passing the extractor
27-
let req = request.get({
28-
url: url,
29-
headers: { 'User-Agent' : 'tldr-node-client' }
30-
});
31-
32-
req.pipe(extractor);
33-
34-
return new Promise((resolve, reject) => {
35-
req.on('error', (err) => {
36-
reject(err);
37-
});
38-
extractor.on('error', () => {
39-
reject(new Error('Cannot update from ' + url));
40-
});
41-
extractor.on('close', () => {
42-
resolve(target);
43-
});
44-
});
11+
12+
// Creating the extractor
13+
let extractor = unzip.Extract({ path });
14+
15+
// Setting the proxy if set by config
16+
if (config.get().proxy) {
17+
request = request.defaults({ proxy: config.proxy });
18+
}
19+
20+
// Creating the request and passing the extractor
21+
let req = request.get({
22+
url: url,
23+
headers: { 'User-Agent' : 'tldr-node-client' }
24+
});
25+
26+
req.pipe(extractor);
27+
28+
return new Promise((resolve, reject) => {
29+
req.on('error', (err) => {
30+
reject(err);
31+
});
32+
extractor.on('error', () => {
33+
reject(new Error('Cannot update from ' + url));
34+
});
35+
extractor.on('close', () => {
36+
resolve();
4537
});
38+
});
4639
};

lib/utils.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const fs = require('fs-extra');
22
const path = require('path');
33
const glob = require('glob');
4+
const crypto = require('crypto');
45
const flatten = require('lodash/flatten');
56

67
module.exports = {
@@ -52,5 +53,11 @@ module.exports = {
5253
}
5354
});
5455
});
56+
},
57+
58+
// eslint-disable-next-line no-magic-numbers
59+
uniqueId(length = 32) {
60+
const size = Math.ceil(length / 2);
61+
return crypto.randomBytes(size).toString('hex').slice(0, length);
5562
}
5663
};

test/cache.spec.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const should = require('should');
55
const sinon = require('sinon');
66
const fs = require('fs-extra');
77
const index = require('../lib/index');
8+
const remote = require('../lib/remote');
89
const platform = require('../lib/platform');
910

1011

@@ -23,6 +24,50 @@ describe('Cache', () => {
2324
});
2425
});
2526

27+
describe('update()', () => {
28+
beforeEach(() => {
29+
sinon.spy(fs, 'ensureDir');
30+
sinon.spy(fs, 'remove');
31+
sinon.stub(fs, 'copy').resolves();
32+
sinon.stub(remote, 'download').resolves();
33+
sinon.stub(index, 'rebuildPagesIndex').resolves();
34+
});
35+
36+
it('should use randomly created temp folder', () => {
37+
const count = 16;
38+
return Promise.all(Array.from({ length: count }).map(() => {
39+
return cache.update();
40+
})).then(() => {
41+
let calls = fs.ensureDir.getCalls().filter((call) => {
42+
return !call.calledWith(cache.CACHE_FOLDER);
43+
});
44+
calls.should.have.length(count);
45+
let tempFolders = calls.map((call) => {
46+
return call.args[0];
47+
});
48+
tempFolders.should.have.length(new Set(tempFolders).size);
49+
});
50+
});
51+
52+
it('should remove temp folder after cache gets updated', () => {
53+
return cache.update().then(() => {
54+
let createFolder = fs.ensureDir.getCalls().find((call) => {
55+
return !call.calledWith(cache.CACHE_FOLDER);
56+
});
57+
let removeFolder = fs.remove.getCall(0);
58+
removeFolder.args[0].should.be.equal(createFolder.args[0]);
59+
});
60+
});
61+
62+
afterEach(() => {
63+
fs.ensureDir.restore();
64+
fs.remove.restore();
65+
fs.copy.restore();
66+
remote.download.restore();
67+
index.rebuildPagesIndex.restore();
68+
});
69+
});
70+
2671
describe('getPage()', () => {
2772
beforeEach(() => {
2873
sinon.stub(index, 'getShortIndex').returns({

0 commit comments

Comments
 (0)