mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-11-16 01:21:16 +01:00
refactor(core): centralize temp file handling in CompressController via TempFileManager (#4629)
# Description of Changes ## What was changed - Introduced `TempFileManager` and injected it into `CompressController` to centralize and control temporary file lifecycle. - Replaced ad-hoc `Files.createTempFile(...)` usages with a new `TempFile` abstraction: - `compressImagesInPDF(...)` now returns a `TempFile` instead of a `Path`. - All intermediate artifacts (original/working/GS/QPDF outputs) are created via `TempFile` and managed with try-with-resources where applicable. - Removed the mutable `List<Path> tempFiles` bookkeeping; cleanup is handled by `TempFile.close()` and a single `finally` block that closes all tracked `TempFile` instances. - Updated save/copy calls to use `TempFile` accessors (`getPath()`, `getAbsolutePath()`, `getFile()`). - Hardened error handling: - Ensured `TempFile` is closed on early exceptions (e.g., in `compressImagesInPDF`). - Ghostscript/QPDF helpers now encapsulate their output lifecycle and no longer accept/require a temp file list. - Minor Java refinements: - Used pattern matching for `instanceof` (e.g., `if (ref instanceof NestedImageReference nestedRef)`). - Improved and wrapped long log messages for readability and consistency. ## Why the change was made - **Resource safety:** Prevent orphaned temp files and reduce file-descriptor leaks under failure conditions or multi-step pipelines. - **Consistency:** Establish a single, testable mechanism for temp file creation, placement, and cleanup across compression flows. - **Portability & stability:** Avoid Windows file-locking/delete-in-use issues by using explicit close semantics and predictable lifetimes. - **Maintainability:** Simplify control flow by removing ad-hoc temp tracking and pushing lifecycle ownership into `TempFile`. --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/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/devGuide/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] 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/devGuide/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/devGuide/DeveloperGuide.md#6-testing) for more details.
This commit is contained in:
parent
e4cfb8befe
commit
fdc8fab545
@ -28,6 +28,17 @@ public class TempFileManager {
|
|||||||
private final TempFileRegistry registry;
|
private final TempFileRegistry registry;
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a managed temporary file that will be tracked by the TempFileManager.
|
||||||
|
*
|
||||||
|
* @param suffix The suffix for the temporary file
|
||||||
|
* @return The created temporary file wrapper
|
||||||
|
* @throws IOException If an I/O error occurs
|
||||||
|
*/
|
||||||
|
public TempFile createManagedTempFile(String suffix) throws IOException {
|
||||||
|
return new TempFile(this, suffix);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a temporary file with the Stirling-PDF prefix. The file is automatically registered
|
* Create a temporary file with the Stirling-PDF prefix. The file is automatically registered
|
||||||
* with the registry.
|
* with the registry.
|
||||||
@ -130,7 +141,6 @@ public class TempFileManager {
|
|||||||
return deleted;
|
return deleted;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.warn("Failed to delete temp file: {}", path.toString(), e);
|
log.warn("Failed to delete temp file: {}", path.toString(), e);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -49,6 +49,8 @@ import stirling.software.common.util.ExceptionUtils;
|
|||||||
import stirling.software.common.util.GeneralUtils;
|
import stirling.software.common.util.GeneralUtils;
|
||||||
import stirling.software.common.util.ProcessExecutor;
|
import stirling.software.common.util.ProcessExecutor;
|
||||||
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
||||||
|
import stirling.software.common.util.TempFile;
|
||||||
|
import stirling.software.common.util.TempFileManager;
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@ -60,6 +62,7 @@ public class CompressController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
private final EndpointConfiguration endpointConfiguration;
|
private final EndpointConfiguration endpointConfiguration;
|
||||||
|
private final TempFileManager tempFileManager;
|
||||||
|
|
||||||
private boolean isQpdfEnabled() {
|
private boolean isQpdfEnabled() {
|
||||||
return endpointConfiguration.isGroupEnabled("qpdf");
|
return endpointConfiguration.isGroupEnabled("qpdf");
|
||||||
@ -97,13 +100,14 @@ public class CompressController {
|
|||||||
long totalCompressedBytes = 0;
|
long totalCompressedBytes = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Path compressImagesInPDF(
|
public TempFile compressImagesInPDF(
|
||||||
Path pdfFile, double scaleFactor, float jpegQuality, boolean convertToGrayscale)
|
Path pdfFile, double scaleFactor, float jpegQuality, boolean convertToGrayscale)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Path newCompressedPDF = Files.createTempFile("compressedPDF", ".pdf");
|
TempFile newCompressedPDF = tempFileManager.createManagedTempFile(".pdf");
|
||||||
long originalFileSize = Files.size(pdfFile);
|
long originalFileSize = Files.size(pdfFile);
|
||||||
log.info(
|
log.info(
|
||||||
"Starting image compression with scale factor: {}, JPEG quality: {}, grayscale: {} on file size: {}",
|
"Starting image compression with scale factor: {}, JPEG quality: {}, grayscale: {}"
|
||||||
|
+ " on file size: {}",
|
||||||
scaleFactor,
|
scaleFactor,
|
||||||
jpegQuality,
|
jpegQuality,
|
||||||
convertToGrayscale,
|
convertToGrayscale,
|
||||||
@ -133,11 +137,11 @@ public class CompressController {
|
|||||||
compressedVersions.clear();
|
compressedVersions.clear();
|
||||||
uniqueImages.clear();
|
uniqueImages.clear();
|
||||||
|
|
||||||
log.info("Saving compressed PDF to {}", newCompressedPDF.toString());
|
log.info("Saving compressed PDF to {}", newCompressedPDF.getPath());
|
||||||
doc.save(newCompressedPDF.toString());
|
doc.save(newCompressedPDF.getAbsolutePath());
|
||||||
|
|
||||||
// Log overall file size reduction
|
// Log overall file size reduction
|
||||||
long compressedFileSize = Files.size(newCompressedPDF);
|
long compressedFileSize = Files.size(newCompressedPDF.getPath());
|
||||||
double overallReduction = 100.0 - ((compressedFileSize * 100.0) / originalFileSize);
|
double overallReduction = 100.0 - ((compressedFileSize * 100.0) / originalFileSize);
|
||||||
log.info(
|
log.info(
|
||||||
"Overall PDF compression: {} → {} (reduced by {}%)",
|
"Overall PDF compression: {} → {} (reduced by {}%)",
|
||||||
@ -145,6 +149,9 @@ public class CompressController {
|
|||||||
GeneralUtils.formatBytes(compressedFileSize),
|
GeneralUtils.formatBytes(compressedFileSize),
|
||||||
String.format("%.1f", overallReduction));
|
String.format("%.1f", overallReduction));
|
||||||
return newCompressedPDF;
|
return newCompressedPDF;
|
||||||
|
} catch (Exception e) {
|
||||||
|
newCompressedPDF.close();
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,9 +334,8 @@ public class CompressController {
|
|||||||
|
|
||||||
// Get original image from a reference
|
// Get original image from a reference
|
||||||
private PDImageXObject getOriginalImage(PDDocument doc, ImageReference ref) throws IOException {
|
private PDImageXObject getOriginalImage(PDDocument doc, ImageReference ref) throws IOException {
|
||||||
if (ref instanceof NestedImageReference) {
|
if (ref instanceof NestedImageReference nestedRef) {
|
||||||
// Get the nested image from within a form XObject
|
// Get the nested image from within a form XObject
|
||||||
NestedImageReference nestedRef = (NestedImageReference) ref;
|
|
||||||
PDPage page = doc.getPage(nestedRef.pageNum);
|
PDPage page = doc.getPage(nestedRef.pageNum);
|
||||||
PDResources pageResources = page.getResources();
|
PDResources pageResources = page.getResources();
|
||||||
|
|
||||||
@ -405,9 +411,8 @@ public class CompressController {
|
|||||||
// Replace a specific image reference with a compressed version
|
// Replace a specific image reference with a compressed version
|
||||||
private void replaceImageReference(
|
private void replaceImageReference(
|
||||||
PDDocument doc, ImageReference ref, PDImageXObject compressedImage) throws IOException {
|
PDDocument doc, ImageReference ref, PDImageXObject compressedImage) throws IOException {
|
||||||
if (ref instanceof NestedImageReference) {
|
if (ref instanceof NestedImageReference nestedRef) {
|
||||||
// Replace nested image within form XObject
|
// Replace nested image within form XObject
|
||||||
NestedImageReference nestedRef = (NestedImageReference) ref;
|
|
||||||
PDPage page = doc.getPage(nestedRef.pageNum);
|
PDPage page = doc.getPage(nestedRef.pageNum);
|
||||||
PDResources pageResources = page.getResources();
|
PDResources pageResources = page.getResources();
|
||||||
|
|
||||||
@ -444,7 +449,8 @@ public class CompressController {
|
|||||||
int duplicatedImages = stats.totalImages - stats.uniqueImagesCount;
|
int duplicatedImages = stats.totalImages - stats.uniqueImagesCount;
|
||||||
|
|
||||||
log.info(
|
log.info(
|
||||||
"Image compression summary - Total unique: {}, Compressed: {}, Skipped: {}, Duplicates: {}, Nested: {}",
|
"Image compression summary - Total unique: {}, Compressed: {}, Skipped: {},"
|
||||||
|
+ " Duplicates: {}, Nested: {}",
|
||||||
stats.uniqueImagesCount,
|
stats.uniqueImagesCount,
|
||||||
stats.compressedImages,
|
stats.compressedImages,
|
||||||
stats.skippedImages,
|
stats.skippedImages,
|
||||||
@ -673,18 +679,20 @@ public class CompressController {
|
|||||||
autoMode = true;
|
autoMode = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<TempFile> tempFiles = new ArrayList<>();
|
||||||
|
|
||||||
// Create initial input file
|
// Create initial input file
|
||||||
Path originalFile = Files.createTempFile("original_", ".pdf");
|
TempFile originalTempFile = tempFileManager.createManagedTempFile(".pdf");
|
||||||
inputFile.transferTo(originalFile.toFile());
|
tempFiles.add(originalTempFile);
|
||||||
|
Path originalFile = originalTempFile.getPath();
|
||||||
|
inputFile.transferTo(originalTempFile.getFile());
|
||||||
long inputFileSize = Files.size(originalFile);
|
long inputFileSize = Files.size(originalFile);
|
||||||
|
|
||||||
Path currentFile = Files.createTempFile("working_", ".pdf");
|
TempFile currentTempFile = tempFileManager.createManagedTempFile(".pdf");
|
||||||
|
tempFiles.add(currentTempFile);
|
||||||
|
Path currentFile = currentTempFile.getPath();
|
||||||
Files.copy(originalFile, currentFile, StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(originalFile, currentFile, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
|
||||||
// Keep track of all temporary files for cleanup
|
|
||||||
List<Path> tempFiles = new ArrayList<>();
|
|
||||||
tempFiles.add(originalFile);
|
|
||||||
tempFiles.add(currentFile);
|
|
||||||
try {
|
try {
|
||||||
if (autoMode) {
|
if (autoMode) {
|
||||||
double sizeReductionRatio = expectedOutputSize / (double) inputFileSize;
|
double sizeReductionRatio = expectedOutputSize / (double) inputFileSize;
|
||||||
@ -703,8 +711,7 @@ public class CompressController {
|
|||||||
// Try Ghostscript first if available - for ANY compression level
|
// Try Ghostscript first if available - for ANY compression level
|
||||||
if (isGhostscriptEnabled()) {
|
if (isGhostscriptEnabled()) {
|
||||||
try {
|
try {
|
||||||
applyGhostscriptCompression(
|
applyGhostscriptCompression(request, optimizeLevel, currentFile);
|
||||||
request, optimizeLevel, currentFile, tempFiles);
|
|
||||||
log.info("Ghostscript compression applied successfully");
|
log.info("Ghostscript compression applied successfully");
|
||||||
ghostscriptSuccess = true;
|
ghostscriptSuccess = true;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@ -715,7 +722,7 @@ public class CompressController {
|
|||||||
// Fallback to QPDF if Ghostscript failed or not available (levels 1-3 only)
|
// Fallback to QPDF if Ghostscript failed or not available (levels 1-3 only)
|
||||||
if (!ghostscriptSuccess && isQpdfEnabled() && optimizeLevel <= 3) {
|
if (!ghostscriptSuccess && isQpdfEnabled() && optimizeLevel <= 3) {
|
||||||
try {
|
try {
|
||||||
applyQpdfCompression(request, optimizeLevel, currentFile, tempFiles);
|
applyQpdfCompression(request, optimizeLevel, currentFile);
|
||||||
log.info("QPDF compression applied successfully");
|
log.info("QPDF compression applied successfully");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.warn("QPDF compression also failed");
|
log.warn("QPDF compression also failed");
|
||||||
@ -724,7 +731,8 @@ public class CompressController {
|
|||||||
|
|
||||||
if (!ghostscriptSuccess && !isQpdfEnabled()) {
|
if (!ghostscriptSuccess && !isQpdfEnabled()) {
|
||||||
log.info(
|
log.info(
|
||||||
"No external compression tools available, using image compression only");
|
"No external compression tools available, using image compression"
|
||||||
|
+ " only");
|
||||||
}
|
}
|
||||||
|
|
||||||
externalCompressionApplied = true;
|
externalCompressionApplied = true;
|
||||||
@ -751,7 +759,7 @@ public class CompressController {
|
|||||||
};
|
};
|
||||||
|
|
||||||
log.info("Applying image compression with scale factor: {}", scaleFactor);
|
log.info("Applying image compression with scale factor: {}", scaleFactor);
|
||||||
Path compressedImageFile =
|
TempFile compressedImageFile =
|
||||||
compressImagesInPDF(
|
compressImagesInPDF(
|
||||||
currentFile,
|
currentFile,
|
||||||
scaleFactor,
|
scaleFactor,
|
||||||
@ -759,7 +767,7 @@ public class CompressController {
|
|||||||
Boolean.TRUE.equals(convertToGrayscale));
|
Boolean.TRUE.equals(convertToGrayscale));
|
||||||
|
|
||||||
tempFiles.add(compressedImageFile);
|
tempFiles.add(compressedImageFile);
|
||||||
currentFile = compressedImageFile;
|
currentFile = compressedImageFile.getPath();
|
||||||
imageCompressionApplied = true;
|
imageCompressionApplied = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -789,7 +797,8 @@ public class CompressController {
|
|||||||
long finalFileSize = Files.size(currentFile);
|
long finalFileSize = Files.size(currentFile);
|
||||||
if (finalFileSize >= inputFileSize) {
|
if (finalFileSize >= inputFileSize) {
|
||||||
log.warn(
|
log.warn(
|
||||||
"Optimized file is larger than the original. Using the original file instead.");
|
"Optimized file is larger than the original. Using the original file"
|
||||||
|
+ " instead.");
|
||||||
currentFile = originalFile;
|
currentFile = originalFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -802,10 +811,10 @@ public class CompressController {
|
|||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
// Clean up all temporary files
|
// Clean up all temporary files
|
||||||
for (Path tempFile : tempFiles) {
|
for (TempFile tempFile : tempFiles) {
|
||||||
try {
|
try {
|
||||||
Files.deleteIfExists(tempFile);
|
tempFile.close();
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
log.warn("Failed to delete temporary file: {}", tempFile, e);
|
log.warn("Failed to delete temporary file: {}", tempFile, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -814,98 +823,99 @@ public class CompressController {
|
|||||||
|
|
||||||
// Run Ghostscript compression
|
// Run Ghostscript compression
|
||||||
private void applyGhostscriptCompression(
|
private void applyGhostscriptCompression(
|
||||||
OptimizePdfRequest request, int optimizeLevel, Path currentFile, List<Path> tempFiles)
|
OptimizePdfRequest request, int optimizeLevel, Path currentFile) throws IOException {
|
||||||
throws IOException {
|
|
||||||
|
|
||||||
long preGsSize = Files.size(currentFile);
|
long preGsSize = Files.size(currentFile);
|
||||||
log.info("Pre-Ghostscript file size: {}", GeneralUtils.formatBytes(preGsSize));
|
log.info("Pre-Ghostscript file size: {}", GeneralUtils.formatBytes(preGsSize));
|
||||||
|
|
||||||
// Create output file for Ghostscript
|
try (TempFile gsOutputFile = tempFileManager.createManagedTempFile(".pdf")) {
|
||||||
Path gsOutputFile = Files.createTempFile("gs_output_", ".pdf");
|
Path gsOutputPath = gsOutputFile.getPath();
|
||||||
tempFiles.add(gsOutputFile);
|
|
||||||
|
|
||||||
// Build Ghostscript command based on optimization level
|
// Build Ghostscript command based on optimization level
|
||||||
List<String> command = new ArrayList<>();
|
List<String> command = new ArrayList<>();
|
||||||
command.add("gs");
|
command.add("gs");
|
||||||
command.add("-sDEVICE=pdfwrite");
|
command.add("-sDEVICE=pdfwrite");
|
||||||
command.add("-dCompatibilityLevel=1.5");
|
command.add("-dCompatibilityLevel=1.5");
|
||||||
command.add("-dNOPAUSE");
|
command.add("-dNOPAUSE");
|
||||||
command.add("-dQUIET");
|
command.add("-dQUIET");
|
||||||
command.add("-dBATCH");
|
command.add("-dBATCH");
|
||||||
|
|
||||||
// Map optimization levels to Ghostscript settings
|
// Map optimization levels to Ghostscript settings
|
||||||
switch (optimizeLevel) {
|
switch (optimizeLevel) {
|
||||||
case 1:
|
case 1:
|
||||||
command.add("-dPDFSETTINGS=/prepress");
|
command.add("-dPDFSETTINGS=/prepress");
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
command.add("-dPDFSETTINGS=/printer");
|
command.add("-dPDFSETTINGS=/printer");
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
command.add("-dPDFSETTINGS=/ebook");
|
command.add("-dPDFSETTINGS=/ebook");
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
case 5:
|
case 5:
|
||||||
command.add("-dPDFSETTINGS=/screen");
|
command.add("-dPDFSETTINGS=/screen");
|
||||||
break;
|
break;
|
||||||
case 6:
|
case 6:
|
||||||
case 7:
|
case 7:
|
||||||
command.add("-dPDFSETTINGS=/screen");
|
command.add("-dPDFSETTINGS=/screen");
|
||||||
command.add("-dColorImageResolution=150");
|
command.add("-dColorImageResolution=150");
|
||||||
command.add("-dGrayImageResolution=150");
|
command.add("-dGrayImageResolution=150");
|
||||||
command.add("-dMonoImageResolution=300");
|
command.add("-dMonoImageResolution=300");
|
||||||
break;
|
break;
|
||||||
case 8:
|
case 8:
|
||||||
case 9:
|
case 9:
|
||||||
command.add("-dPDFSETTINGS=/screen");
|
command.add("-dPDFSETTINGS=/screen");
|
||||||
command.add("-dColorImageResolution=100");
|
command.add("-dColorImageResolution=100");
|
||||||
command.add("-dGrayImageResolution=100");
|
command.add("-dGrayImageResolution=100");
|
||||||
command.add("-dMonoImageResolution=200");
|
command.add("-dMonoImageResolution=200");
|
||||||
break;
|
break;
|
||||||
case 10:
|
case 10:
|
||||||
command.add("-dPDFSETTINGS=/screen");
|
command.add("-dPDFSETTINGS=/screen");
|
||||||
command.add("-dColorImageResolution=72");
|
command.add("-dColorImageResolution=72");
|
||||||
command.add("-dGrayImageResolution=72");
|
command.add("-dGrayImageResolution=72");
|
||||||
command.add("-dMonoImageResolution=150");
|
command.add("-dMonoImageResolution=150");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
command.add("-dPDFSETTINGS=/screen");
|
command.add("-dPDFSETTINGS=/screen");
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
command.add("-sOutputFile=" + gsOutputFile.toString());
|
|
||||||
command.add(currentFile.toString());
|
|
||||||
|
|
||||||
ProcessExecutorResult returnCode;
|
|
||||||
try {
|
|
||||||
returnCode =
|
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT)
|
|
||||||
.runCommandWithOutputHandling(command);
|
|
||||||
|
|
||||||
if (returnCode.getRc() == 0) {
|
|
||||||
// Update current file to the Ghostscript output
|
|
||||||
Files.copy(gsOutputFile, currentFile, StandardCopyOption.REPLACE_EXISTING);
|
|
||||||
|
|
||||||
long postGsSize = Files.size(currentFile);
|
|
||||||
double gsReduction = 100.0 - ((postGsSize * 100.0) / preGsSize);
|
|
||||||
log.info(
|
|
||||||
"Post-Ghostscript file size: {} (reduced by {}%)",
|
|
||||||
GeneralUtils.formatBytes(postGsSize), String.format("%.1f", gsReduction));
|
|
||||||
} else {
|
|
||||||
log.warn("Ghostscript compression failed with return code: {}", returnCode.getRc());
|
|
||||||
throw new IOException("Ghostscript compression failed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
command.add("-sOutputFile=" + gsOutputPath.toString());
|
||||||
log.warn("Ghostscript compression failed, will fallback to other methods", e);
|
command.add(currentFile.toString());
|
||||||
throw new IOException("Ghostscript compression failed", e);
|
|
||||||
|
ProcessExecutorResult returnCode;
|
||||||
|
try {
|
||||||
|
returnCode =
|
||||||
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT)
|
||||||
|
.runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
|
if (returnCode.getRc() == 0) {
|
||||||
|
// Update current file to the Ghostscript output
|
||||||
|
Files.copy(gsOutputPath, currentFile, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
|
||||||
|
long postGsSize = Files.size(currentFile);
|
||||||
|
double gsReduction = 100.0 - ((postGsSize * 100.0) / preGsSize);
|
||||||
|
log.info(
|
||||||
|
"Post-Ghostscript file size: {} (reduced by {}%)",
|
||||||
|
GeneralUtils.formatBytes(postGsSize),
|
||||||
|
String.format("%.1f", gsReduction));
|
||||||
|
} else {
|
||||||
|
log.warn(
|
||||||
|
"Ghostscript compression failed with return code: {}",
|
||||||
|
returnCode.getRc());
|
||||||
|
throw new IOException("Ghostscript compression failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Ghostscript compression failed, will fallback to other methods", e);
|
||||||
|
throw new IOException("Ghostscript compression failed", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run QPDF compression
|
// Run QPDF compression
|
||||||
private void applyQpdfCompression(
|
private void applyQpdfCompression(
|
||||||
OptimizePdfRequest request, int optimizeLevel, Path currentFile, List<Path> tempFiles)
|
OptimizePdfRequest request, int optimizeLevel, Path currentFile) throws IOException {
|
||||||
throws IOException {
|
|
||||||
|
|
||||||
long preQpdfSize = Files.size(currentFile);
|
long preQpdfSize = Files.size(currentFile);
|
||||||
log.info("Pre-QPDF file size: {}", GeneralUtils.formatBytes(preQpdfSize));
|
log.info("Pre-QPDF file size: {}", GeneralUtils.formatBytes(preQpdfSize));
|
||||||
@ -920,47 +930,48 @@ public class CompressController {
|
|||||||
qpdfCompressionLevel = 9;
|
qpdfCompressionLevel = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create output file for QPDF
|
try (TempFile qpdfOutputFile = tempFileManager.createManagedTempFile(".pdf")) {
|
||||||
Path qpdfOutputFile = Files.createTempFile("qpdf_output_", ".pdf");
|
Path qpdfOutputPath = qpdfOutputFile.getPath();
|
||||||
tempFiles.add(qpdfOutputFile);
|
|
||||||
|
|
||||||
// Build QPDF command
|
// Build QPDF command
|
||||||
List<String> command = new ArrayList<>();
|
List<String> command = new ArrayList<>();
|
||||||
command.add("qpdf");
|
command.add("qpdf");
|
||||||
if (request.getNormalize()) {
|
if (request.getNormalize()) {
|
||||||
command.add("--normalize-content=y");
|
command.add("--normalize-content=y");
|
||||||
}
|
}
|
||||||
if (request.getLinearize()) {
|
if (request.getLinearize()) {
|
||||||
command.add("--linearize");
|
command.add("--linearize");
|
||||||
}
|
}
|
||||||
command.add("--recompress-flate");
|
command.add("--recompress-flate");
|
||||||
command.add("--compression-level=" + qpdfCompressionLevel);
|
command.add("--compression-level=" + qpdfCompressionLevel);
|
||||||
command.add("--compress-streams=y");
|
command.add("--compress-streams=y");
|
||||||
command.add("--object-streams=generate");
|
command.add("--object-streams=generate");
|
||||||
command.add(currentFile.toString());
|
command.add(currentFile.toString());
|
||||||
command.add(qpdfOutputFile.toString());
|
command.add(qpdfOutputPath.toString());
|
||||||
|
|
||||||
ProcessExecutorResult returnCode = null;
|
ProcessExecutorResult returnCode = null;
|
||||||
try {
|
try {
|
||||||
returnCode =
|
returnCode =
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.QPDF)
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.QPDF)
|
||||||
.runCommandWithOutputHandling(command);
|
.runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
// Update current file to the QPDF output
|
// Update current file to the QPDF output
|
||||||
Files.copy(qpdfOutputFile, currentFile, StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(qpdfOutputPath, currentFile, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
|
||||||
long postQpdfSize = Files.size(currentFile);
|
long postQpdfSize = Files.size(currentFile);
|
||||||
double qpdfReduction = 100.0 - ((postQpdfSize * 100.0) / preQpdfSize);
|
double qpdfReduction = 100.0 - ((postQpdfSize * 100.0) / preQpdfSize);
|
||||||
log.info(
|
log.info(
|
||||||
"Post-QPDF file size: {} (reduced by {}%)",
|
"Post-QPDF file size: {} (reduced by {}%)",
|
||||||
GeneralUtils.formatBytes(postQpdfSize), String.format("%.1f", qpdfReduction));
|
GeneralUtils.formatBytes(postQpdfSize),
|
||||||
|
String.format("%.1f", qpdfReduction));
|
||||||
} catch (Exception e) {
|
|
||||||
if (returnCode != null && returnCode.getRc() != 3) {
|
} catch (Exception e) {
|
||||||
throw new IOException("QPDF command failed", e);
|
if (returnCode != null && returnCode.getRc() != 3) {
|
||||||
|
throw new IOException("QPDF command failed", e);
|
||||||
|
}
|
||||||
|
// If QPDF fails, keep using the current file
|
||||||
|
log.warn("QPDF compression failed, continuing with current file", e);
|
||||||
}
|
}
|
||||||
// If QPDF fails, keep using the current file
|
|
||||||
log.warn("QPDF compression failed, continuing with current file", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user