import { defineStore, storeToRefs } from 'pinia'; import { ref } from 'vue'; import { useExiftoolStore } from '@/stores/exiftool'; import type { MetadataPreviewEntry } from '@/types/metadata/MetadataPreview'; const EXIFTOOL_PREPARE_ERROR_MESSAGE = 'Could not prepare ExifTool'; const EXIFTOOL_INITIALIZING_MESSAGE = 'Initializing ExifTool'; const METADATA_LOADING_MESSAGE = 'Loading metadata'; const METADATA_LOAD_ERROR_MESSAGE = 'Could not load image metadata'; const METADATA_STRIP_ERROR_MESSAGE = 'Could not strip image metadata'; const METADATA_IDLE_MESSAGE = 'Waiting to load metadata'; const METADATA_READY_MESSAGE = 'Metadata stripped'; const METADATA_IDLE_PREVIEW: MetadataPreviewEntry = { status: 'idle', message: METADATA_IDLE_MESSAGE }; const METADATA_LOADING_PREVIEW: MetadataPreviewEntry = { status: 'loading', message: METADATA_LOADING_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 makeKeyFromFile = (file: File): string => `${file.name}\0${file.size}\0${file.lastModified}`; 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') { const entry: MetadataPreviewEntry = { status: 'error', message: EXIFTOOL_PREPARE_ERROR_MESSAGE }; previewsByKey.value = { ...previewsByKey.value, [key]: entry }; return entry; } if (exiftoolInitStatus.value === 'ready') { previewsByKey.value = { ...previewsByKey.value, [key]: METADATA_LOADING_PREVIEW }; } else { previewsByKey.value = { ...previewsByKey.value, [key]: { status: 'pending', message: EXIFTOOL_INITIALIZING_MESSAGE } }; try { await initializeExiftool(); previewsByKey.value = { ...previewsByKey.value, [key]: METADATA_LOADING_PREVIEW }; } catch (error) { console.error('[metadata] ExifTool init failed', { file: file.name, error }); const entry: MetadataPreviewEntry = { status: 'error', message: EXIFTOOL_PREPARE_ERROR_MESSAGE }; previewsByKey.value = { ...previewsByKey.value, [key]: entry }; return entry; } } let output: string; try { output = await readMetadata(file); } catch (error) { console.error('[metadata] read failed', { file: file.name, error }); const entry: MetadataPreviewEntry = { status: 'error', message: METADATA_LOAD_ERROR_MESSAGE }; previewsByKey.value = { ...previewsByKey.value, [key]: entry }; return entry; } 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 }; previewsByKey.value = { ...previewsByKey.value, [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 }); const entry: MetadataPreviewEntry = { status: 'error', message: METADATA_LOAD_ERROR_MESSAGE }; previewsByKey.value = { ...previewsByKey.value, [key]: entry }; return entry; } strippedBlobByKey.value = { ...strippedBlobByKey.value, [key]: strippedBlob }; const entry: MetadataPreviewEntry = { status: 'ready', message: METADATA_READY_MESSAGE, output, afterStripOutput }; previewsByKey.value = { ...previewsByKey.value, [key]: entry }; return entry; }; const loadPreviews = (files: readonly File[]): Promise => Promise.all(files.map(file => loadPreview(file))); const clearAllPreviews = (): void => { previewsByKey.value = {}; strippedBlobByKey.value = {}; }; return { previewsByKey, strippedBlobByKey, makeKeyFromFile, getPreview, getStrippedBlob, loadPreview, loadPreviews, clearAllPreviews }; });