Execute batch uploads with metadata gates, encryption, and queue state.
Separate upload entries per file, ALTCHA on POST, and stripped-or-original fallback on metadata errors.
This commit is contained in:
@@ -2,23 +2,29 @@ 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 EXIFTOOL_PREPARE_ERROR_MESSAGE = 'Could not prepare ExifTool';
|
||||
const EXIFTOOL_INITIALIZING_MESSAGE = 'Initializing ExifTool';
|
||||
const METADATA_LOADING_MESSAGE = 'Loading metadata';
|
||||
const METADATA_IDLE_MESSAGE = 'Waiting to load metadata...';
|
||||
const EXIFTOOL_INITIALIZING_MESSAGE = 'Initializing ExifTool...';
|
||||
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 METADATA_IDLE_MESSAGE = 'Waiting to load metadata';
|
||||
const METADATA_READY_MESSAGE = 'Metadata stripped';
|
||||
const EXIFTOOL_PREPARE_ERROR_MESSAGE = 'Could not prepare ExifTool';
|
||||
|
||||
const METADATA_IDLE_PREVIEW: MetadataPreviewEntry = {
|
||||
status: 'idle',
|
||||
message: METADATA_IDLE_MESSAGE
|
||||
};
|
||||
|
||||
const METADATA_LOADING_PREVIEW: MetadataPreviewEntry = {
|
||||
status: 'loading',
|
||||
message: METADATA_LOADING_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', () => {
|
||||
@@ -30,7 +36,13 @@ export const useMetadataStore = defineStore('metadata', () => {
|
||||
const { initStatus: exiftoolInitStatus } = storeToRefs(exiftoolStore);
|
||||
const { initializeExiftool, readMetadata, stripAllMetadata } = exiftoolStore;
|
||||
|
||||
const makeKeyFromFile = (file: File): string => `${file.name}\0${file.size}\0${file.lastModified}`;
|
||||
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) {
|
||||
@@ -56,48 +68,27 @@ export const useMetadataStore = defineStore('metadata', () => {
|
||||
const key = makeKeyFromFile(file);
|
||||
|
||||
if (exiftoolInitStatus.value === 'error') {
|
||||
const entry: MetadataPreviewEntry = {
|
||||
status: 'error',
|
||||
message: EXIFTOOL_PREPARE_ERROR_MESSAGE
|
||||
};
|
||||
setPreviewEntry(key, EXIFTOOL_PREPARE_ERROR_PREVIEW);
|
||||
|
||||
previewsByKey.value = { ...previewsByKey.value, [key]: entry };
|
||||
|
||||
return entry;
|
||||
return EXIFTOOL_PREPARE_ERROR_PREVIEW;
|
||||
}
|
||||
|
||||
if (exiftoolInitStatus.value === 'ready') {
|
||||
previewsByKey.value = {
|
||||
...previewsByKey.value,
|
||||
[key]: METADATA_LOADING_PREVIEW
|
||||
};
|
||||
} else {
|
||||
previewsByKey.value = {
|
||||
...previewsByKey.value,
|
||||
[key]: { status: 'pending', message: EXIFTOOL_INITIALIZING_MESSAGE }
|
||||
};
|
||||
if (exiftoolInitStatus.value !== 'ready') {
|
||||
setPreviewEntry(key, { status: 'initializing', 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
|
||||
};
|
||||
setPreviewEntry(key, EXIFTOOL_PREPARE_ERROR_PREVIEW);
|
||||
|
||||
previewsByKey.value = { ...previewsByKey.value, [key]: entry };
|
||||
|
||||
return entry;
|
||||
return EXIFTOOL_PREPARE_ERROR_PREVIEW;
|
||||
}
|
||||
}
|
||||
|
||||
setPreviewEntry(key, { status: 'processing', message: METADATA_PROCESSING_MESSAGE });
|
||||
|
||||
let output: string;
|
||||
|
||||
try {
|
||||
@@ -105,14 +96,9 @@ export const useMetadataStore = defineStore('metadata', () => {
|
||||
} catch (error) {
|
||||
console.error('[metadata] read failed', { file: file.name, error });
|
||||
|
||||
const entry: MetadataPreviewEntry = {
|
||||
status: 'error',
|
||||
message: METADATA_LOAD_ERROR_MESSAGE
|
||||
};
|
||||
setPreviewEntry(key, METADATA_LOAD_ERROR_PREVIEW);
|
||||
|
||||
previewsByKey.value = { ...previewsByKey.value, [key]: entry };
|
||||
|
||||
return entry;
|
||||
return METADATA_LOAD_ERROR_PREVIEW;
|
||||
}
|
||||
|
||||
let strippedBlob: Blob;
|
||||
@@ -129,10 +115,11 @@ export const useMetadataStore = defineStore('metadata', () => {
|
||||
|
||||
const entry: MetadataPreviewEntry = {
|
||||
status: 'error',
|
||||
message: METADATA_STRIP_ERROR_MESSAGE
|
||||
message: METADATA_STRIP_ERROR_MESSAGE,
|
||||
output
|
||||
};
|
||||
|
||||
previewsByKey.value = { ...previewsByKey.value, [key]: entry };
|
||||
setPreviewEntry(key, entry);
|
||||
|
||||
return entry;
|
||||
}
|
||||
@@ -149,17 +136,12 @@ export const useMetadataStore = defineStore('metadata', () => {
|
||||
} catch (error) {
|
||||
console.error('[metadata] read after strip failed', { file: file.name, error });
|
||||
|
||||
const entry: MetadataPreviewEntry = {
|
||||
status: 'error',
|
||||
message: METADATA_LOAD_ERROR_MESSAGE
|
||||
};
|
||||
setPreviewEntry(key, METADATA_LOAD_ERROR_PREVIEW);
|
||||
|
||||
previewsByKey.value = { ...previewsByKey.value, [key]: entry };
|
||||
|
||||
return entry;
|
||||
return METADATA_LOAD_ERROR_PREVIEW;
|
||||
}
|
||||
|
||||
strippedBlobByKey.value = { ...strippedBlobByKey.value, [key]: strippedBlob };
|
||||
setStrippedBlob(key, strippedBlob);
|
||||
|
||||
const entry: MetadataPreviewEntry = {
|
||||
status: 'ready',
|
||||
@@ -168,13 +150,22 @@ export const useMetadataStore = defineStore('metadata', () => {
|
||||
afterStripOutput
|
||||
};
|
||||
|
||||
previewsByKey.value = { ...previewsByKey.value, [key]: entry };
|
||||
setPreviewEntry(key, entry);
|
||||
|
||||
return entry;
|
||||
};
|
||||
|
||||
const loadPreviews = (files: readonly File[]): Promise<MetadataPreviewEntry[]> =>
|
||||
Promise.all(files.map(file => loadPreview(file)));
|
||||
const loadPreviews = async (files: readonly File[]): Promise<MetadataPreviewEntry[]> => {
|
||||
const results: MetadataPreviewEntry[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
const result = await loadPreview(file);
|
||||
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
const clearAllPreviews = (): void => {
|
||||
previewsByKey.value = {};
|
||||
@@ -184,7 +175,6 @@ export const useMetadataStore = defineStore('metadata', () => {
|
||||
return {
|
||||
previewsByKey,
|
||||
strippedBlobByKey,
|
||||
makeKeyFromFile,
|
||||
getPreview,
|
||||
getStrippedBlob,
|
||||
loadPreview,
|
||||
|
||||
Reference in New Issue
Block a user