This commit is contained in:
Reece Browne 2025-08-20 23:17:17 +01:00
parent f181c18f4f
commit 7247edc029
3 changed files with 9 additions and 110 deletions

View File

@ -25,7 +25,7 @@ import {
// Import modular components // Import modular components
import { fileContextReducer, initialFileContextState } from './file/FileReducer'; import { fileContextReducer, initialFileContextState } from './file/FileReducer';
import { createFileSelectors, buildQuickKeySetFromMetadata } from './file/fileSelectors'; import { createFileSelectors } from './file/fileSelectors';
import { addFiles, consumeFiles, createFileActions } from './file/fileActions'; import { addFiles, consumeFiles, createFileActions } from './file/fileActions';
import { FileLifecycleManager } from './file/lifecycle'; import { FileLifecycleManager } from './file/lifecycle';
import { FileStateContext, FileActionsContext } from './file/contexts'; import { FileStateContext, FileActionsContext } from './file/contexts';
@ -76,7 +76,7 @@ function FileContextInner({
const addRawFiles = useCallback(async (files: File[]): Promise<File[]> => { const addRawFiles = useCallback(async (files: File[]): Promise<File[]> => {
const addedFilesWithIds = await addFiles('raw', { files }, stateRef, filesRef, dispatch, lifecycleManager); const addedFilesWithIds = await addFiles('raw', { files }, stateRef, filesRef, dispatch, lifecycleManager);
// Persist to IndexedDB if enabled - pass existing thumbnail to prevent double generation // Persist to IndexedDB if enabled
if (indexedDB && enablePersistence && addedFilesWithIds.length > 0) { if (indexedDB && enablePersistence && addedFilesWithIds.length > 0) {
await Promise.all(addedFilesWithIds.map(async ({ file, id, thumbnail }) => { await Promise.all(addedFilesWithIds.map(async ({ file, id, thumbnail }) => {
try { try {

View File

@ -10,11 +10,10 @@ import { fileStorage, StoredFile } from '../services/fileStorage';
import { FileId } from '../types/fileContext'; import { FileId } from '../types/fileContext';
import { FileMetadata } from '../types/file'; import { FileMetadata } from '../types/file';
import { generateThumbnailForFile } from '../utils/thumbnailUtils'; import { generateThumbnailForFile } from '../utils/thumbnailUtils';
import { pdfWorkerManager } from '../services/pdfWorkerManager';
interface IndexedDBContextValue { interface IndexedDBContextValue {
// Core CRUD operations // Core CRUD operations
saveFile: (file: File, fileId: FileId, thumbnail?: string) => Promise<FileMetadata>; saveFile: (file: File, fileId: FileId, existingThumbnail?: string) => Promise<FileMetadata>;
loadFile: (fileId: FileId) => Promise<File | null>; loadFile: (fileId: FileId) => Promise<File | null>;
loadMetadata: (fileId: FileId) => Promise<FileMetadata | null>; loadMetadata: (fileId: FileId) => Promise<FileMetadata | null>;
deleteFile: (fileId: FileId) => Promise<void>; deleteFile: (fileId: FileId) => Promise<void>;
@ -26,9 +25,6 @@ interface IndexedDBContextValue {
// Utilities // Utilities
getStorageStats: () => Promise<{ used: number; available: number; fileCount: number }>; getStorageStats: () => Promise<{ used: number; available: number; fileCount: number }>;
// Draft operations
loadAllDraftMetadata: () => Promise<FileMetadata[]>;
} }
const IndexedDBContext = createContext<IndexedDBContextValue | null>(null); const IndexedDBContext = createContext<IndexedDBContextValue | null>(null);
@ -60,44 +56,7 @@ export function IndexedDBProvider({ children }: IndexedDBProviderProps) {
}, []); }, []);
const saveFile = useCallback(async (file: File, fileId: FileId, existingThumbnail?: string): Promise<FileMetadata> => { const saveFile = useCallback(async (file: File, fileId: FileId, existingThumbnail?: string): Promise<FileMetadata> => {
// Check for duplicate at IndexedDB level before saving // Use existing thumbnail or generate new one if none provided
const quickKey = `${file.name}|${file.size}|${file.lastModified}`;
const existingFiles = await fileStorage.getAllFileMetadata();
const duplicate = existingFiles.find(stored =>
`${stored.name}|${stored.size}|${stored.lastModified}` === quickKey
);
if (duplicate) {
if (DEBUG) console.log(`🔍 SAVE: Skipping IndexedDB duplicate - using existing record:`, duplicate.name);
// Return the existing file's metadata instead of saving duplicate
return {
id: duplicate.id,
name: duplicate.name,
type: duplicate.type,
size: duplicate.size,
lastModified: duplicate.lastModified,
thumbnail: duplicate.thumbnail
};
}
// DEBUG: Check original file before saving
if (DEBUG && file.type === 'application/pdf') {
try {
const arrayBuffer = await file.arrayBuffer();
const pdf = await pdfWorkerManager.createDocument(arrayBuffer);
console.log(`🔍 BEFORE SAVE - Original file:`, {
name: file.name,
size: file.size,
arrayBufferSize: arrayBuffer.byteLength,
pages: pdf.numPages
});
pdfWorkerManager.destroyDocument(pdf);
} catch (error) {
console.error(`🔍 Error validating file before save:`, error);
}
}
// Use existing thumbnail or generate new one
const thumbnail = existingThumbnail || await generateThumbnailForFile(file); const thumbnail = existingThumbnail || await generateThumbnailForFile(file);
// Store in IndexedDB // Store in IndexedDB
@ -127,12 +86,9 @@ export function IndexedDBProvider({ children }: IndexedDBProviderProps) {
return cached.file; return cached.file;
} }
// Load from IndexedDB using the internal fileStorage (which wraps indexedDBManager) // Load from IndexedDB
const storedFile = await fileStorage.getFile(fileId); const storedFile = await fileStorage.getFile(fileId);
if (!storedFile) { if (!storedFile) return null;
if (DEBUG) console.log(`📁 File not found in IndexedDB: ${fileId}`);
return null;
}
// Reconstruct File object // Reconstruct File object
const file = new File([storedFile.data], storedFile.name, { const file = new File([storedFile.data], storedFile.name, {
@ -140,27 +96,6 @@ export function IndexedDBProvider({ children }: IndexedDBProviderProps) {
lastModified: storedFile.lastModified lastModified: storedFile.lastModified
}); });
// DEBUG: Check if file reconstruction is working
if (DEBUG && file.type === 'application/pdf') {
console.log(`🔍 AFTER LOAD - Reconstructed file:`, {
name: file.name,
originalSize: storedFile.size,
reconstructedSize: file.size,
dataLength: storedFile.data.byteLength,
sizesMatch: storedFile.size === file.size
});
// Quick PDF validation
try {
const arrayBuffer = await file.arrayBuffer();
const pdf = await pdfWorkerManager.createDocument(arrayBuffer);
console.log(`🔍 AFTER LOAD - PDF validation: ${pdf.numPages} pages in reconstructed file`);
pdfWorkerManager.destroyDocument(pdf);
} catch (error) {
console.error(`🔍 AFTER LOAD - PDF reconstruction error:`, error);
}
}
// Cache for future use with LRU eviction // Cache for future use with LRU eviction
fileCache.current.set(fileId, { file, lastAccessed: Date.now() }); fileCache.current.set(fileId, { file, lastAccessed: Date.now() });
evictLRUEntries(); evictLRUEntries();
@ -239,40 +174,6 @@ export function IndexedDBProvider({ children }: IndexedDBProviderProps) {
return await fileStorage.getStorageStats(); return await fileStorage.getStorageStats();
}, []); }, []);
const loadAllDraftMetadata = useCallback(async (): Promise<FileMetadata[]> => {
try {
const { indexedDBManager, DATABASE_CONFIGS } = await import('../services/indexedDBManager');
const db = await indexedDBManager.openDatabase(DATABASE_CONFIGS.DRAFTS);
return new Promise((resolve, reject) => {
const transaction = db.transaction(['drafts'], 'readonly');
const store = transaction.objectStore('drafts');
const request = store.getAll();
request.onsuccess = () => {
const drafts = request.result || [];
const draftMetadata: FileMetadata[] = drafts.map((draft: any) => ({
id: draft.id,
name: draft.name || `Draft ${draft.id}`,
type: 'application/pdf',
size: draft.size || 0,
lastModified: draft.timestamp || Date.now(),
thumbnail: draft.thumbnail,
isDraft: true
}));
resolve(draftMetadata);
};
request.onerror = () => reject(request.error);
});
} catch (error) {
console.warn('Failed to load draft metadata:', error);
return [];
}
}, []);
// No periodic cleanup needed - LRU eviction happens on-demand when cache fills
const value: IndexedDBContextValue = { const value: IndexedDBContextValue = {
saveFile, saveFile,
loadFile, loadFile,
@ -281,8 +182,7 @@ export function IndexedDBProvider({ children }: IndexedDBProviderProps) {
loadAllMetadata, loadAllMetadata,
deleteMultiple, deleteMultiple,
clearAll, clearAll,
getStorageStats, getStorageStats
loadAllDraftMetadata
}; };
return ( return (

View File

@ -217,7 +217,7 @@ export async function addFiles(
try { try {
if (DEBUG) console.log(`📄 addFiles(stored): Generating PDF metadata for stored file ${file.name}`); if (DEBUG) console.log(`📄 addFiles(stored): Generating PDF metadata for stored file ${file.name}`);
// Use PDF worker manager directly for page count (avoids fileProcessingService conflicts) // Get page count from PDF
const arrayBuffer = await file.arrayBuffer(); const arrayBuffer = await file.arrayBuffer();
const { pdfWorkerManager } = await import('../../services/pdfWorkerManager'); const { pdfWorkerManager } = await import('../../services/pdfWorkerManager');
const pdf = await pdfWorkerManager.createDocument(arrayBuffer); const pdf = await pdfWorkerManager.createDocument(arrayBuffer);
@ -243,7 +243,6 @@ export async function addFiles(
fileRecords.push(record); fileRecords.push(record);
addedFiles.push({ file, id: fileId, thumbnail: metadata.thumbnail }); addedFiles.push({ file, id: fileId, thumbnail: metadata.thumbnail });
// Note: No background fileProcessingService call for stored files - we already processed them above
} }
break; break;
} }
@ -326,4 +325,4 @@ export const createFileActions = (dispatch: React.Dispatch<FileContextAction>) =
pinFile: (fileId: FileId) => dispatch({ type: 'PIN_FILE', payload: { fileId } }), pinFile: (fileId: FileId) => dispatch({ type: 'PIN_FILE', payload: { fileId } }),
unpinFile: (fileId: FileId) => dispatch({ type: 'UNPIN_FILE', payload: { fileId } }), unpinFile: (fileId: FileId) => dispatch({ type: 'UNPIN_FILE', payload: { fileId } }),
resetContext: () => dispatch({ type: 'RESET_CONTEXT' }) resetContext: () => dispatch({ type: 'RESET_CONTEXT' })
}); });