// Keep an LRU cache of recently-uploaded files for OCR. // We keep them in IDB to avoid tainted canvas errors after a refresh. // https://github.com/nolanlawson/pinafore/issues/1901 import { get, set, keys, del } from '../_thirdparty/idb-keyval/idb-keyval' const PREFIX = 'media-cache-' const DELIMITER = '-cache-' const LIMIT = 4 // you can upload 4 images per post, this seems reasonable despite cross-instance usage export const DELETE_AFTER = 604800000 // 7 days let deleteAfter = DELETE_AFTER function keyToData (key) { key = key.substring(PREFIX.length) const index = key.indexOf(DELIMITER) // avoiding str.split() to not have to worry about ids containing the delimiter string somehow return [key.substring(0, index), key.substring(index + DELIMITER.length)] } function dataToKey (timestamp, id) { return `${PREFIX}${timestamp}${DELIMITER}${id}` } async function getAllKeys () { return (await keys()).filter(key => key.startsWith(PREFIX)).sort() } export async function getCachedMediaFile (id) { const allKeys = await getAllKeys() for (const key of allKeys) { const otherId = keyToData(key)[1] if (id === otherId) { return get(key) } } } export async function setCachedMediaFile (id, file) { const allKeys = await getAllKeys() if (allKeys.map(keyToData).map(_ => _[1]).includes(id)) { return // do nothing, it's already in there } while (allKeys.length >= LIMIT) { // already sorted in chronological order, so delete the oldest await del(allKeys.shift()) } // delete anything that's too old, while we're at it for (const key of allKeys) { const timestamp = keyToData(key)[0] if (Date.now() - Date.parse(timestamp) >= deleteAfter) { await del(key) } } const key = dataToKey(new Date().toISOString(), id) await set(key, file) } export async function deleteCachedMediaFile (id) { const allKeys = await getAllKeys() for (const key of allKeys) { const otherId = keyToData(key)[1] if (otherId === id) { await del(key) } } } // The following are only used in tests export async function getAllCachedFileIds () { return (await getAllKeys()).map(keyToData).map(_ => _[1]) } export function setDeleteAfter (newDeleteAfter) { deleteAfter = newDeleteAfter }