@stencila/jesta
Version:
Stencila plugin for executable documents using JavaScript
125 lines (124 loc) • 4.17 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.cache = exports.Cache = void 0;
const crypto_1 = __importDefault(require("crypto"));
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const util_1 = require("util");
const dirs_1 = require("../util/dirs");
/**
* Cache class that implements the methods required to
* act as a [Keyv](https://github.com/lukechilds/keyv) storage adapter.
*
* Stores values as files in a Stencila cache directory intended to
* be used across applications and plugins. Removes the least
* recently accessed files to maintain the size of the cache below a
* defined size.
*
* Note that this differs from https://github.com/zaaack/keyv-file in that
* if persists values across processes. This is important for CLI applications
* since you do not want to create a new cache for every invocation (as
* does `keyv-file` by default).
*/
class Cache {
/**
* Create the cache.
*/
constructor() {
/**
* The maximum size of the cache (MiB).
*/
this.maximumSize = 100;
/**
* The amount of time between cleanup checks (seconds).
*/
this.cleanupInterval = 120;
this.dir = dirs_1.cache();
}
/**
* Generates a file name within the cache directory.
*
* Use a hash to avoid invalid characters and names
* that are too long. Use SHA1 because faster than SHA256
* and does not need to be secure.
*/
filename(key) {
const filename = crypto_1.default.createHash('sha1').update(key).digest('hex');
return path_1.default.join(this.dir, filename);
}
/**
* Set a value to be cached.
*/
set(key, value) {
fs_1.default.writeFileSync(this.filename(key), value, 'utf8');
process.nextTick(() => this.cleanup());
}
/**
* Get a value from the cache.
*/
get(key) {
try {
return fs_1.default.readFileSync(this.filename(key), 'utf8');
}
catch (error) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (error.code === 'ENOENT')
return undefined;
throw error;
}
}
/**
* Delete a value from the cache.
*/
delete(key) {
try {
fs_1.default.unlinkSync(this.filename(key));
}
catch (error) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (error.code !== 'ENOENT')
throw error;
}
return true;
}
/**
* Clear the cache completely.
*/
clear() {
return fs_1.default.rmdirSync(this.dir, { recursive: true });
}
/**
* If the cache directory has gone over the maximum size remove
* the files that are least recently accessed.
*/
cleanup() {
const doit = async () => {
const names = await util_1.promisify(fs_1.default.readdir)(this.dir);
const files = await Promise.all(names.map(async (name) => {
const path_ = path_1.default.join(this.dir, name);
const stats = await util_1.promisify(fs_1.default.stat)(path_);
return {
path: path_,
stats,
};
}));
const total = files.reduce((prev, file) => prev + file.stats.size, 0) / 1e6;
if (total > this.maximumSize) {
const sorted = files.sort((a, b) => a.stats.atimeMs - b.stats.atimeMs);
let remain = total;
for (const file of sorted) {
if (remain <= this.maximumSize)
break;
fs_1.default.unlinkSync(file.path);
remain -= file.stats.size;
}
}
};
doit().catch(console.error);
}
}
exports.Cache = Cache;
exports.cache = new Cache();