2023-03-20 22:55:11 +01:00
|
|
|
package stirling.software.SPDF.utils;
|
|
|
|
|
2023-03-25 23:16:26 +01:00
|
|
|
import java.io.BufferedReader;
|
2023-07-22 17:57:40 +02:00
|
|
|
import java.io.File;
|
2023-03-20 22:55:11 +01:00
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStreamReader;
|
2024-01-10 01:33:07 +01:00
|
|
|
import java.io.InterruptedIOException;
|
2023-03-20 22:55:11 +01:00
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
import java.util.ArrayList;
|
2023-03-25 23:16:26 +01:00
|
|
|
import java.util.List;
|
2023-04-01 22:02:54 +02:00
|
|
|
import java.util.Map;
|
2023-04-16 23:03:30 +02:00
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
2023-04-01 22:02:54 +02:00
|
|
|
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;
|
2023-04-22 13:51:01 +02:00
|
|
|
|
2024-02-08 03:40:33 +01:00
|
|
|
import io.github.pixee.security.BoundedLineReader;
|
|
|
|
|
2023-03-20 22:55:11 +01:00
|
|
|
public class ProcessExecutor {
|
2023-04-22 13:51:01 +02:00
|
|
|
|
2024-01-10 01:33:07 +01:00
|
|
|
private static final Logger logger = LoggerFactory.getLogger(ProcessExecutor.class);
|
|
|
|
|
2023-04-22 13:51:01 +02:00
|
|
|
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
|
2023-04-01 22:02:54 +02:00
|
|
|
}
|
2023-03-20 22:55:11 +01:00
|
|
|
|
2023-04-22 13:51:01 +02:00
|
|
|
private static final Map<Processes, ProcessExecutor> instances = new ConcurrentHashMap<>();
|
2023-03-20 22:55:11 +01:00
|
|
|
|
2023-04-22 13:51:01 +02:00
|
|
|
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) {
|
2023-04-22 13:51:01 +02:00
|
|
|
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;
|
2023-04-22 13:51:01 +02:00
|
|
|
};
|
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);
|
2023-04-22 13:51:01 +02:00
|
|
|
});
|
|
|
|
}
|
2023-03-20 22:55:11 +01:00
|
|
|
|
2023-04-01 22:02:54 +02:00
|
|
|
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;
|
2023-03-20 22:55:11 +01:00
|
|
|
|
2024-01-10 01:33:07 +01:00
|
|
|
private ProcessExecutor(int semaphoreLimit, boolean liveUpdates, long timeout) {
|
2023-04-01 22:02:54 +02:00
|
|
|
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-04-01 22:02:54 +02:00
|
|
|
}
|
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;
|
2023-04-22 13:51:01 +02:00
|
|
|
semaphore.acquire();
|
|
|
|
try {
|
|
|
|
|
2024-01-10 01:33:07 +01:00
|
|
|
logger.info("Running command: " + String.join(" ", command));
|
2023-04-22 13:51:01 +02:00
|
|
|
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);
|
|
|
|
}
|
2023-04-22 13:51:01 +02:00
|
|
|
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;
|
2024-02-08 03:40:33 +01:00
|
|
|
while ((line =
|
|
|
|
BoundedLineReader.readLine(
|
|
|
|
errorReader, 5_000_000))
|
|
|
|
!= null) {
|
2023-04-22 13:51:01 +02:00
|
|
|
errorLines.add(line);
|
2024-01-10 01:33:07 +01:00
|
|
|
if (liveUpdates) logger.info(line);
|
2023-04-22 13:51:01 +02:00
|
|
|
}
|
2024-01-10 01:33:07 +01:00
|
|
|
} catch (InterruptedIOException e) {
|
|
|
|
logger.warn(
|
|
|
|
"Error reader thread was interrupted due to timeout.");
|
2023-04-22 13:51:01 +02:00
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
});
|
2023-12-30 20:11:27 +01:00
|
|
|
|
2023-04-22 13:51:01 +02:00
|
|
|
Thread outputReaderThread =
|
|
|
|
new Thread(
|
|
|
|
() -> {
|
|
|
|
try (BufferedReader outputReader =
|
|
|
|
new BufferedReader(
|
|
|
|
new InputStreamReader(
|
|
|
|
process.getInputStream(),
|
|
|
|
StandardCharsets.UTF_8))) {
|
|
|
|
String line;
|
2024-02-08 03:40:33 +01:00
|
|
|
while ((line =
|
|
|
|
BoundedLineReader.readLine(
|
|
|
|
outputReader, 5_000_000))
|
|
|
|
!= null) {
|
2023-04-22 13:51:01 +02:00
|
|
|
outputLines.add(line);
|
2024-01-10 01:33:07 +01:00
|
|
|
if (liveUpdates) logger.info(line);
|
2023-04-22 13:51:01 +02:00
|
|
|
}
|
2024-01-10 01:33:07 +01:00
|
|
|
} catch (InterruptedIOException e) {
|
|
|
|
logger.warn(
|
|
|
|
"Error reader thread was interrupted due to timeout.");
|
2023-04-22 13:51:01 +02:00
|
|
|
} 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();
|
2023-04-22 13:51:01 +02:00
|
|
|
// Wait for the reader threads to finish
|
|
|
|
errorReaderThread.join();
|
|
|
|
outputReaderThread.join();
|
2023-12-30 20:11:27 +01:00
|
|
|
|
2024-01-09 23:39:21 +01:00
|
|
|
if (!liveUpdates) {
|
|
|
|
if (outputLines.size() > 0) {
|
|
|
|
String outputMessage = String.join("\n", outputLines);
|
|
|
|
messages += outputMessage;
|
2024-01-10 01:33:07 +01:00
|
|
|
logger.info("Command output:\n" + outputMessage);
|
2024-01-09 23:39:21 +01:00
|
|
|
}
|
2023-04-22 13:51:01 +02:00
|
|
|
|
2024-01-09 23:39:21 +01:00
|
|
|
if (errorLines.size() > 0) {
|
|
|
|
String errorMessage = String.join("\n", errorLines);
|
|
|
|
messages += errorMessage;
|
2024-01-10 01:33:07 +01:00
|
|
|
logger.warn("Command error output:\n" + errorMessage);
|
2024-01-09 23:39:21 +01:00
|
|
|
if (exitCode != 0) {
|
|
|
|
throw new IOException(
|
|
|
|
"Command process failed with exit code "
|
|
|
|
+ exitCode
|
|
|
|
+ ". Error message: "
|
|
|
|
+ errorMessage);
|
|
|
|
}
|
2023-04-22 13:51:01 +02:00
|
|
|
}
|
2024-01-09 23:39:21 +01:00
|
|
|
} else if (exitCode != 0) {
|
|
|
|
throw new IOException("Command process failed with exit code " + exitCode);
|
2023-04-22 13:51:01 +02:00
|
|
|
}
|
|
|
|
} 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;
|
|
|
|
}
|
2023-04-01 22:02:54 +02:00
|
|
|
}
|
2023-03-20 22:55:11 +01:00
|
|
|
}
|