mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-26 14:19:24 +00:00
Address copilot issues
This commit is contained in:
parent
c8b3db295c
commit
3afbf8fb4d
@ -19,6 +19,40 @@ import { buildQuickKeySet, buildQuickKeySetFromMetadata } from './fileSelectors'
|
||||
|
||||
const DEBUG = process.env.NODE_ENV === 'development';
|
||||
|
||||
/**
|
||||
* Simple mutex to prevent race conditions in addFiles
|
||||
*/
|
||||
class SimpleMutex {
|
||||
private locked = false;
|
||||
private queue: Array<() => void> = [];
|
||||
|
||||
async lock(): Promise<void> {
|
||||
if (!this.locked) {
|
||||
this.locked = true;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise<void>((resolve) => {
|
||||
this.queue.push(() => {
|
||||
this.locked = true;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
unlock(): void {
|
||||
if (this.queue.length > 0) {
|
||||
const next = this.queue.shift()!;
|
||||
next();
|
||||
} else {
|
||||
this.locked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Global mutex for addFiles operations
|
||||
const addFilesMutex = new SimpleMutex();
|
||||
|
||||
/**
|
||||
* Helper to create ProcessedFile metadata structure
|
||||
*/
|
||||
@ -63,6 +97,10 @@ export async function addFiles(
|
||||
dispatch: React.Dispatch<FileContextAction>,
|
||||
lifecycleManager: FileLifecycleManager
|
||||
): Promise<Array<{ file: File; id: FileId; thumbnail?: string }>> {
|
||||
// Acquire mutex to prevent race conditions
|
||||
await addFilesMutex.lock();
|
||||
|
||||
try {
|
||||
const fileRecords: FileRecord[] = [];
|
||||
const addedFiles: Array<{ file: File; id: FileId; thumbnail?: string }> = [];
|
||||
|
||||
@ -99,7 +137,7 @@ export async function addFiles(
|
||||
const result = await generateThumbnailWithMetadata(file);
|
||||
thumbnail = result.thumbnail;
|
||||
pageCount = result.pageCount;
|
||||
if (DEBUG) console.log(`📄 Generated PDF metadata for ${file.name}: ${pageCount} pages, thumbnail: ${!!thumbnail}`);
|
||||
if (DEBUG) console.log(`📄 Generated PDF metadata for ${file.name}: ${pageCount} pages, thumbnail: SUCCESS`);
|
||||
} catch (error) {
|
||||
if (DEBUG) console.warn(`📄 Failed to generate PDF metadata for ${file.name}:`, error);
|
||||
}
|
||||
@ -110,7 +148,7 @@ export async function addFiles(
|
||||
const { generateThumbnailForFile } = await import('../../utils/thumbnailUtils');
|
||||
thumbnail = await generateThumbnailForFile(file);
|
||||
pageCount = 0; // Non-PDFs have no page count
|
||||
if (DEBUG) console.log(`📄 Generated simple thumbnail for ${file.name}: no page count, thumbnail: ${!!thumbnail}`);
|
||||
if (DEBUG) console.log(`📄 Generated simple thumbnail for ${file.name}: no page count, thumbnail: SUCCESS`);
|
||||
} catch (error) {
|
||||
if (DEBUG) console.warn(`📄 Failed to generate simple thumbnail for ${file.name}:`, error);
|
||||
}
|
||||
@ -255,6 +293,10 @@ export async function addFiles(
|
||||
}
|
||||
|
||||
return addedFiles;
|
||||
} finally {
|
||||
// Always release mutex even if error occurs
|
||||
addFilesMutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -50,13 +50,8 @@ export const useToolResources = () => {
|
||||
try {
|
||||
console.log(`🖼️ Generating thumbnail for: ${file.name} (${file.type}, ${file.size} bytes)`);
|
||||
const thumbnail = await generateThumbnailForFile(file);
|
||||
console.log(`🖼️ Generated thumbnail for ${file.name}:`, thumbnail ? 'SUCCESS' : 'FAILED (no thumbnail returned)');
|
||||
if (thumbnail) {
|
||||
console.log(`🖼️ Generated thumbnail for ${file.name}: SUCCESS`);
|
||||
thumbnails.push(thumbnail);
|
||||
} else {
|
||||
console.warn(`🖼️ No thumbnail returned for ${file.name}`);
|
||||
thumbnails.push('');
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`🖼️ Failed to generate thumbnail for ${file.name}:`, error);
|
||||
thumbnails.push('');
|
||||
@ -74,9 +69,7 @@ export const useToolResources = () => {
|
||||
try {
|
||||
console.log(`🖼️ Generating thumbnail with metadata for: ${file.name} (${file.type}, ${file.size} bytes)`);
|
||||
const result = await generateThumbnailWithMetadata(file);
|
||||
console.log(`🖼️ Generated thumbnail with metadata for ${file.name}:`,
|
||||
result.thumbnail ? 'SUCCESS' : 'FAILED (no thumbnail)',
|
||||
`${result.pageCount} pages`);
|
||||
console.log(`🖼️ Generated thumbnail with metadata for ${file.name}: SUCCESS, ${result.pageCount} pages`);
|
||||
results.push(result);
|
||||
} catch (error) {
|
||||
console.warn(`🖼️ Failed to generate thumbnail with metadata for ${file.name}:`, error);
|
||||
@ -84,7 +77,7 @@ export const useToolResources = () => {
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`🖼️ useToolResources.generateThumbnailsWithMetadata: Complete. Generated ${results.filter(r => r.thumbnail).length}/${files.length} thumbnails with metadata`);
|
||||
console.log(`🖼️ useToolResources.generateThumbnailsWithMetadata: Complete. Generated ${results.length}/${files.length} thumbnails with metadata`);
|
||||
return results;
|
||||
}, []);
|
||||
|
||||
|
@ -64,10 +64,8 @@ export function useIndexedDBThumbnail(file: FileMetadata | undefined | null): {
|
||||
|
||||
// Use the universal thumbnail generator
|
||||
const thumbnail = await generateThumbnailForFile(fileObject);
|
||||
if (!cancelled && thumbnail) {
|
||||
if (!cancelled) {
|
||||
setThumb(thumbnail);
|
||||
} else if (!cancelled) {
|
||||
setThumb(null);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to generate thumbnail for file', file.name, error);
|
||||
|
@ -107,18 +107,8 @@ class FileProcessingService {
|
||||
throw new Error('Processing cancelled');
|
||||
}
|
||||
} catch (pdfError) {
|
||||
console.warn(`📁 FileProcessingService: PDF.js failed for ${file.name}, trying fallback:`, pdfError);
|
||||
|
||||
// Fallback to text analysis (reuse same arrayBuffer)
|
||||
try {
|
||||
const text = new TextDecoder('latin1').decode(arrayBuffer);
|
||||
const pageMatches = text.match(/\/Type\s*\/Page[^s]/g);
|
||||
totalPages = pageMatches ? pageMatches.length : 1;
|
||||
console.log(`📁 FileProcessingService: Text analysis discovered ${totalPages} pages for ${file.name}`);
|
||||
} catch (textError) {
|
||||
console.warn(`📁 FileProcessingService: Text analysis also failed for ${file.name}:`, textError);
|
||||
totalPages = 1;
|
||||
}
|
||||
console.warn(`📁 FileProcessingService: PDF.js failed for ${file.name}, setting pages to 0:`, pdfError);
|
||||
totalPages = 0; // Unknown page count - UI will hide page count display
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,6 +117,19 @@ export class ThumbnailGenerationService {
|
||||
options: ThumbnailGenerationOptions = {},
|
||||
onProgress?: (progress: { completed: number; total: number; thumbnails: ThumbnailResult[] }) => void
|
||||
): Promise<ThumbnailResult[]> {
|
||||
// Input validation
|
||||
if (!fileId || typeof fileId !== 'string' || fileId.trim() === '') {
|
||||
throw new Error('generateThumbnails: fileId must be a non-empty string');
|
||||
}
|
||||
|
||||
if (!pdfArrayBuffer || pdfArrayBuffer.byteLength === 0) {
|
||||
throw new Error('generateThumbnails: pdfArrayBuffer must not be empty');
|
||||
}
|
||||
|
||||
if (!pageNumbers || pageNumbers.length === 0) {
|
||||
throw new Error('generateThumbnails: pageNumbers must not be empty');
|
||||
}
|
||||
|
||||
const {
|
||||
scale = 0.2,
|
||||
quality = 0.8
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { pdfWorkerManager } from '../services/pdfWorkerManager';
|
||||
|
||||
export interface ThumbnailWithMetadata {
|
||||
thumbnail: string | undefined;
|
||||
thumbnail: string; // Always returns a thumbnail (placeholder if needed)
|
||||
pageCount: number;
|
||||
}
|
||||
|
||||
@ -325,9 +325,9 @@ async function generatePDFThumbnail(arrayBuffer: ArrayBuffer, file: File, scale:
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate thumbnail for any file type
|
||||
* Generate thumbnail for any file type - always returns a thumbnail (placeholder if needed)
|
||||
*/
|
||||
export async function generateThumbnailForFile(file: File): Promise<string | undefined> {
|
||||
export async function generateThumbnailForFile(file: File): Promise<string> {
|
||||
// Skip very large files
|
||||
if (file.size >= 100 * 1024 * 1024) {
|
||||
return generatePlaceholderThumbnail(file);
|
||||
@ -351,11 +351,17 @@ export async function generateThumbnailForFile(file: File): Promise<string | und
|
||||
return await generatePDFThumbnail(arrayBuffer, file, scale);
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.name === 'InvalidPDFException') {
|
||||
console.warn(`PDF structure issue for ${file.name} - using fallback thumbnail`);
|
||||
console.warn(`PDF structure issue for ${file.name} - trying with full file`);
|
||||
try {
|
||||
// Try with full file instead of chunk
|
||||
const fullArrayBuffer = await file.arrayBuffer();
|
||||
return await generatePDFThumbnail(fullArrayBuffer, file, scale);
|
||||
} catch (fullFileError) {
|
||||
console.warn(`Full file PDF processing also failed for ${file.name} - using placeholder`);
|
||||
return generatePlaceholderThumbnail(file);
|
||||
}
|
||||
}
|
||||
console.warn(`PDF processing failed for ${file.name} - using placeholder:`, error);
|
||||
return generatePlaceholderThumbnail(file);
|
||||
}
|
||||
}
|
||||
@ -365,7 +371,7 @@ export async function generateThumbnailForFile(file: File): Promise<string | und
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate thumbnail and extract page count for a PDF file
|
||||
* Generate thumbnail and extract page count for a PDF file - always returns a valid thumbnail
|
||||
*/
|
||||
export async function generateThumbnailWithMetadata(file: File): Promise<ThumbnailWithMetadata> {
|
||||
// Non-PDF files have no page count
|
||||
|
Loading…
x
Reference in New Issue
Block a user