mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-21 19:59:24 +00:00

# Description of Changes Please provide a summary of the changes, including: Vite fixes Indexxdb Closes #(issue_number) --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details.
194 lines
5.8 KiB
TypeScript
194 lines
5.8 KiB
TypeScript
import { FileWithUrl } from "../types/file";
|
|
import { fileStorage, StorageStats } from "./fileStorage";
|
|
import { loadFilesFromIndexedDB, createEnhancedFileFromStored, cleanupFileUrls } from "../utils/fileUtils";
|
|
import { generateThumbnailForFile } from "../utils/thumbnailUtils";
|
|
import { updateStorageStatsIncremental } from "../utils/storageUtils";
|
|
|
|
/**
|
|
* Service for file storage operations
|
|
* Contains all IndexedDB operations and file management logic
|
|
*/
|
|
export const fileOperationsService = {
|
|
|
|
/**
|
|
* Load storage statistics
|
|
*/
|
|
async loadStorageStats(): Promise<StorageStats | null> {
|
|
try {
|
|
return await fileStorage.getStorageStats();
|
|
} catch (error) {
|
|
console.error('Failed to load storage stats:', error);
|
|
return null;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Force reload files from IndexedDB
|
|
*/
|
|
async forceReloadFiles(): Promise<FileWithUrl[]> {
|
|
try {
|
|
return await loadFilesFromIndexedDB();
|
|
} catch (error) {
|
|
console.error('Failed to force reload files:', error);
|
|
return [];
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Load existing files from IndexedDB if not already loaded
|
|
*/
|
|
async loadExistingFiles(
|
|
filesLoaded: boolean,
|
|
currentFiles: FileWithUrl[]
|
|
): Promise<FileWithUrl[]> {
|
|
if (filesLoaded && currentFiles.length > 0) {
|
|
return currentFiles;
|
|
}
|
|
|
|
try {
|
|
await fileStorage.init();
|
|
const storedFiles = await fileStorage.getAllFileMetadata();
|
|
|
|
// Detect if IndexedDB was purged by comparing with current UI state
|
|
if (currentFiles.length > 0 && storedFiles.length === 0) {
|
|
console.warn('IndexedDB appears to have been purged - clearing UI state');
|
|
return [];
|
|
}
|
|
|
|
return await loadFilesFromIndexedDB();
|
|
} catch (error) {
|
|
console.error('Failed to load existing files:', error);
|
|
return [];
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Upload files to IndexedDB with thumbnail generation
|
|
*/
|
|
async uploadFiles(
|
|
uploadedFiles: File[],
|
|
useIndexedDB: boolean
|
|
): Promise<FileWithUrl[]> {
|
|
const newFiles: FileWithUrl[] = [];
|
|
|
|
for (const file of uploadedFiles) {
|
|
if (useIndexedDB) {
|
|
try {
|
|
console.log('Storing file in IndexedDB:', file.name);
|
|
|
|
// Generate thumbnail only during upload
|
|
const thumbnail = await generateThumbnailForFile(file);
|
|
|
|
const storedFile = await fileStorage.storeFile(file, thumbnail);
|
|
console.log('File stored with ID:', storedFile.id);
|
|
|
|
const baseFile = fileStorage.createFileFromStored(storedFile);
|
|
const enhancedFile = createEnhancedFileFromStored(storedFile, thumbnail);
|
|
|
|
// Copy File interface methods from baseFile
|
|
enhancedFile.arrayBuffer = baseFile.arrayBuffer.bind(baseFile);
|
|
enhancedFile.slice = baseFile.slice.bind(baseFile);
|
|
enhancedFile.stream = baseFile.stream.bind(baseFile);
|
|
enhancedFile.text = baseFile.text.bind(baseFile);
|
|
|
|
newFiles.push(enhancedFile);
|
|
} catch (error) {
|
|
console.error('Failed to store file in IndexedDB:', error);
|
|
// Fallback to RAM storage
|
|
const enhancedFile: FileWithUrl = Object.assign(file, {
|
|
url: URL.createObjectURL(file),
|
|
storedInIndexedDB: false
|
|
});
|
|
newFiles.push(enhancedFile);
|
|
}
|
|
} else {
|
|
// IndexedDB disabled - use RAM
|
|
const enhancedFile: FileWithUrl = Object.assign(file, {
|
|
url: URL.createObjectURL(file),
|
|
storedInIndexedDB: false
|
|
});
|
|
newFiles.push(enhancedFile);
|
|
}
|
|
}
|
|
|
|
return newFiles;
|
|
},
|
|
|
|
/**
|
|
* Remove a file from storage
|
|
*/
|
|
async removeFile(file: FileWithUrl): Promise<void> {
|
|
// Clean up blob URL
|
|
if (file.url && !file.url.startsWith('indexeddb:')) {
|
|
URL.revokeObjectURL(file.url);
|
|
}
|
|
|
|
// Remove from IndexedDB if stored there
|
|
if (file.storedInIndexedDB && file.id) {
|
|
try {
|
|
await fileStorage.deleteFile(file.id);
|
|
} catch (error) {
|
|
console.error('Failed to delete file from IndexedDB:', error);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Clear all files from storage
|
|
*/
|
|
async clearAllFiles(files: FileWithUrl[]): Promise<void> {
|
|
// Clean up all blob URLs
|
|
cleanupFileUrls(files);
|
|
|
|
// Clear IndexedDB
|
|
try {
|
|
await fileStorage.clearAll();
|
|
} catch (error) {
|
|
console.error('Failed to clear IndexedDB:', error);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Create blob URL for file viewing
|
|
*/
|
|
async createBlobUrlForFile(file: FileWithUrl): Promise<string> {
|
|
// For large files, use IndexedDB direct access to avoid memory issues
|
|
const FILE_SIZE_LIMIT = 100 * 1024 * 1024; // 100MB
|
|
if (file.size > FILE_SIZE_LIMIT) {
|
|
console.warn(`File ${file.name} is too large for blob URL. Use direct IndexedDB access.`);
|
|
return `indexeddb:${file.id}`;
|
|
}
|
|
|
|
// For all files, avoid persistent blob URLs
|
|
if (file.storedInIndexedDB && file.id) {
|
|
const storedFile = await fileStorage.getFile(file.id);
|
|
if (storedFile) {
|
|
return fileStorage.createBlobUrl(storedFile);
|
|
}
|
|
}
|
|
|
|
// Fallback for files not in IndexedDB
|
|
return URL.createObjectURL(file);
|
|
},
|
|
|
|
/**
|
|
* Check for IndexedDB purge
|
|
*/
|
|
async checkForPurge(currentFiles: FileWithUrl[]): Promise<boolean> {
|
|
if (currentFiles.length === 0) return false;
|
|
|
|
try {
|
|
await fileStorage.init();
|
|
const storedFiles = await fileStorage.getAllFileMetadata();
|
|
return storedFiles.length === 0; // Purge detected if no files in storage but UI shows files
|
|
} catch (error) {
|
|
console.error('Error checking for purge:', error);
|
|
return true; // Assume purged if can't access IndexedDB
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Update storage stats incrementally (re-export utility for convenience)
|
|
*/
|
|
updateStorageStatsIncremental
|
|
}; |