diff --git a/src/main/java/stirling/software/SPDF/utils/FileToPdf.java b/src/main/java/stirling/software/SPDF/utils/FileToPdf.java index ca06c5c2..ebdbf4fa 100644 --- a/src/main/java/stirling/software/SPDF/utils/FileToPdf.java +++ b/src/main/java/stirling/software/SPDF/utils/FileToPdf.java @@ -35,15 +35,17 @@ public class FileToPdf { command.add("weasyprint"); } else { command.add("wkhtmltopdf"); + command.add("--enable-local-file-access"); } + command.add(tempInputFile.toString()); command.add(tempOutputFile.toString()); ProcessExecutorResult returnCode; if (fileName.endsWith(".zip")) { if (htmlFormatsInstalled) { - command.add("--allow"); - command.add(tempOutputFile.getParent().toString()); + // command.add(1, "--allow"); + // command.add(2, tempInputFile.getParent().toString()); } returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT) diff --git a/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java b/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java index 18bc925a..23311bde 100644 --- a/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java +++ b/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java @@ -4,15 +4,22 @@ import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; +import java.io.InterruptedIOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ProcessExecutor { + private static final Logger logger = LoggerFactory.getLogger(ProcessExecutor.class); + public enum Processes { LIBRE_OFFICE, OCR_MY_PDF, @@ -26,7 +33,7 @@ public class ProcessExecutor { private static final Map instances = new ConcurrentHashMap<>(); public static ProcessExecutor getInstance(Processes processType) { - return getInstance(processType, false); + return getInstance(processType, true); } public static ProcessExecutor getInstance(Processes processType, boolean liveUpdates) { @@ -43,16 +50,29 @@ public class ProcessExecutor { case INSTALL_APP -> 1; case CALIBRE -> 1; }; - return new ProcessExecutor(semaphoreLimit, liveUpdates); + + long timeoutMinutes = + switch (key) { + case LIBRE_OFFICE -> 30; + case OCR_MY_PDF -> 30; + case PYTHON_OPENCV -> 30; + case GHOSTSCRIPT -> 5; + case WEASYPRINT -> 30; + case INSTALL_APP -> 60; + case CALIBRE -> 30; + }; + return new ProcessExecutor(semaphoreLimit, liveUpdates, timeoutMinutes); }); } private final Semaphore semaphore; private final boolean liveUpdates; + private long timeoutDuration; - private ProcessExecutor(int semaphoreLimit, boolean liveUpdates) { + private ProcessExecutor(int semaphoreLimit, boolean liveUpdates, long timeout) { this.semaphore = new Semaphore(semaphoreLimit); this.liveUpdates = liveUpdates; + this.timeoutDuration = timeout; } public ProcessExecutorResult runCommandWithOutputHandling(List command) @@ -62,12 +82,12 @@ public class ProcessExecutor { public ProcessExecutorResult runCommandWithOutputHandling( List command, File workingDirectory) throws IOException, InterruptedException { - int exitCode = 1; String messages = ""; + int exitCode = 1; semaphore.acquire(); try { - System.out.print("Running command: " + String.join(" ", command)); + logger.info("Running command: " + String.join(" ", command)); ProcessBuilder processBuilder = new ProcessBuilder(command); // Use the working directory if it's set @@ -91,8 +111,11 @@ public class ProcessExecutor { String line; while ((line = errorReader.readLine()) != null) { errorLines.add(line); - if (liveUpdates) System.out.println(line); + if (liveUpdates) logger.info(line); } + } catch (InterruptedIOException e) { + logger.warn( + "Error reader thread was interrupted due to timeout."); } catch (IOException e) { e.printStackTrace(); } @@ -109,8 +132,11 @@ public class ProcessExecutor { String line; while ((line = outputReader.readLine()) != null) { outputLines.add(line); - if (liveUpdates) System.out.println(line); + if (liveUpdates) logger.info(line); } + } catch (InterruptedIOException e) { + logger.warn( + "Error reader thread was interrupted due to timeout."); } catch (IOException e) { e.printStackTrace(); } @@ -120,8 +146,17 @@ public class ProcessExecutor { outputReaderThread.start(); // Wait for the conversion process to complete - exitCode = process.waitFor(); + boolean finished = process.waitFor(timeoutDuration, TimeUnit.MINUTES); + if (!finished) { + // Terminate the process + process.destroy(); + // Interrupt the reader threads + errorReaderThread.interrupt(); + outputReaderThread.interrupt(); + throw new IOException("Process timeout exceeded."); + } + exitCode = process.exitValue(); // Wait for the reader threads to finish errorReaderThread.join(); outputReaderThread.join(); @@ -130,13 +165,13 @@ public class ProcessExecutor { if (outputLines.size() > 0) { String outputMessage = String.join("\n", outputLines); messages += outputMessage; - System.out.println("Command output:\n" + outputMessage); + logger.info("Command output:\n" + outputMessage); } if (errorLines.size() > 0) { String errorMessage = String.join("\n", errorLines); messages += errorMessage; - System.out.println("Command error output:\n" + errorMessage); + logger.warn("Command error output:\n" + errorMessage); if (exitCode != 0) { throw new IOException( "Command process failed with exit code " diff --git a/src/main/resources/settings.yml.template b/src/main/resources/settings.yml.template index 1245c9e0..00c5998e 100644 --- a/src/main/resources/settings.yml.template +++ b/src/main/resources/settings.yml.template @@ -13,7 +13,9 @@ system: defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc) googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow enableAlphaFunctionality: false # Set to enable functionality which might need more testing before it fully goes live (This feature might make no changes) -# customExternalPort: 8000 used for when port mappings do not work correctly + customApplications: + installBookFormats: false # Installs Calibre for book format conversion (For non docker it must be manually downloaded but will need to be true to show in UI) + installAdvancedHtmlToPDF: false # DO NOT USE EXTERNALLY, NOT SAFE! Install wkHtmlToPDF (For non docker it must be manually downloaded but will need to be true to show in UI) #ui: # appName: exampleAppName # Application's visible name