Stirling-PDF/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

234 lines
8.9 KiB
Java
Raw Normal View History

package stirling.software.SPDF.utils;
import java.io.BufferedReader;
2023-07-22 17:57:40 +02:00
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
2024-01-10 01:33:07 +01:00
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;
2024-01-10 01:33:07 +01:00
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.github.pixee.security.BoundedLineReader;
public class ProcessExecutor {
2024-01-10 01:33:07 +01:00
private static final Logger logger = LoggerFactory.getLogger(ProcessExecutor.class);
public enum Processes {
2023-07-22 17:57:40 +02:00
LIBRE_OFFICE,
OCR_MY_PDF,
PYTHON_OPENCV,
GHOSTSCRIPT,
2024-01-09 23:39:21 +01:00
WEASYPRINT,
INSTALL_APP,
CALIBRE
}
private static final Map<Processes, ProcessExecutor> instances = new ConcurrentHashMap<>();
public static ProcessExecutor getInstance(Processes processType) {
2024-01-10 01:33:07 +01:00
return getInstance(processType, true);
2024-01-09 23:39:21 +01:00
}
public static ProcessExecutor getInstance(Processes processType, boolean liveUpdates) {
return instances.computeIfAbsent(
processType,
key -> {
int semaphoreLimit =
switch (key) {
case LIBRE_OFFICE -> 1;
case OCR_MY_PDF -> 2;
2023-05-01 22:57:48 +02:00
case PYTHON_OPENCV -> 8;
case GHOSTSCRIPT -> 16;
2023-07-22 17:57:40 +02:00
case WEASYPRINT -> 16;
2024-01-09 23:39:21 +01:00
case INSTALL_APP -> 1;
case CALIBRE -> 1;
};
2024-01-10 01:33:07 +01:00
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;
2024-01-09 23:39:21 +01:00
private final boolean liveUpdates;
2024-01-10 01:33:07 +01:00
private long timeoutDuration;
2024-01-10 01:33:07 +01:00
private ProcessExecutor(int semaphoreLimit, boolean liveUpdates, long timeout) {
this.semaphore = new Semaphore(semaphoreLimit);
2024-01-09 23:39:21 +01:00
this.liveUpdates = liveUpdates;
2024-01-10 01:33:07 +01:00
this.timeoutDuration = timeout;
}
2023-12-30 20:11:27 +01:00
2023-07-29 14:53:30 +02:00
public ProcessExecutorResult runCommandWithOutputHandling(List<String> command)
throws IOException, InterruptedException {
2023-07-22 17:57:40 +02:00
return runCommandWithOutputHandling(command, null);
}
2023-12-30 20:11:27 +01:00
2023-07-29 14:53:30 +02:00
public ProcessExecutorResult runCommandWithOutputHandling(
List<String> command, File workingDirectory) throws IOException, InterruptedException {
String messages = "";
2024-01-10 01:33:07 +01:00
int exitCode = 1;
semaphore.acquire();
try {
2024-01-10 01:33:07 +01:00
logger.info("Running command: " + String.join(" ", command));
ProcessBuilder processBuilder = new ProcessBuilder(command);
2023-12-30 20:11:27 +01:00
2023-07-22 17:57:40 +02:00
// Use the working directory if it's set
if (workingDirectory != null) {
processBuilder.directory(workingDirectory);
}
Process process = processBuilder.start();
// Read the error stream and standard output stream concurrently
List<String> errorLines = new ArrayList<>();
List<String> outputLines = new ArrayList<>();
Thread errorReaderThread =
new Thread(
() -> {
try (BufferedReader errorReader =
new BufferedReader(
new InputStreamReader(
process.getErrorStream(),
StandardCharsets.UTF_8))) {
String line;
while ((line =
BoundedLineReader.readLine(
errorReader, 5_000_000))
!= null) {
errorLines.add(line);
2024-01-10 01:33:07 +01:00
if (liveUpdates) logger.info(line);
}
2024-01-10 01:33:07 +01:00
} catch (InterruptedIOException e) {
logger.warn(
"Error reader thread was interrupted due to timeout.");
} catch (IOException e) {
e.printStackTrace();
}
});
2023-12-30 20:11:27 +01:00
Thread outputReaderThread =
new Thread(
() -> {
try (BufferedReader outputReader =
new BufferedReader(
new InputStreamReader(
process.getInputStream(),
StandardCharsets.UTF_8))) {
String line;
while ((line =
BoundedLineReader.readLine(
outputReader, 5_000_000))
!= null) {
outputLines.add(line);
2024-01-10 01:33:07 +01:00
if (liveUpdates) logger.info(line);
}
2024-01-10 01:33:07 +01:00
} catch (InterruptedIOException e) {
logger.warn(
"Error reader thread was interrupted due to timeout.");
} catch (IOException e) {
e.printStackTrace();
}
});
errorReaderThread.start();
outputReaderThread.start();
// Wait for the conversion process to complete
2024-01-10 01:33:07 +01:00
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();
2023-12-30 20:11:27 +01:00
2024-03-28 18:09:21 +01:00
if (outputLines.size() > 0) {
String outputMessage = String.join("\n", outputLines);
messages += outputMessage;
if (!liveUpdates) {
2024-01-10 01:33:07 +01:00
logger.info("Command output:\n" + outputMessage);
2024-01-09 23:39:21 +01:00
}
2024-03-28 18:09:21 +01:00
}
2024-03-28 18:09:21 +01:00
if (errorLines.size() > 0) {
String errorMessage = String.join("\n", errorLines);
messages += errorMessage;
if (!liveUpdates) {
2024-01-10 01:33:07 +01:00
logger.warn("Command error output:\n" + errorMessage);
}
2024-03-28 18:09:21 +01:00
if (exitCode != 0) {
throw new IOException(
"Command process failed with exit code "
+ exitCode
+ ". Error message: "
+ errorMessage);
}
}
if (exitCode != 0) {
throw new IOException(
"Command process failed with exit code "
+ exitCode
+ "\nLogs: "
+ messages);
}
} finally {
semaphore.release();
}
2023-07-29 14:53:30 +02:00
return new ProcessExecutorResult(exitCode, messages);
}
2023-12-30 20:11:27 +01:00
2023-07-29 14:53:30 +02:00
public class ProcessExecutorResult {
int rc;
String messages;
2023-12-30 20:11:27 +01:00
2023-07-29 14:53:30 +02:00
public ProcessExecutorResult(int rc, String messages) {
this.rc = rc;
this.messages = messages;
}
2023-12-30 20:11:27 +01:00
2023-07-29 14:53:30 +02:00
public int getRc() {
return rc;
}
2023-12-30 20:11:27 +01:00
2023-07-29 14:53:30 +02:00
public void setRc(int rc) {
this.rc = rc;
}
2023-12-30 20:11:27 +01:00
2023-07-29 14:53:30 +02:00
public String getMessages() {
return messages;
}
2023-12-30 20:11:27 +01:00
2023-07-29 14:53:30 +02:00
public void setMessages(String messages) {
this.messages = messages;
}
}
}