diff --git a/frontend/src/types/workers/exiftoolWorkerJob.ts b/frontend/src/types/workers/exiftoolWorkerJob.ts new file mode 100644 index 0000000..275f945 --- /dev/null +++ b/frontend/src/types/workers/exiftoolWorkerJob.ts @@ -0,0 +1,3 @@ +export type ExiftoolWorkerJob = + | { type: 'init' } + | { type: 'read-metadata'; id: string; file: File }; diff --git a/frontend/src/workers/exiftoolWorker.ts b/frontend/src/workers/exiftoolWorker.ts index 603a4e3..a383a04 100644 --- a/frontend/src/workers/exiftoolWorker.ts +++ b/frontend/src/workers/exiftoolWorker.ts @@ -2,11 +2,15 @@ import zeroperlWasmUrl from '@6over3/zeroperl-ts/zeroperl.wasm?url'; import { parseMetadata } from '@uswriting/exiftool'; import { EXIFTOOL_WORKER_INIT_JPEG } from '@/consts/exiftoolWorkerInitJpeg'; import type { ExiftoolWorkerInMessage } from '@/types/workers/exiftoolWorkerInMessage'; +import type { ExiftoolWorkerJob } from '@/types/workers/exiftoolWorkerJob'; import { resolveErrorMessage } from '@/utils/resolveErrorMessage'; /** Custom fetch function for `@uswriting/exiftool` to load `zeroperl.wasm` from the worker. */ const exiftoolParseOptions = { fetch: () => fetch(zeroperlWasmUrl) }; +const jobQueue: ExiftoolWorkerJob[] = []; +let drainPromise: Promise | null = null; + let initialized = false; let initializeWorkerPromise: Promise | null = null; @@ -20,8 +24,6 @@ let initializeWorkerPromise: Promise | null = null; * We run `parseMetadata` on {@link EXIFTOOL_WORKER_INIT_JPEG} so the library performs * a real read without needing a user `File`. Output is discarded; sets {@link initialized}. * - * Concurrent callers (init message, read-metadata, future write) share one in-flight warmup. - * * @throws When warmup `parseMetadata` fails (network, WASM, or ExifTool error). * @see Main thread `initializeExiftool()` posts `{ type: 'init' }` when idle. */ @@ -54,10 +56,36 @@ const initializeWorker = async (): Promise => { return initializeWorkerPromise; }; -self.addEventListener('message', async (event: MessageEvent) => { - const message = event.data; +const enqueue = (job: ExiftoolWorkerJob): void => { + jobQueue.push(job); - if (message.type === 'init') { + if (!drainPromise) { + drainPromise = (async () => { + try { + await drain(); + } catch { + console.error('Failed to drain ExifTool worker'); + } + + drainPromise = null; + })(); + } +}; + +const drain = async (): Promise => { + while (jobQueue.length > 0) { + const job = jobQueue.shift(); + + if (!job) { + return; + } + + await processJob(job); + } +}; + +const processJob = async (job: ExiftoolWorkerJob): Promise => { + if (job.type === 'init') { try { await initializeWorker(); @@ -72,25 +100,39 @@ self.addEventListener('message', async (event: MessageEvent) => { + const message = event.data; + + if (message.type === 'init') { + enqueue({ type: 'init' }); + + return; + } + + if (message.type === 'read-metadata') { + enqueue({ type: 'read-metadata', id: message.id, file: message.file }); + } });