Unified extraction method

This commit is contained in:
Connor Yoh
2025-10-10 16:20:49 +01:00
parent ebe6ef1581
commit 7460177ace
5 changed files with 68 additions and 67 deletions

View File

@@ -25,13 +25,13 @@ export const extractImagesOperationConfig = {
export const useExtractImagesOperation = () => {
const { t } = useTranslation();
const { extractAllZipFiles } = useToolResources();
const { extractZipFiles } = useToolResources();
// Response handler that respects auto-unzip preferences
const responseHandler = useCallback(async (blob: Blob, _originalFiles: File[]): Promise<File[]> => {
// Extract images returns a ZIP file - use preference-aware extraction
return await extractAllZipFiles(blob);
}, [extractAllZipFiles]);
return await extractZipFiles(blob);
}, [extractZipFiles]);
return useToolOperation<ExtractImagesParameters>({
...extractImagesOperationConfig,

View File

@@ -27,14 +27,14 @@ export const scannerImageSplitOperationConfig = {
export const useScannerImageSplitOperation = () => {
const { t } = useTranslation();
const { extractAllZipFiles } = useToolResources();
const { extractZipFiles } = useToolResources();
// Custom response handler that extracts ZIP files containing images
// Can't add to exported config because it requires access to the hook so must be part of the hook
const responseHandler = useCallback(async (blob: Blob, originalFiles: File[]): Promise<File[]> => {
try {
// Scanner image split returns ZIP files with multiple images
const extractedFiles = await extractAllZipFiles(blob);
const extractedFiles = await extractZipFiles(blob);
// If extraction succeeded and returned files, use them
if (extractedFiles.length > 0) {
@@ -49,7 +49,7 @@ export const useScannerImageSplitOperation = () => {
const baseFileName = inputFileName.replace(/\.[^.]+$/, '');
const singleFile = new File([blob], `${baseFileName}.png`, { type: 'image/png' });
return [singleFile];
}, [extractAllZipFiles]);
}, [extractZipFiles]);
const config: ToolOperationConfig<ScannerImageSplitParameters> = {
...scannerImageSplitOperationConfig,

View File

@@ -151,7 +151,7 @@ export const useToolOperation = <TParams>(
const { state, actions } = useToolState();
const { actions: fileActions } = useFileContext();
const { processFiles, cancelOperation: cancelApiCalls } = useToolApiCalls<TParams>();
const { generateThumbnails, createDownloadInfo, cleanupBlobUrls, extractZipFiles, extractAllZipFiles } = useToolResources();
const { generateThumbnails, createDownloadInfo, cleanupBlobUrls, extractZipFiles } = useToolResources();
// Track last operation for undo functionality
const lastOperationRef = useRef<{
@@ -259,11 +259,6 @@ export const useToolOperation = <TParams>(
// Default: assume ZIP response for multi-file endpoints
// Note: extractZipFiles will check preferences.autoUnzip setting
processedFiles = await extractZipFiles(response.data);
if (processedFiles.length === 0) {
// Try the generic extraction as fallback
processedFiles = await extractAllZipFiles(response.data);
}
}
// Assume all inputs succeeded together unless server provided an error earlier
successSourceIds = validFiles.map(f => (f as any).fileId) as any;
@@ -446,7 +441,7 @@ export const useToolOperation = <TParams>(
actions.setLoading(false);
actions.setProgress(null);
}
}, [t, config, actions, addFiles, consumeFiles, processFiles, generateThumbnails, createDownloadInfo, cleanupBlobUrls, extractZipFiles, extractAllZipFiles]);
}, [t, config, actions, addFiles, consumeFiles, processFiles, generateThumbnails, createDownloadInfo, cleanupBlobUrls, extractZipFiles]);
const cancelOperation = useCallback(() => {
cancelApiCalls();

View File

@@ -85,66 +85,17 @@ export const useToolResources = () => {
const extractZipFiles = useCallback(async (zipBlob: Blob, skipAutoUnzip = false): Promise<File[]> => {
try {
// Check if ZIP contains HTML files - if so, keep as ZIP
const zipFile = new File([zipBlob], 'temp.zip', { type: 'application/zip' });
const containsHtml = await zipFileService.containsHtmlFiles(zipFile);
if (containsHtml) {
// HTML files should stay zipped
return [new File([zipBlob], 'result.zip', { type: 'application/zip' })];
}
// Check if we should extract based on preferences
const shouldExtract = await zipFileService.shouldUnzip(
zipBlob,
preferences.autoUnzip,
preferences.autoUnzipFileLimit,
return await zipFileService.extractWithPreferences(zipBlob, {
autoUnzip: preferences.autoUnzip,
autoUnzipFileLimit: preferences.autoUnzipFileLimit,
skipAutoUnzip
);
if (!shouldExtract) {
return [new File([zipBlob], 'result.zip', { type: 'application/zip' })];
}
const extractionResult = await zipFileService.extractAllFiles(zipFile);
return extractionResult.success ? extractionResult.extractedFiles : [];
});
} catch (error) {
console.error('useToolResources.extractZipFiles - Error:', error);
return [];
}
}, [preferences.autoUnzip, preferences.autoUnzipFileLimit]);
const extractAllZipFiles = useCallback(async (zipBlob: Blob, skipAutoUnzip = false): Promise<File[]> => {
try {
// Check if ZIP contains HTML files - if so, keep as ZIP
const zipFile = new File([zipBlob], 'temp.zip', { type: 'application/zip' });
const containsHtml = await zipFileService.containsHtmlFiles(zipFile);
if (containsHtml) {
// HTML files should stay zipped
return [new File([zipBlob], 'result.zip', { type: 'application/zip' })];
}
// Check if we should extract based on preferences
const shouldExtract = await zipFileService.shouldUnzip(
zipBlob,
preferences.autoUnzip,
preferences.autoUnzipFileLimit,
skipAutoUnzip
);
if (!shouldExtract) {
return [new File([zipBlob], 'result.zip', { type: 'application/zip' })];
}
const extractionResult = await zipFileService.extractAllFiles(zipFile);
return extractionResult.success ? extractionResult.extractedFiles : [];
} catch (error) {
console.error('useToolResources.extractAllZipFiles - Error:', error);
return [];
}
}, [preferences.autoUnzip, preferences.autoUnzipFileLimit]);
const createDownloadInfo = useCallback(async (
files: File[],
operationType: string
@@ -168,7 +119,6 @@ export const useToolResources = () => {
generateThumbnailsWithMetadata,
createDownloadInfo,
extractZipFiles,
extractAllZipFiles,
cleanupBlobUrls,
};
};

View File

@@ -401,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)
*/