Skip to content

Commit f4bd3e8

Browse files
authored
Caching the downloaded archive (#30)
* Implement filesystem caching of downloaded binary Closes #24 * Fix failure to install multiple times on Mac * Cater for different OSs sharing node_modules Closes #25 * Update proposed filesystem caching after feedback * Turn caching on by default, using default filesystem locations * `CHROMIUM_CACHE` now overrides default cache location * Add flag `CHROMIUM_CACHE_SKIP` to disable caching * Many little refactors as suggested * Updated readme accordingly * Delete temporary files when the process exits * Refactor setEnvVar test util method * Ask wise and all-knowing friend for cache location * Rename CHROMIUM_CACHE to NODE_CHROMIUM_CACHE * Update readme * Rename cacheDir variable now cachedir is in scope dtolstyi would never let me get away with that :) * Rename all env vars CHROMIUM_* > NODE_CHROMIUM_* * More resilient progress bar and rename env var
1 parent 78f0289 commit f4bd3e8

File tree

12 files changed

+342
-83
lines changed

12 files changed

+342
-83
lines changed

README.MD

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,40 +40,40 @@ npm config set https-proxy http://<username>:<password>@<the.proxy.hostname>:<po
4040
npm config set no-proxy localhost,127.0.0.1,example.org
4141
```
4242

43-
Additionally proxy settings found in the environment variables `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY` will be used if they are not defined in the `.npmrc` file.
43+
Additionally proxy settings found in the environment variables `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY` will be used if they are not defined in the `.npmrc` file.
4444

4545
### Install a concrete revision
46-
If you want to specify the revision of Chromium to be installed, just set the environment variable `CHROMIUM_REVISION` to the number of the revision you want to install, as in:
46+
If you want to specify the revision of Chromium to be installed, just set the environment variable `NODE_CHROMIUM_REVISION` to the number of the revision you want to install, as in:
4747
```shell script
48-
export CHROMIUM_REVISION=729994
48+
export NODE_CHROMIUM_REVISION=729994
4949
```
5050

5151
Note - may also be set in .npmrc like so:
5252

5353
```ini
54-
chromium_revision=729994
54+
node_chromium_revision=729994
5555
```
5656

5757
### Use a Download Mirror
58-
You may download a specific revision from an alternate download host using the environment variable `CHROMIUM_DOWNLOAD_HOST`, for example:
58+
You may download a specific revision from an alternate download host using the environment variable `NODE_CHROMIUM_DOWNLOAD_HOST`, for example:
5959

6060
```bash
61-
export CHROMIUM_REVISION=737027
62-
export CHROMIUM_DOWNLOAD_HOST=https://npm.taobao.org/mirrors/chromium-browser-snapshots/
61+
export NODE_CHROMIUM_REVISION=737027
62+
export NODE_CHROMIUM_DOWNLOAD_HOST=https://npm.taobao.org/mirrors/chromium-browser-snapshots/
6363

6464
# If running on Linux x64 this will download binary from:
6565
# https://npm.taobao.org/mirrors/chromium-browser-snapshots/Linux_x64/737027/chrome-linux.zip?alt=media
6666
```
6767

68-
Notes on `CHROMIUM_DOWNLOAD_HOST`:
68+
Notes on `NODE_CHROMIUM_DOWNLOAD_HOST`:
6969

7070
* The default download host is `https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/`
71-
* Mirrors are expected to host binaries in the structure: `<CHROMIUM_DOWNLOAD_HOST>/<PLATFORM_ARCHITECTURE>/<REVISION>/<OS_CHROMIUM_FILE_NAME>.zip?alt=media` for example see the taobao mirror [chromium-browser-snapshots](https://npm.taobao.org/mirrors/chromium-browser-snapshots/).
71+
* Mirrors are expected to host binaries in the structure: `<NODE_CHROMIUM_DOWNLOAD_HOST>/<PLATFORM_ARCHITECTURE>/<REVISION>/<OS_CHROMIUM_FILE_NAME>.zip?alt=media` for example see the taobao mirror [chromium-browser-snapshots](https://npm.taobao.org/mirrors/chromium-browser-snapshots/).
7272
* May also be set in .npmrc like so:
7373

7474
```ini
75-
chromium_download_host=https://npm.taobao.org/mirrors/chromium-browser-snapshots/
76-
chromium_revision=737027
75+
node_chromium_download_host=https://npm.taobao.org/mirrors/chromium-browser-snapshots/
76+
node_chromium_revision=737027
7777
```
7878

7979
## Selenium WebDriver Headless (without UI) tests
@@ -121,7 +121,27 @@ async function takeScreenshot(driver, name) {
121121

122122
start();
123123
```
124-
##
124+
125+
### Cache Downloaded Binaries
126+
By default downloaded chromium binaries are cached in the appropriate cache directory for your operating system.
127+
128+
You may override the cache path by setting the `NODE_CHROMIUM_CACHE_PATH` environment variable to a directory path, for example:
129+
130+
```bash
131+
export NODE_CHROMIUM_CACHE_PATH=/path/to/cache/dir/
132+
133+
# or in .npmrc like so:
134+
# node_chromium_cache_path=/path/to/cache/dir/
135+
```
136+
137+
You may disable caching by setting `NODE_CHROMIUM_CACHE_DISABLE` to `true`:
138+
139+
```bash
140+
export NODE_CHROMIUM_CACHE_DISABLE=true
141+
142+
# or in .npmrc like so:
143+
# node_chromium_cache_disable=true
144+
```
125145

126146
## License
127147
MIT

cache.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
'use strict';
2+
const path = require('path');
3+
const fs = require('fs');
4+
const cachedir = require('cachedir');
5+
const config = require('./config');
6+
7+
/**
8+
* Retrieve a Chromium archive from filesystem cache.
9+
* @param {string} revision The Chromium revision to retrieve.
10+
* @returns {string} The path to the cached Chromium archive. Falsy if not found.
11+
*/
12+
function get(revision) {
13+
const cachePath = buildCachePath(revision);
14+
if (fs.existsSync(cachePath)) {
15+
return cachePath;
16+
}
17+
18+
return '';
19+
}
20+
21+
/**
22+
* Store a Chromium archive in filesystem cache for future use.
23+
* Has no effect if the user has not configured a cache location.
24+
* @param {string} revision The Chromium revision in the archive file.
25+
* @param {string} filePath The path to the Chromium archive file to store in cache.
26+
*/
27+
function put(revision, filePath) {
28+
const cachePath = buildCachePath(revision);
29+
if (cachePath && filePath) {
30+
try {
31+
fs.mkdirSync(path.dirname(cachePath), {recursive: true});
32+
fs.copyFileSync(filePath, cachePath);
33+
} catch (error) {
34+
// Don't die on cache fail
35+
console.error('Could not cache file', cachePath, error);
36+
}
37+
}
38+
}
39+
40+
/**
41+
* Get the unique cache path for this revision, on this platform and architecture.
42+
* @param {string} revision The revision of this Chromium binary, essentially a unique cache key.
43+
* @returns {string} The cache path, or falsy if caching is not enabled.
44+
*/
45+
function buildCachePath(revision) {
46+
if (!revision || config.getEnvVar('NODE_CHROMIUM_CACHE_DISABLE').toLowerCase() === 'true') {
47+
return '';
48+
}
49+
50+
const cachePath = config.getEnvVar('NODE_CHROMIUM_CACHE_PATH') || cachedir('node-chromium');
51+
return path.join(cachePath, `chromium-${revision}-${process.platform}-${process.arch}.zip`);
52+
}
53+
54+
module.exports = {
55+
get,
56+
put
57+
};

index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ function getBinaryPath() {
1515
}
1616

1717
module.exports = {
18-
path: getBinaryPath()
18+
path: getBinaryPath(),
19+
install: require('./install')
1920
};

install.js

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

3+
const path = require('path');
34
const fs = require('fs');
45
const extractZip = require('extract-zip');
56
const got = require('got');
67
const tmp = require('tmp');
78
const debug = require('debug')('node-chromium');
9+
const rimraf = require('rimraf');
810
const ProgressBar = require('progress');
911

1012
const config = require('./config');
1113
const utils = require('./utils');
14+
const cache = require('./cache');
1215

1316
let progressBar = null;
1417

@@ -27,13 +30,26 @@ function createTempFile() {
2730
});
2831
}
2932

33+
/**
34+
* Downloads the Chromium archive from the default CDN or mirror if configured.
35+
* If the required archive is retrieved from the cache directory then the download will be skipped.
36+
* @param {string} revision The Chromium revision to download.
37+
*/
3038
async function downloadChromiumRevision(revision) {
31-
const tmpPath = await createTempFile();
39+
const cacheEntry = cache.get(revision);
40+
if (cacheEntry) {
41+
debug('Found Chromium archive in cache, skipping download');
42+
43+
return Promise.resolve(cacheEntry);
44+
}
3245

3346
debug('Downloading Chromium archive from Google CDN');
3447
const url = utils.getDownloadUrl(revision);
35-
36-
return _downloadFile(url, tmpPath);
48+
const tmpPath = await createTempFile();
49+
return _downloadFile(url, tmpPath).then(tmpPath => {
50+
cache.put(revision, tmpPath);
51+
return tmpPath;
52+
});
3753
}
3854

3955
function _downloadFile(url, destPath) {
@@ -60,54 +76,63 @@ function _downloadFile(url, destPath) {
6076
* @param progress Information about progress so far.
6177
*/
6278
function onProgress(progress) {
79+
const fakeProgressBar = {tick: () => {}};
6380
try {
6481
if (!progressBar) {
6582
const formatBytes = bytes => {
6683
const mb = bytes / 1024 / 1024;
6784
return `${Math.round(mb * 10) / 10} MB`;
6885
};
6986

70-
progressBar = new ProgressBar(`Downloading Chromium - ${formatBytes(progress.total)} [:bar] :percent :etas `, {
71-
width: 20,
72-
total: progress.total
73-
});
87+
if (progress.total) {
88+
progressBar = new ProgressBar(`Downloading Chromium - ${formatBytes(progress.total)} [:bar] :percent :etas `, {
89+
width: 20,
90+
total: progress.total
91+
});
92+
} else {
93+
progressBar = fakeProgressBar;
94+
console.info('\tPlease wait, this may take a while...');
95+
}
7496
}
7597

7698
progressBar.tick(progress.transferred - progressBar.curr);
7799
} catch (error) {
78100
// Don't die on progress bar failure, log it and stop progress
79101
console.error('Error displaying progress bar. Continuing anyway...', error);
80-
progressBar = {tick: () => {}};
102+
progressBar = fakeProgressBar;
81103
}
82104
}
83105

84106
function unzipArchive(archivePath, outputFolder) {
85107
debug('Started extracting archive', archivePath);
86108

87109
return new Promise((resolve, reject) => {
88-
extractZip(archivePath, {dir: outputFolder}, error => {
89-
if (error) {
90-
console.error('An error occurred while trying to extract archive', error);
91-
reject(error);
92-
} else {
93-
debug('Archive was successfully extracted');
94-
resolve(true);
95-
}
110+
const osOutputFolder = path.join(outputFolder, utils.getOsChromiumFolderName());
111+
rimraf(osOutputFolder, () => {
112+
extractZip(archivePath, {dir: outputFolder}, error => {
113+
if (error) {
114+
console.error('An error occurred while trying to extract archive', error);
115+
reject(error);
116+
} else {
117+
debug('Archive was successfully extracted');
118+
resolve(true);
119+
}
120+
});
96121
});
97122
});
98123
}
99124

100125
async function install() {
101-
const chromiumRevision = config.getEnvVar('CHROMIUM_REVISION');
126+
const chromiumRevision = config.getEnvVar('NODE_CHROMIUM_REVISION');
102127
try {
103128
console.info('Step 1. Retrieving Chromium revision number');
104129
const revision = chromiumRevision || await utils.getLatestRevisionNumber();
105130

106131
console.info(`Step 2. Downloading Chromium revision ${revision}`);
107-
const tmpPath = await downloadChromiumRevision(revision);
132+
const archivePath = await downloadChromiumRevision(revision);
108133

109134
console.info('Step 3. Setting up Chromium binaries');
110-
await unzipArchive(tmpPath, config.BIN_OUT_PATH);
135+
await unzipArchive(archivePath, config.BIN_OUT_PATH);
111136

112137
console.info('Process is successfully finished');
113138
} catch (error) {
@@ -120,4 +145,6 @@ if (require.main === module) {
120145
install();
121146
}
122147

148+
tmp.setGracefulCleanup(); // Ensure temporary files are cleaned up when process exits
149+
123150
module.exports = install;

package-lock.json

Lines changed: 9 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)