import { defineStore, storeToRefs } from 'pinia'; import { ref } from 'vue'; import { useExiftoolStore } from '@/stores/exiftool'; import type { MetadataPreviewEntry } from '@/types/metadata/MetadataPreview'; import { makeKeyFromFile } from '@/utils/file/makeKeyFromFile'; const METADATA_IDLE_MESSAGE = 'Waiting to load metadata...'; const EXIFTOOL_INITIALIZING_MESSAGE = 'Initializing ExifTool. It may take a minute on slow networks...'; const METADATA_PROCESSING_MESSAGE = 'Stripping metadata...'; const METADATA_READY_MESSAGE = 'Metadata stripped'; const METADATA_LOAD_ERROR_MESSAGE = 'Could not load image metadata'; const METADATA_STRIP_ERROR_MESSAGE = 'Could not strip image metadata'; const EXIFTOOL_PREPARE_ERROR_MESSAGE = 'Could not prepare ExifTool'; const METADATA_IDLE_PREVIEW: MetadataPreviewEntry = { status: 'idle', message: METADATA_IDLE_MESSAGE }; const METADATA_LOAD_ERROR_PREVIEW: MetadataPreviewEntry = { status: 'error', message: METADATA_LOAD_ERROR_MESSAGE }; const EXIFTOOL_PREPARE_ERROR_PREVIEW: MetadataPreviewEntry = { status: 'error', message: EXIFTOOL_PREPARE_ERROR_MESSAGE }; export const useMetadataStore = defineStore('metadata', () => { const previewsByKey = ref>({}); const strippedBlobByKey = ref>({}); const exiftoolStore = useExiftoolStore(); const { initStatus: exiftoolInitStatus } = storeToRefs(exiftoolStore); const { initializeExiftool, readMetadata, stripAllMetadata } = exiftoolStore; const setPreviewEntry = (key: string, entry: MetadataPreviewEntry): void => { previewsByKey.value = { ...previewsByKey.value, [key]: entry }; }; const setStrippedBlob = (key: string, blob: Blob): void => { strippedBlobByKey.value = { ...strippedBlobByKey.value, [key]: blob }; }; const getPreview = (file?: File | null): MetadataPreviewEntry => { if (!file) { return METADATA_IDLE_PREVIEW; } const key = makeKeyFromFile(file); return previewsByKey.value[key] ?? METADATA_IDLE_PREVIEW; }; const getStrippedBlob = (file?: File | null): Blob | null => { if (!file) { return null; } const key = makeKeyFromFile(file); return strippedBlobByKey.value[key] ?? null; }; const loadPreview = async (file: File): Promise => { const key = makeKeyFromFile(file); if (exiftoolInitStatus.value === 'error') { setPreviewEntry(key, EXIFTOOL_PREPARE_ERROR_PREVIEW); return EXIFTOOL_PREPARE_ERROR_PREVIEW; } if (exiftoolInitStatus.value !== 'ready') { setPreviewEntry(key, { status: 'initializing', message: EXIFTOOL_INITIALIZING_MESSAGE }); try { await initializeExiftool(); } catch (error) { console.error('[metadata] ExifTool init failed', { file: file.name, error }); setPreviewEntry(key, EXIFTOOL_PREPARE_ERROR_PREVIEW); return EXIFTOOL_PREPARE_ERROR_PREVIEW; } } setPreviewEntry(key, { status: 'processing', message: METADATA_PROCESSING_MESSAGE }); let output: string; try { output = await readMetadata(file); } catch (error) { console.error('[metadata] read failed', { file: file.name, error }); setPreviewEntry(key, METADATA_LOAD_ERROR_PREVIEW); return METADATA_LOAD_ERROR_PREVIEW; } let strippedBlob: Blob; try { strippedBlob = await stripAllMetadata(file); } catch (error) { console.error('[metadata] strip failed', { file: file.name, type: file.type, size: file.size, error }); const entry: MetadataPreviewEntry = { status: 'error', message: METADATA_STRIP_ERROR_MESSAGE, output }; setPreviewEntry(key, entry); return entry; } const strippedFile = new File([strippedBlob], file.name, { type: file.type || strippedBlob.type || 'application/octet-stream', lastModified: file.lastModified }); let afterStripOutput: string; try { afterStripOutput = await readMetadata(strippedFile); } catch (error) { console.error('[metadata] read after strip failed', { file: file.name, error }); setPreviewEntry(key, METADATA_LOAD_ERROR_PREVIEW); return METADATA_LOAD_ERROR_PREVIEW; } setStrippedBlob(key, strippedBlob); const entry: MetadataPreviewEntry = { status: 'ready', message: METADATA_READY_MESSAGE, output, afterStripOutput }; setPreviewEntry(key, entry); return entry; }; const loadPreviews = async (files: readonly File[]): Promise => { const results: MetadataPreviewEntry[] = []; for (const file of files) { const result = await loadPreview(file); results.push(result); } return results; }; const clearAllPreviews = (): void => { previewsByKey.value = {}; strippedBlobByKey.value = {}; }; return { previewsByKey, strippedBlobByKey, getPreview, getStrippedBlob, loadPreview, loadPreviews, clearAllPreviews }; });