Slave/metadata pipeline #6
@@ -0,0 +1,83 @@
|
|||||||
|
import { parseMetadata } from '@uswriting/exiftool';
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import type { MetadataPreviewEntry } from '@/types/metadata/MetadataPreview';
|
||||||
|
import { resolveErrorMessage } from '@/utils/resolveErrorMessage';
|
||||||
|
|
||||||
|
export const useMetadataStore = defineStore('metadata', () => {
|
||||||
|
const previewsByKey = ref<Record<string, MetadataPreviewEntry>>({});
|
||||||
|
|
||||||
|
const readExifToolStdout = async (file: File): Promise<string> => {
|
||||||
|
const result = await parseMetadata(file);
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeKeyFromFile = (file: File): string => `${file.name}\0${file.size}\0${file.lastModified}`;
|
||||||
|
|
||||||
|
const getPreview = (file: File): MetadataPreviewEntry => {
|
||||||
|
const key = makeKeyFromFile(file);
|
||||||
|
|
||||||
|
return previewsByKey.value[key] ?? { status: 'idle' };
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadPreview = async (file: File): Promise<MetadataPreviewEntry> => {
|
||||||
|
const key = makeKeyFromFile(file);
|
||||||
|
|
||||||
|
previewsByKey.value = { ...previewsByKey.value, [key]: { status: 'loading' } };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const output = await readExifToolStdout(file);
|
||||||
|
|
||||||
|
const entry: MetadataPreviewEntry = { status: 'ready', output };
|
||||||
|
|
||||||
|
previewsByKey.value = { ...previewsByKey.value, [key]: entry };
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
} catch (error) {
|
||||||
|
const entry: MetadataPreviewEntry = {
|
||||||
|
status: 'error',
|
||||||
|
message: resolveErrorMessage(error, 'Could not read image metadata')
|
||||||
|
};
|
||||||
|
|
||||||
|
previewsByKey.value = { ...previewsByKey.value, [key]: entry };
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadPreviews = async (files: readonly File[]): Promise<void> => {
|
||||||
|
await Promise.all(files.map(file => loadPreview(file)));
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearPreview = (file: File): void => {
|
||||||
|
const key = makeKeyFromFile(file);
|
||||||
|
|
||||||
|
if (!(key in previewsByKey.value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const next = { ...previewsByKey.value };
|
||||||
|
|
||||||
|
delete next[key];
|
||||||
|
previewsByKey.value = next;
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearAllPreviews = (): void => {
|
||||||
|
previewsByKey.value = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
previewsByKey,
|
||||||
|
makeKeyFromFile,
|
||||||
|
getPreview,
|
||||||
|
loadPreview,
|
||||||
|
loadPreviews,
|
||||||
|
clearPreview,
|
||||||
|
clearAllPreviews
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export type MetadataPreviewEntry =
|
||||||
|
| { status: 'idle' }
|
||||||
|
| { status: 'loading' }
|
||||||
|
| { status: 'ready'; output: string }
|
||||||
|
| { status: 'error'; message: string };
|
||||||
@@ -1,23 +1,40 @@
|
|||||||
import axios from 'axios';
|
import { isAxiosError } from 'axios';
|
||||||
|
import { resolveErrorMessage } from '@/utils/resolveErrorMessage';
|
||||||
|
|
||||||
|
const getAxiosErrorResponseMessage = (data: unknown): string | null => {
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof data !== 'object' || data === null || !('message' in data)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { message } = data;
|
||||||
|
|
||||||
export function resolveAxiosErrorMessage(error: unknown, fallback = 'Request failed'): string {
|
|
||||||
if (axios.isAxiosError(error)) {
|
|
||||||
const data = error.response?.data;
|
|
||||||
if (typeof data === 'object' && data !== null && 'message' in data) {
|
|
||||||
const message = (data as { message: unknown }).message;
|
|
||||||
if (typeof message === 'string') {
|
if (typeof message === 'string') {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(message)) {
|
if (Array.isArray(message)) {
|
||||||
return message.map(String).join(', ');
|
const texts = message.filter((item): item is string => typeof item === 'string');
|
||||||
|
|
||||||
|
if (texts.length === 1) {
|
||||||
|
return texts[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (texts.length > 1) {
|
||||||
|
return texts.join(', ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (error.message) {
|
|
||||||
return error.message;
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resolveAxiosErrorMessage = (error: unknown, fallback: string): string => {
|
||||||
|
if (!isAxiosError(error) || !error.response) {
|
||||||
|
return resolveErrorMessage(error, fallback);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (error instanceof Error) {
|
return getAxiosErrorResponseMessage(error.response.data) ?? fallback;
|
||||||
return error.message;
|
};
|
||||||
}
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export function resolveErrorMessage(error: unknown, fallback = 'Something went wrong'): string {
|
||||||
|
if (error instanceof Error && error.message.length > 0) {
|
||||||
|
return error.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user