Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: fix tainted canvas error with OCR (#1902)
* fix: fix tainted canvas error with OCR fixes #1901 * fix: minor tweaks
- Loading branch information
1 parent
d3ce112
commit 69aad56
Showing
7 changed files
with
176 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,84 @@ | ||
// keep a cache of files for the most recent uploads to avoid | ||
// re-downloading them for OCR | ||
// 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 { QuickLRU } from '../_thirdparty/quick-lru/quick-lru' | ||
import { get, set, keys, del } from '../_thirdparty/idb-keyval/idb-keyval' | ||
|
||
export const mediaUploadFileCache = new QuickLRU({ maxSize: 4 }) | ||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/* global it describe beforeEach */ | ||
|
||
import '../indexedDBShims' | ||
import assert from 'assert' | ||
import { | ||
getCachedMediaFile, setCachedMediaFile, deleteCachedMediaFile, getAllCachedFileIds, setDeleteAfter, DELETE_AFTER | ||
} from '../../src/routes/_utils/mediaUploadFileCache' | ||
|
||
describe('test-database.js', function () { | ||
this.timeout(60000) | ||
|
||
beforeEach(async () => { | ||
for (const key of await getAllCachedFileIds()) { | ||
await deleteCachedMediaFile(key) | ||
} | ||
setDeleteAfter(DELETE_AFTER) | ||
}) | ||
|
||
it('can store media files', async () => { | ||
await setCachedMediaFile('woot', 'woot') | ||
const result = await getCachedMediaFile('woot') | ||
assert.deepStrictEqual(result, 'woot') | ||
await deleteCachedMediaFile('woot') | ||
const result2 = await getCachedMediaFile('woot') | ||
assert.deepStrictEqual(result2, undefined) | ||
}) | ||
|
||
it('does nothing if you set() the same id twice', async () => { | ||
await setCachedMediaFile('woot', 'woot') | ||
await setCachedMediaFile('woot', 'woot2') | ||
const result = await getCachedMediaFile('woot') | ||
assert.deepStrictEqual(result, 'woot') | ||
}) | ||
|
||
it('returns undefined if not found', async () => { | ||
const result = await getCachedMediaFile('woot') | ||
assert.deepStrictEqual(result, undefined) | ||
}) | ||
|
||
it('does nothing when deleting an unfound key', async () => { | ||
await deleteCachedMediaFile('doesnt-exist') | ||
}) | ||
|
||
it('only stores up to 4 files', async () => { | ||
for (let i = 0; i < 10; i++) { | ||
await new Promise(resolve => setTimeout(resolve, 4)) // delay to avoid timing collisions | ||
await setCachedMediaFile(i.toString(), i) | ||
} | ||
const ids = await getAllCachedFileIds() | ||
assert.deepStrictEqual(ids, [6, 7, 8, 9].map(_ => _.toString())) | ||
}) | ||
|
||
it('deletes old files during set()', async () => { | ||
setDeleteAfter(0) | ||
await setCachedMediaFile('woot', 'woot') | ||
await setCachedMediaFile('woot2', 'woot2') | ||
assert.deepStrictEqual(await getCachedMediaFile('woot'), undefined) | ||
assert.deepStrictEqual(await getCachedMediaFile('woot2'), 'woot2') | ||
}) | ||
}) |
69aad56
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs: