@ampproject/toolbox-core
Version:
Commonly used functionality for amp-toolbox
115 lines (104 loc) • 3.31 kB
JavaScript
/**
* Copyright 2020 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
;
const fs = require('fs').promises;
const {existsSync, mkdirSync} = require('fs');
const crypto = require('crypto');
const os = require('os');
const log = require('./Log');
const LRUCache = require('lru-cache');
const path = require('path');
const DEFAULT_OPTS = {
log,
maxItems: 50,
};
class FileSystemCache {
static create(customOpts = {}) {
const opts = Object.assign(DEFAULT_OPTS, customOpts);
// Try to create a tmp directory...
try {
if (!opts.baseDir) {
opts.baseDir = path.join(os.tmpdir(), opts.name || 'amp-toolbox');
}
if (!existsSync(opts.baseDir)) {
mkdirSync(opts.baseDir);
}
} catch (e) {
log.debug('No filesystem access, falling back to in-memory cache only', e);
// ... if this fails, we have no write access to the fs, use a simple memory cache instead
return new LRUCache(opts.maxItems);
}
return new FileSystemCache(opts);
}
constructor(opts) {
this.opts = opts;
this.cache = new LRUCache(opts.maxItems);
}
async get(key, defaultValue = null) {
let value = this.cache.get(key);
if (value) {
return value;
}
const cacheFile = this.createCacheFileName(key);
try {
const content = await fs.readFile(cacheFile, 'utf-8');
value = JSON.parse(content);
this.cache.set(key, value);
} catch (error) {
value = defaultValue;
}
return value;
}
async set(key, value) {
try {
this.cache.set(key, value);
const cacheFile = this.createCacheFileName(key);
return fs.writeFile(cacheFile, JSON.stringify(value, null, ''), 'utf-8');
} catch (e) {
this.opts.log.error('Could not write cache file', e);
}
}
async clear() {
const dir = this.opts.baseDir;
if (!existsSync(dir)) {
return;
}
let entries = await fs.readdir(dir, {withFileTypes: true});
await Promise.all(
entries.map((entry) => {
let fullPath = path.join(dir, entry.name);
return entry.isFile() && entry.name.endsWith('.json')
? fs.unlink(fullPath)
: Promise.resolve();
})
);
}
createCacheFileName(key) {
const keyHash = crypto.createHash('md5').update(key).digest('hex');
return path.join(this.opts.baseDir, keyHash + '.json');
}
async deleteDir_(dir) {
let entries = await fs.readdir(dir, {withFileTypes: true});
await Promise.all(
entries.map((entry) => {
let fullPath = path.join(dir, entry.name);
return entry.isDirectory() ? this.deleteDir_(fullPath) : fs.unlink(fullPath);
})
);
await fs.rmdir(dir);
}
}
module.exports = FileSystemCache;