Pinafore-Web-Client-Frontend/src/routes/_database/mediaUploadFileCache.js

85 lines
2.3 KiB
JavaScript

// 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.js'
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 setDeleteCachedMediaFilesAfter (newDeleteAfter) {
deleteAfter = newDeleteAfter
}