mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-15 19:10:47 +02:00
Fix/V2/unzip_images (#4647)
Method Usage by Context | Context | Method Used | Respects Preferences | HTML Detection | |------------------------------|-------------------------------------------------------|------------------------|----------------| | Tools (via useToolResources) | extractZipFiles() → extractWithPreferences() | ✅ Yes | ✅ Yes | | Automation | extractAutomationZipFiles() → extractAllFiles() | ❌ No (always extracts) | ✅ Yes | | Manual Unzip | extractAndStoreFilesWithHistory() → extractAllFiles() | ❌ No (always extracts) | ✅ Yes | | Auto-Upload | extractAllFiles() directly | ❌ No (always extracts) | ✅ Yes | Detailed Behavior Matrix | Context | HTML Files | Auto-Unzip OFF | Within Limit | Exceeds Limit | Notes | |--------------------------|-------------|----------------|--------------|---------------|----------------------------------------| | Tools (useToolResources) | Keep zipped | Keep zipped | Extract all | Keep zipped | Respects user preferences | | Automation | Keep zipped | Extract all | Extract all | Extract all | Ignores preferences (automation needs) | | Manual Unzip | Keep zipped | Extract all | Extract all | Extract all | User explicitly unzipping | | Auto-Upload | Keep zipped | Extract all | Extract all | Extract all | User dropped files | Simplified Decision Flow ZIP File Received │ ├─ Contains HTML? → Keep as ZIP (all contexts) │ └─ No HTML │ ├─ Tools Context │ ├─ Auto-unzip OFF? → Keep as ZIP │ └─ Auto-unzip ON │ ├─ File count ≤ limit? → Extract all │ └─ File count > limit? → Keep as ZIP │ └─ Automation/Manual/Auto-Upload └─ Extract all (ignore preferences) Key Changes from Previous Version | Entry Point | Code Path | skipAutoUnzip | Respects Preferences? | HTML Detection? | Extraction Behavior | |-----------------------------------------------|----------------------------------------------------------------------------------------|---------------|-----------------------|---------------------------|-------------------------------------------------------------------------| | Direct File Upload (FileEditor, LandingPage) | FileContext.addRawFiles() → fileActions.addFiles() | True | ❌ No | ✅ Yes | Always extract (except HTML ZIPs) | | Tool Outputs (Split, Merge, etc.) | useToolResources.extractZipFiles() → zipFileService.extractWithPreferences() | false | ✅ Yes | ✅ Yes | Conditional: Only if autoUnzip=true AND file count ≤ autoUnzipFileLimit | | Load from Storage (FileManager) | fileActions.addStirlingFileStubs() | N/A | N/A | N/A | No extraction - files already processed | | Automation Outputs | AutomationFileProcessor.extractAutomationZipFiles() → zipFileService.extractAllFiles() | N/A | ❌ No | ✅ Yes | Always extract (except HTML ZIPs) | | Manual Unzip Action (FileEditor context menu) | zipFileService.extractAndStoreFilesWithHistory() → extractAllFiles() | N/A | ❌ No | ✅ Yes (blocks extraction) | Always extract (except HTML ZIPs) - explicit user action | --------- Co-authored-by: Connor Yoh <[email protected]>
This commit is contained in:
@@ -29,6 +29,7 @@ export interface ZipValidationResult {
|
||||
fileCount: number;
|
||||
totalSizeBytes: number;
|
||||
containsPDFs: boolean;
|
||||
containsFiles: boolean;
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
@@ -42,7 +43,6 @@ export interface ZipExtractionProgress {
|
||||
export class ZipFileService {
|
||||
private readonly maxFileSize = 100 * 1024 * 1024; // 100MB per file
|
||||
private readonly maxTotalSize = 500 * 1024 * 1024; // 500MB total extraction limit
|
||||
private readonly supportedExtensions = ['.pdf'];
|
||||
|
||||
// ZIP file validation constants
|
||||
private static readonly VALID_ZIP_TYPES = [
|
||||
@@ -62,6 +62,7 @@ export class ZipFileService {
|
||||
fileCount: 0,
|
||||
totalSizeBytes: 0,
|
||||
containsPDFs: false,
|
||||
containsFiles: false,
|
||||
errors: []
|
||||
};
|
||||
|
||||
@@ -115,10 +116,13 @@ export class ZipFileService {
|
||||
result.fileCount = fileCount;
|
||||
result.totalSizeBytes = totalSize;
|
||||
result.containsPDFs = containsPDFs;
|
||||
result.isValid = result.errors.length === 0 && containsPDFs;
|
||||
result.containsFiles = fileCount > 0;
|
||||
|
||||
if (!containsPDFs) {
|
||||
result.errors.push('ZIP file does not contain any PDF files');
|
||||
// ZIP is valid if it has files and no size errors
|
||||
result.isValid = result.errors.length === 0 && result.containsFiles;
|
||||
|
||||
if (!result.containsFiles) {
|
||||
result.errors.push('ZIP file does not contain any files');
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -278,6 +282,37 @@ export class ZipFileService {
|
||||
return filename.toLowerCase().endsWith('.pdf');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a filename indicates an HTML file
|
||||
*/
|
||||
private isHtmlFile(filename: string): boolean {
|
||||
const lowerName = filename.toLowerCase();
|
||||
return lowerName.endsWith('.html') || lowerName.endsWith('.htm') || lowerName.endsWith('.xhtml');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a ZIP file contains HTML files
|
||||
* Used to determine if the ZIP should be kept intact (HTML) or extracted (other files)
|
||||
*/
|
||||
async containsHtmlFiles(file: Blob | File): Promise<boolean> {
|
||||
try {
|
||||
const zip = new JSZip();
|
||||
const zipContents = await zip.loadAsync(file);
|
||||
|
||||
// Check if any file is an HTML file
|
||||
for (const [filename, zipEntry] of Object.entries(zipContents.files)) {
|
||||
if (!zipEntry.dir && this.isHtmlFile(filename)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Error checking for HTML files:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that a file is actually a PDF by checking its header
|
||||
*/
|
||||
@@ -366,6 +401,62 @@ export class ZipFileService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract files from ZIP with HTML detection and preference checking
|
||||
* This is the unified method that handles the common pattern of:
|
||||
* 1. Check for HTML files → keep zipped if present
|
||||
* 2. Check user preferences → respect autoUnzipFileLimit
|
||||
* 3. Extract files if appropriate
|
||||
*
|
||||
* @param zipBlob - The ZIP blob to process
|
||||
* @param options - Extraction options
|
||||
* @returns Array of files (either extracted or the ZIP itself)
|
||||
*/
|
||||
async extractWithPreferences(
|
||||
zipBlob: Blob,
|
||||
options: {
|
||||
autoUnzip: boolean;
|
||||
autoUnzipFileLimit: number;
|
||||
skipAutoUnzip?: boolean;
|
||||
}
|
||||
): Promise<File[]> {
|
||||
try {
|
||||
// Create File object if not already
|
||||
const zipFile = zipBlob instanceof File
|
||||
? zipBlob
|
||||
: new File([zipBlob], 'result.zip', { type: 'application/zip' });
|
||||
|
||||
// Check if ZIP contains HTML files - if so, keep as ZIP
|
||||
const containsHtml = await this.containsHtmlFiles(zipFile);
|
||||
if (containsHtml) {
|
||||
return [zipFile];
|
||||
}
|
||||
|
||||
// Check if we should extract based on preferences
|
||||
const shouldExtract = await this.shouldUnzip(
|
||||
zipBlob,
|
||||
options.autoUnzip,
|
||||
options.autoUnzipFileLimit,
|
||||
options.skipAutoUnzip || false
|
||||
);
|
||||
|
||||
if (!shouldExtract) {
|
||||
return [zipFile];
|
||||
}
|
||||
|
||||
// Extract all files
|
||||
const extractionResult = await this.extractAllFiles(zipFile);
|
||||
return extractionResult.success ? extractionResult.extractedFiles : [zipFile];
|
||||
} catch (error) {
|
||||
console.error('Error in extractWithPreferences:', error);
|
||||
// On error, return ZIP as-is
|
||||
const zipFile = zipBlob instanceof File
|
||||
? zipBlob
|
||||
: new File([zipBlob], 'result.zip', { type: 'application/zip' });
|
||||
return [zipFile];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all files from a ZIP archive (not limited to PDFs)
|
||||
*/
|
||||
@@ -486,9 +577,11 @@ export class ZipFileService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract PDF files from ZIP and store them in IndexedDB with preserved history metadata
|
||||
* Extract all files from ZIP and store them in IndexedDB with preserved history metadata
|
||||
* Used by both FileManager and FileEditor to avoid code duplication
|
||||
*
|
||||
* Note: HTML files will NOT be extracted - the ZIP is kept intact when HTML is detected
|
||||
*
|
||||
* @param zipFile - The ZIP file to extract from
|
||||
* @param zipStub - The StirlingFileStub for the ZIP (contains metadata to preserve)
|
||||
* @returns Object with success status, extracted stubs, and any errors
|
||||
@@ -504,8 +597,15 @@ export class ZipFileService {
|
||||
};
|
||||
|
||||
try {
|
||||
// Extract PDF files from ZIP
|
||||
const extractionResult = await this.extractPdfFiles(zipFile);
|
||||
// Check if ZIP contains HTML files - if so, don't extract
|
||||
const hasHtml = await this.containsHtmlFiles(zipFile);
|
||||
if (hasHtml) {
|
||||
result.errors.push('ZIP contains HTML files and will not be auto-extracted. Download the ZIP to access the files.');
|
||||
return result;
|
||||
}
|
||||
|
||||
// Extract all files from ZIP (not just PDFs)
|
||||
const extractionResult = await this.extractAllFiles(zipFile);
|
||||
|
||||
if (!extractionResult.success || extractionResult.extractedFiles.length === 0) {
|
||||
result.errors = extractionResult.errors;
|
||||
@@ -515,7 +615,7 @@ export class ZipFileService {
|
||||
// Process each extracted file
|
||||
for (const extractedFile of extractionResult.extractedFiles) {
|
||||
try {
|
||||
// Generate thumbnail
|
||||
// Generate thumbnail (works for PDFs and images)
|
||||
const thumbnail = await generateThumbnailForFile(extractedFile);
|
||||
|
||||
// Create StirlingFile
|
||||
|
||||
Reference in New Issue
Block a user