diff --git a/app/common/src/main/java/stirling/software/common/util/GeneralUtils.java b/app/common/src/main/java/stirling/software/common/util/GeneralUtils.java index be73e51e2..ddbec92e0 100644 --- a/app/common/src/main/java/stirling/software/common/util/GeneralUtils.java +++ b/app/common/src/main/java/stirling/software/common/util/GeneralUtils.java @@ -16,7 +16,6 @@ import java.util.List; import java.util.Locale; import java.util.UUID; -import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.ResourcePatternUtils; @@ -443,40 +442,6 @@ public class GeneralUtils { } } - /** - * Extracts a file from classpath:/static/python to a temporary directory and returns the path. - */ - public static Path extractScript(String scriptName) throws IOException { - // Validate input - if (scriptName == null || scriptName.trim().isEmpty()) { - throw new IllegalArgumentException("scriptName must not be null or empty"); - } - if (scriptName.contains("..") || scriptName.contains("/")) { - throw new IllegalArgumentException( - "scriptName must not contain path traversal characters"); - } - - List validScripts = Arrays.asList("png_to_webp.py", "split_photos.py"); - - if (!validScripts.contains(scriptName)) { - throw new IllegalArgumentException( - "scriptName must be either 'png_to_webp.py' or 'split_photos.py'"); - } - // 1. load the script from classpath - ClassPathResource resource = new ClassPathResource("static/python/" + scriptName); - - // 2. create a temporary directory - Path tmpDir = Files.createTempDirectory("stirling-pdf-scripts"); - tmpDir.toFile().deleteOnExit(); - - // 3. copy the script to the temporary directory - Path scriptFile = tmpDir.resolve(scriptName); - try (InputStream in = resource.getInputStream()) { - Files.copy(in, scriptFile, StandardCopyOption.REPLACE_EXISTING); - return scriptFile; - } - } - public static boolean isVersionHigher(String currentVersion, String compareVersion) { if (currentVersion == null || compareVersion == null) { return false; diff --git a/app/common/src/main/java/stirling/software/common/util/TempFileManager.java b/app/common/src/main/java/stirling/software/common/util/TempFileManager.java index 867931f8b..8d43351a4 100644 --- a/app/common/src/main/java/stirling/software/common/util/TempFileManager.java +++ b/app/common/src/main/java/stirling/software/common/util/TempFileManager.java @@ -2,12 +2,17 @@ package stirling.software.common.util; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.time.Duration; +import java.util.Arrays; +import java.util.List; import java.util.Set; import java.util.UUID; +import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -246,4 +251,62 @@ public class TempFileManager { return registry.registerDirectory(loTempDir); } + + /** + * Extracts a Python script from classpath:/static/python to a registered temp directory + * and returns the script's path. + * + * @param scriptName name of the script (png_to_webp.py or split_photos.py) + * @return Path to the extracted script file + * @throws IOException if I/O operations fail + * @throws IllegalArgumentException if scriptName is invalid + */ + public Path extractScript(String scriptName) throws IOException { + // Validate input + if (scriptName == null || scriptName.trim().isEmpty()) { + throw new IllegalArgumentException("scriptName must not be null or empty"); + } + if (scriptName.contains("..") || scriptName.contains("/")) { + throw new IllegalArgumentException( + "scriptName must not contain path traversal characters"); + } + + List validScripts = Arrays.asList("png_to_webp.py", "split_photos.py"); + if (!validScripts.contains(scriptName)) { + throw new IllegalArgumentException( + "scriptName must be either 'png_to_webp.py' or 'split_photos.py'"); + } + + // Load the resource from classpath + ClassPathResource resource = new ClassPathResource("static/python/" + scriptName); + + // Determine prefix and base directory for scripts + ApplicationProperties.TempFileManagement tempFiles = + applicationProperties.getSystem().getTempFileManagement(); + String baseDir = tempFiles.getBaseTmpDir(); + String prefix = tempFiles.getPrefix() + "-scripts"; + + // Create and register temp directory + Path scriptDir; + if (baseDir != null && !baseDir.isEmpty()) { + Path custom = Path.of(baseDir); + if (!Files.exists(custom)) { + Files.createDirectories(custom); + } + scriptDir = Files.createTempDirectory(custom, prefix); + } else { + scriptDir = Files.createTempDirectory(prefix); + } + registry.registerDirectory(scriptDir); + scriptDir.toFile().deleteOnExit(); + + // Copy script into the directory + Path target = scriptDir.resolve(scriptName); + try (InputStream in = resource.getInputStream()) { + Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING); + } + + log.debug("Extracted script '{}' to {}", scriptName, target); + return target; + } } diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java index 5eff72a4a..914315ef8 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java @@ -40,6 +40,7 @@ import stirling.software.common.util.GeneralUtils; import stirling.software.common.util.PdfUtils; import stirling.software.common.util.ProcessExecutor; import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult; +import stirling.software.common.util.TempFileManager; import stirling.software.common.util.WebResponseUtils; @RestController @@ -50,14 +51,15 @@ import stirling.software.common.util.WebResponseUtils; public class ConvertImgPDFController { private final CustomPDFDocumentFactory pdfDocumentFactory; + private final TempFileManager tempFileManager; @PostMapping(consumes = "multipart/form-data", value = "/pdf/img") @Operation( summary = "Convert PDF to image(s)", description = "This endpoint converts a PDF file to image(s) with the specified image format," - + " color type, and DPI. Users can choose to get a single image or multiple" - + " images. Input:PDF Output:Image Type:SI-Conditional") + + " color type, and DPI. Users can choose to get a single image or multiple" + + " images. Input:PDF Output:Image Type:SI-Conditional") public ResponseEntity convertToImage(@ModelAttribute ConvertToImageRequest request) throws Exception { MultipartFile file = request.getFileInput(); @@ -117,7 +119,7 @@ public class ConvertImgPDFController { } String pythonVersion = CheckProgramInstall.getAvailablePythonCommand(); - Path pngToWebpScript = GeneralUtils.extractScript("png_to_webp.py"); + Path pngToWebpScript = tempFileManager.extractScript("png_to_webp.py"); List command = new ArrayList<>(); command.add(pythonVersion); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java index 3992595ab..1cab0e9d6 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java @@ -34,9 +34,9 @@ import stirling.software.SPDF.model.api.misc.ExtractImageScansRequest; import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.util.CheckProgramInstall; import stirling.software.common.util.ExceptionUtils; -import stirling.software.common.util.GeneralUtils; import stirling.software.common.util.ProcessExecutor; import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult; +import stirling.software.common.util.TempFileManager; import stirling.software.common.util.WebResponseUtils; @RestController @@ -49,15 +49,16 @@ public class ExtractImageScansController { private static final String REPLACEFIRST = "[.][^.]+$"; private final CustomPDFDocumentFactory pdfDocumentFactory; + private final TempFileManager tempFileManager; @PostMapping(consumes = "multipart/form-data", value = "/extract-image-scans") @Operation( summary = "Extract image scans from an input file", description = "This endpoint extracts image scans from a given file based on certain" - + " parameters. Users can specify angle threshold, tolerance, minimum area," - + " minimum contour area, and border size. Input:PDF Output:IMAGE/ZIP" - + " Type:SIMO") + + " parameters. Users can specify angle threshold, tolerance, minimum area," + + " minimum contour area, and border size. Input:PDF Output:IMAGE/ZIP" + + " Type:SIMO") public ResponseEntity extractImageScans( @ModelAttribute ExtractImageScansRequest request) throws IOException, InterruptedException { @@ -79,7 +80,7 @@ public class ExtractImageScansController { } String pythonVersion = CheckProgramInstall.getAvailablePythonCommand(); - Path splitPhotosScript = GeneralUtils.extractScript("split_photos.py"); + Path splitPhotosScript = tempFileManager.extractScript("split_photos.py"); try { // Check if input file is a PDF if ("pdf".equalsIgnoreCase(extension)) {