Skip to content

Instantly share code, notes, and snippets.

@erdesigns-eu
Last active August 26, 2022 07:46
Show Gist options
  • Save erdesigns-eu/7a962da1b167c2f801f4fda9ffc42509 to your computer and use it in GitHub Desktop.
Save erdesigns-eu/7a962da1b167c2f801f4fda9ffc42509 to your computer and use it in GitHub Desktop.
Simple File Cache
// Import os module
const os = require('os');
// Import fs module
const fs = require('fs');
// Import path module
const path = require('node:path');
/**
* Simple file cache
*/
module.exports = class FileCache {
// Cache object
#cache = Object.create(null);
// Cache size
#size = 0;
// Default cache timeout duration (1 Day)
#timeout = 86400000;
// Temp files directory
#tempDir = path.join(os.tmpdir(), 'FileCache');
// Class constructor
constructor(options) {
const vm = this;
// Set default cache timeout
if (options && options.timeout && !isNaN(options.timeout)) {
vm.#timeout = parseInt(`${options.timeout}`);
}
// Override temp directory
if (options && options.tempDir && options.tempDir.length) {
vm.#tempDir = `${options.tempDir}`;
}
// If temp directory does not exist, create it
if (!fs.existsSync(this.#tempDir)){
fs.mkdirSync(this.#tempDir, { recursive: true });
}
// Clear "old" cache files, if any..
fs.readdirSync(this.#tempDir).forEach((fn) => fs.rmSync(path.format({
dir : this.#tempDir,
base : fn
})));
}
// Save response stream to file, and add to cache
put(key, filename, res, timeout) {
// Validate timeout
if (typeof timeout !== 'undefined' && (typeof timeout !== 'number' || isNaN(timeout) || timeout <= 0)) {
throw new Error('Cache timeout must be a positive number');
}
// Reference to cache (for timeout functon)
var cache = this.#cache;
// Reference to size (for timeout function)
var size = this.#size;
// Clear "old" timeout if record already exists in the cache
var oldRecord = cache[key];
if (oldRecord) {
clearTimeout(oldRecord.timeout);
} else {
this.#size++;
}
// Construct filename
const fn = path.format({
dir : this.#tempDir,
base : filename
});
// Create new record for the cache
const expire = timeout ? timeout : this.#timeout;
var record = {
filename : fn,
expire : Date.now() + expire,
timeout : setTimeout(() => {
size--;
delete cache[key];
fs.unlinkSync(fn);
}, expire)
}
// Write response stream to file
res.on('pipe', (readable) => {
readable.pipe(fs.createWriteStream(fn));
});
// Attach record to cache
cache[key] = record;
}
// Delete from cache
del(key) {
// Reference to cache
var cache = this.#cache;
// Reference to size
var size = this.#size;
// Can delete key?
var canDelete = true;
// Remove timeout from record
var oldRecord = cache[key];
if (oldRecord) {
clearTimeout(oldRecord.timeout);
if (!isNaN(oldRecord.expire) && oldRecord.expire < Date.now()) {
canDelete = false;
}
} else {
canDelete = false;
}
// Delete key from cache and remove file
if (canDelete) {
size--;
fs.unlinkSync(cache[key].filename);
delete cache[key];
}
// Return success
return canDelete;
}
// Clear cache
clear() {
// Reference to cache
var cache = this.#cache;
// Loop over keys in cache, and remove all timeouts and delete files
for (var key in cache) {
fs.unlinkSync(cache[key].filename);
clearTimeout(cache[key].timeout);
}
// Reset size
size = 0;
// Reset cache object
cache = Object.create(null);
}
// Get file stream of key from cache
get(key, res) {
// Reference to cache
var cache = this.#cache;
// Get record if not expired
var record = cache[key];
if (typeof record !== 'undefined') {
if (isNaN(record.expire) || record.expire >= Date.now()) {
fs.createReadStream(record.filename).pipe(res);
} else {
size--;
delete cache[key];
res.status(404);
}
}
}
// Check if key exists in cache
exists(key) {
// Reference to cache
var cache = this.#cache;
// Get record if not expired
var record = cache[key];
// Return exists and not expired
return typeof record !== 'undefined' && (isNaN(record.expire) || record.expire >= Date.now());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment