UNPKG

homebridge-homeconnect

Version:

A Homebridge plugin that connects Home Connect appliances to Apple HomeKit

121 lines 4.28 kB
// Homebridge plugin for Home Connect home appliances // Copyright © 2019-2025 Alexander Thoukydides import { setImmediate as setImmediateP } from 'timers/promises'; import semver from 'semver'; import { MS, formatList, formatMilliseconds } from './utils.js'; import { logError } from './log-error.js'; import { PLUGIN_VERSION } from './settings.js'; // A simple persistent cache, with soft expiry export class PersistCache { log; persist; preferred; // Key used to store/retrieve the cache from persistent storage cacheName; // Wait for cache to be read from persistent storage initialised; // Saving the cache to persistent storage saving; pendingSave; // Local copy of the cache cache = new Map(); // Length of time before values in the cache expire ttl = 24 * 60 * 60 * MS; // (24 hours in milliseconds) // Initialise a cache constructor(log, persist, name, preferred) { this.log = log; this.persist = persist; this.preferred = preferred; this.cacheName = `${name} cache`; this.initialised = this.load(); } // Retrieve an item from the cache async get(key) { await this.initialised; const item = this.cache.get(key); if (!item) return; return item.value; } // Retrieve an item, if it exists checking that is has not expired async getWithExpiry(key) { await this.initialised; const item = this.cache.get(key); let description = `"${key}"`; const expired = []; if (!item) { expired.push('does not exist in cache'); } else { const age = Date.now() - item.updated; description += ` [${item.preferred}, v${item.version ?? '?'}, updated ${formatMilliseconds(age)} ago]`; if (!semver.satisfies(PLUGIN_VERSION, `^${item.version}`)) expired.push(`not written by v${PLUGIN_VERSION}`); if (item.preferred !== this.preferred) expired.push(`does not match preference ${this.preferred}`); if (this.ttl < age) expired.push('is too old'); } this.log.debug(expired.length ? `Expired cache ${description} ${formatList(expired)}` : `Cache ${description}`); return item && { value: item.value, valid: !expired.length }; } // Write an item to the cache async set(key, value) { await this.initialised; this.cache.set(key, { value: value, preferred: this.preferred, updated: Date.now(), version: PLUGIN_VERSION }); this.log.debug(`"${key}" written to cache`); await this.save(); } // Load the cache async load() { try { const cache = await this.persist.getItem(this.cacheName); if (cache) { this.cache = new Map(Object.entries(cache)); const entries = Object.keys(this.cache).length; this.log.debug(`Cache loaded (${entries} entries)`); } else { this.log.debug('No cache found'); } } catch (err) { logError(this.log, 'Cache read', err); } } // Schedule saving the cache save() { if (!this.pendingSave) { const doSave = async () => { if (this.saving) await this.saving; await setImmediateP(); this.saving = this.pendingSave; this.pendingSave = undefined; await this.saveDeferred(); this.saving = undefined; }; this.pendingSave = doSave(); } return this.pendingSave; } // Save the cache async saveDeferred() { try { await this.initialised; const cache = Object.fromEntries(this.cache); await this.persist.setItem(this.cacheName, cache); const entries = Object.keys(this.cache).length; this.log.debug(`Cache saved (${entries} entries)`); } catch (err) { logError(this.log, 'Cache write', err); } } } //# sourceMappingURL=persist-cache.js.map