147 lines
5.3 KiB
JavaScript
147 lines
5.3 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
Object.defineProperty(exports, "__esModule", {
|
||
|
value: true
|
||
|
});
|
||
|
exports.getCacheDir = exports.Cacher = undefined;
|
||
|
|
||
|
var _fs;
|
||
|
|
||
|
function _load_fs() {
|
||
|
return _fs = _interopRequireDefault(require('mz/fs'));
|
||
|
}
|
||
|
|
||
|
var _mkdirpPromise;
|
||
|
|
||
|
function _load_mkdirpPromise() {
|
||
|
return _mkdirpPromise = _interopRequireDefault(require('mkdirp-promise'));
|
||
|
}
|
||
|
|
||
|
var _os = _interopRequireDefault(require('os'));
|
||
|
|
||
|
var _path = _interopRequireDefault(require('path'));
|
||
|
|
||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||
|
|
||
|
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
|
||
|
|
||
|
/*
|
||
|
A Cacher is used to wrap a fallible or expensive function and to memoize its results on disk
|
||
|
in case it either fails or we don't need fresh results very often. It stores objects in JSON, and
|
||
|
parses JSON from disk when returning an object.
|
||
|
|
||
|
It's constructed with a "refresher" callback which will be called for the results, a filename to use
|
||
|
for the cache, and an optional TTL and boostrap file. The TTL (in milliseconds) can be used to speed
|
||
|
up slow calls from the cache (for example checking npm published versions can be very slow). The
|
||
|
bootstrap file can be used to "seed" the cache with a particular value stored in a file.
|
||
|
|
||
|
If there is a problem calling the refresher function or in performing the cache's disk I/O, errors
|
||
|
will be stored in variables on the class. The only times Cacher will throw an exception are if it's
|
||
|
not possible to create the cache directory (usually weird home directory permissions), or if getAsync()
|
||
|
is called but no value can be provided. The latter will only occur if the refresher fails, no cache
|
||
|
is available on disk (i.e. this is the first call or it has been recently cleared), and bootstrapping
|
||
|
was not available (either a bootstrap file wasn't provided or reading/writing failed).
|
||
|
|
||
|
See src/__tests__/tools/FsCache-test.js for usage examples.
|
||
|
*/
|
||
|
class Cacher {
|
||
|
|
||
|
constructor(refresher, filename, ttlMilliseconds, bootstrapFile) {
|
||
|
this.refresher = refresher;
|
||
|
this.filename = _path.default.join(getCacheDir(), filename);
|
||
|
this.ttlMilliseconds = ttlMilliseconds || 0;
|
||
|
this.bootstrapFile = bootstrapFile;
|
||
|
}
|
||
|
|
||
|
getAsync() {
|
||
|
var _this = this;
|
||
|
|
||
|
return _asyncToGenerator(function* () {
|
||
|
yield (0, (_mkdirpPromise || _load_mkdirpPromise()).default)(getCacheDir());
|
||
|
|
||
|
let mtime;
|
||
|
try {
|
||
|
const stats = yield (_fs || _load_fs()).default.stat(_this.filename);
|
||
|
mtime = stats.mtime;
|
||
|
} catch (e) {
|
||
|
if (_this.bootstrapFile) {
|
||
|
try {
|
||
|
const bootstrapContents = (yield (_fs || _load_fs()).default.readFile(_this.bootstrapFile)).toString();
|
||
|
yield (_fs || _load_fs()).default.writeFile(_this.filename, bootstrapContents, 'utf8');
|
||
|
} catch (e) {
|
||
|
// intentional no-op
|
||
|
}
|
||
|
}
|
||
|
mtime = new Date(1989, 10, 19);
|
||
|
}
|
||
|
|
||
|
let fromCache;
|
||
|
let failedRefresh = null;
|
||
|
|
||
|
// if mtime + ttl >= now, attempt to fetch the value, otherwise read from disk
|
||
|
if (new Date() - mtime > _this.ttlMilliseconds) {
|
||
|
try {
|
||
|
fromCache = yield _this.refresher();
|
||
|
try {
|
||
|
yield (_fs || _load_fs()).default.writeFile(_this.filename, JSON.stringify(fromCache), 'utf8');
|
||
|
} catch (e) {
|
||
|
_this.writeError = e;
|
||
|
// do nothing, if the refresh succeeded it'll be returned, if the persist failed we don't care
|
||
|
}
|
||
|
} catch (e) {
|
||
|
failedRefresh = e;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!fromCache) {
|
||
|
try {
|
||
|
fromCache = JSON.parse((yield (_fs || _load_fs()).default.readFile(_this.filename)));
|
||
|
} catch (e) {
|
||
|
_this.readError = e;
|
||
|
// if this fails then we've exhausted our options and it should remain null
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (fromCache) {
|
||
|
return fromCache;
|
||
|
} else {
|
||
|
if (failedRefresh) {
|
||
|
throw new Error(`Unable to perform cache refresh for ${_this.filename}: ${failedRefresh}`);
|
||
|
} else {
|
||
|
throw new Error(`Unable to read ${_this.filename}. ${_this.readError || ''}`);
|
||
|
}
|
||
|
}
|
||
|
})();
|
||
|
}
|
||
|
|
||
|
clearAsync() {
|
||
|
var _this2 = this;
|
||
|
|
||
|
return _asyncToGenerator(function* () {
|
||
|
try {
|
||
|
yield (_fs || _load_fs()).default.unlink(_this2.filename);
|
||
|
} catch (e) {
|
||
|
_this2.writeError = e;
|
||
|
}
|
||
|
})();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function getCacheDir() {
|
||
|
const homeDir = _os.default.homedir();
|
||
|
if (process.env.XDG_CACHE_HOME) {
|
||
|
return process.env.XDG_CACHE_HOME;
|
||
|
} else if (process.platform === 'win32') {
|
||
|
return _path.default.join(homeDir, 'AppData', 'Local', 'Expo');
|
||
|
} else if (process.platform === 'darwin') {
|
||
|
// too many mac users have broken permissions on their ~/.cache directory
|
||
|
return _path.default.join(homeDir, '.expo', 'cache');
|
||
|
} else {
|
||
|
return _path.default.join(homeDir, '.cache', 'expo');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
exports.Cacher = Cacher;
|
||
|
exports.getCacheDir = getCacheDir;
|
||
|
//# sourceMappingURL=../__sourcemaps__/tools/FsCache.js.map
|