'use strict'; /** * Caching of previously downloaded firmwares and release descriptions * * Depends on LRUMap for which the docs can be found here: * https://github.com/rsms/js-lru */ /** * @typedef {object} Descriptor Release descriptor object * @property {string} releaseUrl * @property {string} name * @property {string} version * @property {string} url * @property {string} file * @property {string} target * @property {string} date * @property {string} notes * @property {string} status * @see buildBoardOptions() in {@link release_checker.js} */ /** * @typedef {object} CacheItem * @property {Descriptor} release * @property {string} hexdata */ /** * Manages caching of downloaded firmware files */ let FirmwareCache = (function () { let onPutToCacheCallback, onRemoveFromCacheCallback; let JournalStorage = (function () { let CACHEKEY = "firmware-cache-journal"; /** * @param {Array} data LRU key-value pairs */ function persist(data) { let obj = {}; obj[CACHEKEY] = data; chrome.storage.local.set(obj); } /** * @param {Function} callback */ function load(callback) { chrome.storage.local.get(CACHEKEY, obj => { let entries = typeof obj === "object" && obj.hasOwnProperty(CACHEKEY) ? obj[CACHEKEY] : []; callback(entries); }); } return { persist: persist, load: load, }; })(); let journal = new LRUMap(100), journalLoaded = false; journal.shift = function () { // remove cached data for oldest release let oldest = LRUMap.prototype.shift.call(this); if (oldest === undefined) { return undefined; } let key = oldest[0]; let cacheKey = withCachePrefix(key); chrome.storage.local.get(cacheKey, obj => { /** @type {CacheItem} */ let cached = typeof obj === "object" && obj.hasOwnProperty(cacheKey) ? obj[cacheKey] : null; chrome.storage.local.remove(cacheKey, () => { onRemoveFromCache(cached.release); console.debug("Cache data removed: " + cacheKey); }); }); return oldest; }; /** * @param {Descriptor} release * @returns {string} A key used to store a release in the journal */ function keyOf(release) { return release.file; } /** * @param {string} key * @returns {string} A key for storing cached data for a release */ function withCachePrefix(key) { return "cache:" + key; } /** * @param {Descriptor} release * @returns {boolean} */ function has(release) { if (!journalLoaded) { console.warn("Cache not yet loaded"); return false; } return journal.has(keyOf(release)); } /** * @param {Descriptor} release * @param {string} hexdata */ function put(release, hexdata) { if (!journalLoaded) { console.warn("Cache journal not yet loaded"); return; } let key = keyOf(release); if (has(release)) { console.debug("Firmware is already cached: " + key); return; } journal.set(key, true); JournalStorage.persist(journal.toJSON()); let obj = {}; obj[withCachePrefix(key)] = { release: release, hexdata: hexdata, }; chrome.storage.local.set(obj, () => { console.info("Release put to cache: " + key); onPutToCache(release); }); } /** * @param {Descriptor} release * @param {Function} callback */ function get(release, callback) { if (!journalLoaded) { console.warn("Cache journal not yet loaded"); return undefined; } let key = keyOf(release); if (!has(release)) { console.debug("Firmware is not cached: " + key); return; } let cacheKey = withCachePrefix(key); chrome.storage.local.get(cacheKey, obj => { /** @type {CacheItem} */ let cached = typeof obj === "object" && obj.hasOwnProperty(cacheKey) ? obj[cacheKey] : null; callback(cached); }); } /** * @param {Descriptor} release */ function onPutToCache(release) { if (typeof onPutToCacheCallback === "function") { onPutToCacheCallback(release); } } /** * @param {Descriptor} release */ function onRemoveFromCache(release) { if (typeof onRemoveFromCacheCallback === "function") { onRemoveFromCacheCallback(release); } } /** * @param {Array} entries */ function onEntriesLoaded(entries) { let pairs = []; for (let entry of entries) { pairs.push([entry.key, entry.value]); } journal.assign(pairs); journalLoaded = true; console.info("Firmware cache journal loaded; number of entries: " + entries.length); } return { has: has, put: put, get: get, onPutToCache: callback => onPutToCacheCallback = callback, onRemoveFromCache: callback => onRemoveFromCacheCallback = callback, load: () => { JournalStorage.load(onEntriesLoaded); }, flush: () => { JournalStorage.persist(journal.toJSON()); journal.clear(); }, }; })();