mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-08 17:51:20 +02:00
cleanups
This commit is contained in:
parent
3e5dc34bbc
commit
09a1f9b1a1
@ -2,7 +2,10 @@
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(chmod:*)",
|
||||
"Bash(mkdir:*)"
|
||||
"Bash(mkdir:*)",
|
||||
"Bash(./gradlew:*)",
|
||||
"Bash(grep:*)",
|
||||
"Bash(cat:*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
|
@ -8,22 +8,22 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
/**
|
||||
* Shortcut for a POST endpoint that is executed through the Stirling "auto‑job" framework.
|
||||
* <p>
|
||||
* Behaviour notes:
|
||||
* <ul>
|
||||
* <li>The endpoint is registered with {@code POST} and, by default, consumes
|
||||
* {@code multipart/form-data} unless you override {@link #consumes()}.</li>
|
||||
* <li>When the client supplies {@code ?async=true} the call is handed to
|
||||
* {@link stirling.software.common.service.JobExecutorService JobExecutorService} where it may
|
||||
* be queued, retried, tracked and subject to time‑outs. For synchronous (default)
|
||||
* invocations these advanced options are ignored.</li>
|
||||
* <li>Progress information (see {@link #trackProgress()}) is stored in
|
||||
* {@link stirling.software.common.service.TaskManager TaskManager} and can be
|
||||
* polled via <code>GET /api/v1/general/job/{id}</code>.</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* <p>Unless stated otherwise an attribute only affects <em>async</em> execution.</p>
|
||||
* <p>Behaviour notes:
|
||||
*
|
||||
* <ul>
|
||||
* <li>The endpoint is registered with {@code POST} and, by default, consumes {@code
|
||||
* multipart/form-data} unless you override {@link #consumes()}.
|
||||
* <li>When the client supplies {@code ?async=true} the call is handed to {@link
|
||||
* stirling.software.common.service.JobExecutorService JobExecutorService} where it may be
|
||||
* queued, retried, tracked and subject to time‑outs. For synchronous (default) invocations
|
||||
* these advanced options are ignored.
|
||||
* <li>Progress information (see {@link #trackProgress()}) is stored in {@link
|
||||
* stirling.software.common.service.TaskManager TaskManager} and can be polled via <code>
|
||||
* GET /api/v1/general/job/{id}</code>.
|
||||
* </ul>
|
||||
*
|
||||
* <p>Unless stated otherwise an attribute only affects <em>async</em> execution.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@ -31,42 +31,42 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||
@RequestMapping(method = RequestMethod.POST)
|
||||
public @interface AutoJobPostMapping {
|
||||
|
||||
/**
|
||||
* Alias for {@link RequestMapping#value} – the path mapping of the endpoint.
|
||||
*/
|
||||
/** Alias for {@link RequestMapping#value} – the path mapping of the endpoint. */
|
||||
@AliasFor(annotation = RequestMapping.class, attribute = "value")
|
||||
String[] value() default {};
|
||||
|
||||
/**
|
||||
* MIME types this endpoint accepts. Defaults to {@code multipart/form-data}.
|
||||
*/
|
||||
/** MIME types this endpoint accepts. Defaults to {@code multipart/form-data}. */
|
||||
@AliasFor(annotation = RequestMapping.class, attribute = "consumes")
|
||||
String[] consumes() default {"multipart/form-data"};
|
||||
|
||||
/**
|
||||
* Maximum execution time in milliseconds before the job is aborted.
|
||||
* A negative value means "use the application default".
|
||||
* <p>Only honoured when {@code async=true}.</p>
|
||||
* Maximum execution time in milliseconds before the job is aborted. A negative value means "use
|
||||
* the application default".
|
||||
*
|
||||
* <p>Only honoured when {@code async=true}.
|
||||
*/
|
||||
long timeout() default -1;
|
||||
|
||||
/**
|
||||
* Total number of attempts (initial + retries). Must be at least 1.
|
||||
* Retries are executed with exponential back‑off.
|
||||
* <p>Only honoured when {@code async=true}.</p>
|
||||
* Total number of attempts (initial + retries). Must be at least 1. Retries are executed
|
||||
* with exponential back‑off.
|
||||
*
|
||||
* <p>Only honoured when {@code async=true}.
|
||||
*/
|
||||
int retryCount() default 1;
|
||||
|
||||
/**
|
||||
* Record percentage / note updates so they can be retrieved via the REST status endpoint.
|
||||
* <p>Only honoured when {@code async=true}.</p>
|
||||
*
|
||||
* <p>Only honoured when {@code async=true}.
|
||||
*/
|
||||
boolean trackProgress() default true;
|
||||
|
||||
/**
|
||||
* If {@code true} the job may be placed in a queue instead of being rejected when resources
|
||||
* are scarce.
|
||||
* <p>Only honoured when {@code async=true}.</p>
|
||||
* If {@code true} the job may be placed in a queue instead of being rejected when resources are
|
||||
* scarce.
|
||||
*
|
||||
* <p>Only honoured when {@code async=true}.
|
||||
*/
|
||||
boolean queueable() default false;
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package stirling.software.common.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
@ -14,7 +13,6 @@ import jakarta.annotation.PostConstruct;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.common.configuration.InstallationPathConfig;
|
||||
import stirling.software.common.util.TempFileRegistry;
|
||||
|
||||
/**
|
||||
|
@ -173,7 +173,9 @@ public class ResourceMonitor {
|
||||
log.info("System resource status changed from {} to {}", oldStatus, newStatus);
|
||||
log.info(
|
||||
"Current metrics - CPU: {}%, Memory: {}%, Free Memory: {} MB",
|
||||
String.format("%.1f", cpuUsage * 100), String.format("%.1f", memoryUsage * 100), freeMemory / (1024 * 1024));
|
||||
String.format("%.1f", cpuUsage * 100),
|
||||
String.format("%.1f", memoryUsage * 100),
|
||||
freeMemory / (1024 * 1024));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error updating resource metrics: {}", e.getMessage(), e);
|
||||
|
@ -57,31 +57,34 @@ public class TempFileCleanupService {
|
||||
private static final int MAX_RECURSION_DEPTH = 5;
|
||||
|
||||
// File patterns that identify our temp files
|
||||
private static final Predicate<String> IS_OUR_TEMP_FILE = fileName ->
|
||||
fileName.startsWith("stirling-pdf-") ||
|
||||
fileName.startsWith("output_") ||
|
||||
fileName.startsWith("compressedPDF") ||
|
||||
fileName.startsWith("pdf-save-") ||
|
||||
fileName.startsWith("pdf-stream-") ||
|
||||
fileName.startsWith("PDFBox") ||
|
||||
fileName.startsWith("input_") ||
|
||||
fileName.startsWith("overlay-");
|
||||
private static final Predicate<String> IS_OUR_TEMP_FILE =
|
||||
fileName ->
|
||||
fileName.startsWith("stirling-pdf-")
|
||||
|| fileName.startsWith("output_")
|
||||
|| fileName.startsWith("compressedPDF")
|
||||
|| fileName.startsWith("pdf-save-")
|
||||
|| fileName.startsWith("pdf-stream-")
|
||||
|| fileName.startsWith("PDFBox")
|
||||
|| fileName.startsWith("input_")
|
||||
|| fileName.startsWith("overlay-");
|
||||
|
||||
// File patterns that identify common system temp files
|
||||
private static final Predicate<String> IS_SYSTEM_TEMP_FILE = fileName ->
|
||||
fileName.matches("lu\\d+[a-z0-9]*\\.tmp") ||
|
||||
fileName.matches("ocr_process\\d+") ||
|
||||
(fileName.startsWith("tmp") && !fileName.contains("jetty")) ||
|
||||
fileName.startsWith("OSL_PIPE_") ||
|
||||
(fileName.endsWith(".tmp") && !fileName.contains("jetty"));
|
||||
private static final Predicate<String> IS_SYSTEM_TEMP_FILE =
|
||||
fileName ->
|
||||
fileName.matches("lu\\d+[a-z0-9]*\\.tmp")
|
||||
|| fileName.matches("ocr_process\\d+")
|
||||
|| (fileName.startsWith("tmp") && !fileName.contains("jetty"))
|
||||
|| fileName.startsWith("OSL_PIPE_")
|
||||
|| (fileName.endsWith(".tmp") && !fileName.contains("jetty"));
|
||||
|
||||
// File patterns that should be excluded from cleanup
|
||||
private static final Predicate<String> SHOULD_SKIP = fileName ->
|
||||
fileName.contains("jetty") ||
|
||||
fileName.startsWith("jetty-") ||
|
||||
fileName.equals("proc") ||
|
||||
fileName.equals("sys") ||
|
||||
fileName.equals("dev");
|
||||
private static final Predicate<String> SHOULD_SKIP =
|
||||
fileName ->
|
||||
fileName.contains("jetty")
|
||||
|| fileName.startsWith("jetty-")
|
||||
|| fileName.equals("proc")
|
||||
|| fileName.equals("sys")
|
||||
|| fileName.equals("dev");
|
||||
|
||||
@Autowired
|
||||
public TempFileCleanupService(TempFileRegistry registry, TempFileManager tempFileManager) {
|
||||
@ -190,50 +193,58 @@ public class TempFileCleanupService {
|
||||
* @param maxAgeMillis Maximum age of files to clean in milliseconds
|
||||
* @return Number of files deleted
|
||||
*/
|
||||
private int cleanupUnregisteredFiles(boolean containerMode, boolean isScheduled, long maxAgeMillis) {
|
||||
private int cleanupUnregisteredFiles(
|
||||
boolean containerMode, boolean isScheduled, long maxAgeMillis) {
|
||||
AtomicInteger totalDeletedCount = new AtomicInteger(0);
|
||||
|
||||
try {
|
||||
// Get all directories we need to clean
|
||||
Path systemTempPath = getSystemTempPath();
|
||||
Path[] dirsToScan = {
|
||||
systemTempPath,
|
||||
Path.of(customTempDirectory),
|
||||
Path.of(libreOfficeTempDir)
|
||||
systemTempPath, Path.of(customTempDirectory), Path.of(libreOfficeTempDir)
|
||||
};
|
||||
|
||||
// Process each directory
|
||||
Arrays.stream(dirsToScan)
|
||||
.filter(Files::exists)
|
||||
.forEach(tempDir -> {
|
||||
try {
|
||||
String phase = isScheduled ? "scheduled" : "startup";
|
||||
log.info("Scanning directory for {} cleanup: {}", phase, tempDir);
|
||||
.filter(Files::exists)
|
||||
.forEach(
|
||||
tempDir -> {
|
||||
try {
|
||||
String phase = isScheduled ? "scheduled" : "startup";
|
||||
log.info(
|
||||
"Scanning directory for {} cleanup: {}",
|
||||
phase,
|
||||
tempDir);
|
||||
|
||||
AtomicInteger dirDeletedCount = new AtomicInteger(0);
|
||||
cleanupDirectoryStreaming(
|
||||
tempDir,
|
||||
containerMode,
|
||||
0,
|
||||
maxAgeMillis,
|
||||
isScheduled,
|
||||
path -> {
|
||||
dirDeletedCount.incrementAndGet();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Deleted temp file during {} cleanup: {}", phase, path);
|
||||
AtomicInteger dirDeletedCount = new AtomicInteger(0);
|
||||
cleanupDirectoryStreaming(
|
||||
tempDir,
|
||||
containerMode,
|
||||
0,
|
||||
maxAgeMillis,
|
||||
isScheduled,
|
||||
path -> {
|
||||
dirDeletedCount.incrementAndGet();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(
|
||||
"Deleted temp file during {} cleanup: {}",
|
||||
phase,
|
||||
path);
|
||||
}
|
||||
});
|
||||
|
||||
int count = dirDeletedCount.get();
|
||||
totalDeletedCount.addAndGet(count);
|
||||
if (count > 0) {
|
||||
log.info(
|
||||
"Cleaned up {} files/directories in {}",
|
||||
count,
|
||||
tempDir);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Error during cleanup of directory: {}", tempDir, e);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
int count = dirDeletedCount.get();
|
||||
totalDeletedCount.addAndGet(count);
|
||||
if (count > 0) {
|
||||
log.info("Cleaned up {} files/directories in {}", count, tempDir);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Error during cleanup of directory: {}", tempDir, e);
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("Error during cleanup of unregistered files", e);
|
||||
}
|
||||
@ -241,9 +252,7 @@ public class TempFileCleanupService {
|
||||
return totalDeletedCount.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the system temp directory path based on configuration or system property.
|
||||
*/
|
||||
/** Get the system temp directory path based on configuration or system property. */
|
||||
private Path getSystemTempPath() {
|
||||
if (systemTempDir != null && !systemTempDir.isEmpty()) {
|
||||
return Path.of(systemTempDir);
|
||||
@ -252,9 +261,7 @@ public class TempFileCleanupService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we're running in a container environment.
|
||||
*/
|
||||
/** Determine if we're running in a container environment. */
|
||||
private boolean isContainerMode() {
|
||||
return "Docker".equals(machineType) || "Kubernetes".equals(machineType);
|
||||
}
|
||||
@ -276,7 +283,8 @@ public class TempFileCleanupService {
|
||||
int depth,
|
||||
long maxAgeMillis,
|
||||
boolean isScheduled,
|
||||
Consumer<Path> onDeleteCallback) throws IOException {
|
||||
Consumer<Path> onDeleteCallback)
|
||||
throws IOException {
|
||||
|
||||
// Check recursion depth limit
|
||||
if (depth > MAX_RECURSION_DEPTH) {
|
||||
@ -287,59 +295,68 @@ public class TempFileCleanupService {
|
||||
// Use try-with-resources to ensure the stream is closed
|
||||
try (Stream<Path> pathStream = Files.list(directory)) {
|
||||
// Process files in a streaming fashion instead of materializing the whole list
|
||||
pathStream.forEach(path -> {
|
||||
try {
|
||||
String fileName = path.getFileName().toString();
|
||||
|
||||
// Skip if file should be excluded
|
||||
if (SHOULD_SKIP.test(fileName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle directories recursively
|
||||
if (Files.isDirectory(path)) {
|
||||
pathStream.forEach(
|
||||
path -> {
|
||||
try {
|
||||
cleanupDirectoryStreaming(
|
||||
path, containerMode, depth + 1, maxAgeMillis, isScheduled, onDeleteCallback);
|
||||
} catch (IOException e) {
|
||||
log.warn("Error processing subdirectory: {}", path, e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
String fileName = path.getFileName().toString();
|
||||
|
||||
// Skip registered files - these are handled by TempFileManager
|
||||
if (isScheduled && registry.contains(path.toFile())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this file should be deleted
|
||||
if (shouldDeleteFile(path, fileName, containerMode, maxAgeMillis)) {
|
||||
try {
|
||||
Files.deleteIfExists(path);
|
||||
onDeleteCallback.accept(path);
|
||||
} catch (IOException e) {
|
||||
// Handle locked files more gracefully
|
||||
if (e.getMessage() != null && e.getMessage().contains("being used by another process")) {
|
||||
log.debug("File locked, skipping delete: {}", path);
|
||||
} else {
|
||||
log.warn("Failed to delete temp file: {}", path, e);
|
||||
// Skip if file should be excluded
|
||||
if (SHOULD_SKIP.test(fileName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle directories recursively
|
||||
if (Files.isDirectory(path)) {
|
||||
try {
|
||||
cleanupDirectoryStreaming(
|
||||
path,
|
||||
containerMode,
|
||||
depth + 1,
|
||||
maxAgeMillis,
|
||||
isScheduled,
|
||||
onDeleteCallback);
|
||||
} catch (IOException e) {
|
||||
log.warn("Error processing subdirectory: {}", path, e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip registered files - these are handled by TempFileManager
|
||||
if (registry.contains(path.toFile())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this file should be deleted
|
||||
if (shouldDeleteFile(path, fileName, containerMode, maxAgeMillis)) {
|
||||
try {
|
||||
Files.deleteIfExists(path);
|
||||
onDeleteCallback.accept(path);
|
||||
} catch (IOException e) {
|
||||
// Handle locked files more gracefully
|
||||
if (e.getMessage() != null
|
||||
&& e.getMessage()
|
||||
.contains("being used by another process")) {
|
||||
log.debug("File locked, skipping delete: {}", path);
|
||||
} else {
|
||||
log.warn("Failed to delete temp file: {}", path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Error processing path: {}", path, e);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Error processing path: {}", path, e);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a file should be deleted based on its name, age, and other criteria.
|
||||
*/
|
||||
private boolean shouldDeleteFile(Path path, String fileName, boolean containerMode, long maxAgeMillis) {
|
||||
/** Determine if a file should be deleted based on its name, age, and other criteria. */
|
||||
private boolean shouldDeleteFile(
|
||||
Path path, String fileName, boolean containerMode, long maxAgeMillis) {
|
||||
// First check if it matches our known temp file patterns
|
||||
boolean isOurTempFile = IS_OUR_TEMP_FILE.test(fileName);
|
||||
boolean isSystemTempFile = IS_SYSTEM_TEMP_FILE.test(fileName);
|
||||
|
||||
// Normal operation - check against temp file patterns
|
||||
boolean shouldDelete = isOurTempFile || (containerMode && isSystemTempFile);
|
||||
|
||||
// Get file info for age checks
|
||||
@ -362,8 +379,10 @@ public class TempFileCleanupService {
|
||||
log.debug("Could not check file info, skipping: {}", path);
|
||||
}
|
||||
|
||||
// Check file age against maxAgeMillis only if it's not an empty file that we've already decided to delete
|
||||
// Check file age against maxAgeMillis only if it's not an empty file that we've already
|
||||
// decided to delete
|
||||
if (!isEmptyFile && shouldDelete && maxAgeMillis > 0) {
|
||||
// In normal mode, check age against maxAgeMillis
|
||||
shouldDelete = (currentTime - lastModified) > maxAgeMillis;
|
||||
}
|
||||
|
||||
@ -380,13 +399,12 @@ public class TempFileCleanupService {
|
||||
// For directories containing "libreoffice", delete all contents
|
||||
// but keep the directory itself for future use
|
||||
cleanupDirectoryStreaming(
|
||||
dir,
|
||||
isContainerMode(),
|
||||
0,
|
||||
0, // age doesn't matter for LibreOffice cleanup
|
||||
false,
|
||||
path -> log.debug("Cleaned up LibreOffice temp file: {}", path)
|
||||
);
|
||||
dir,
|
||||
isContainerMode(),
|
||||
0,
|
||||
0, // age doesn't matter for LibreOffice cleanup
|
||||
false,
|
||||
path -> log.debug("Cleaned up LibreOffice temp file: {}", path));
|
||||
log.debug("Cleaned up LibreOffice temp directory contents: {}", dir);
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,6 @@ public class TempFileManager {
|
||||
@Value("${stirling.tempfiles.max-age-hours:24}")
|
||||
private long maxAgeHours;
|
||||
|
||||
|
||||
@Autowired
|
||||
public TempFileManager(TempFileRegistry registry) {
|
||||
this.registry = registry;
|
||||
|
@ -10,19 +10,19 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
@ -67,7 +67,7 @@ public class TempFileCleanupServiceTest {
|
||||
ReflectionTestUtils.setField(cleanupService, "systemTempDir", systemTempDir.toString());
|
||||
ReflectionTestUtils.setField(cleanupService, "customTempDirectory", customTempDir.toString());
|
||||
ReflectionTestUtils.setField(cleanupService, "libreOfficeTempDir", libreOfficeTempDir.toString());
|
||||
ReflectionTestUtils.setField(cleanupService, "machineType", "Docker"); // Test in container mode
|
||||
ReflectionTestUtils.setField(cleanupService, "machineType", "Standard"); // Regular mode
|
||||
ReflectionTestUtils.setField(cleanupService, "performStartupCleanup", false); // Disable auto-startup cleanup
|
||||
|
||||
when(tempFileManager.getMaxAgeMillis()).thenReturn(3600000L); // 1 hour
|
||||
@ -98,6 +98,9 @@ public class TempFileCleanupServiceTest {
|
||||
Path ourTempFile4 = Files.createFile(customTempDir.resolve("pdf-save-123-456.tmp"));
|
||||
Path ourTempFile5 = Files.createFile(libreOfficeTempDir.resolve("input_file.pdf"));
|
||||
|
||||
// Old temporary files
|
||||
Path oldTempFile = Files.createFile(systemTempDir.resolve("output_old.pdf"));
|
||||
|
||||
// System temp files that should be cleaned in container mode
|
||||
Path sysTempFile1 = Files.createFile(systemTempDir.resolve("lu123abc.tmp"));
|
||||
Path sysTempFile2 = Files.createFile(customTempDir.resolve("ocr_process123"));
|
||||
@ -118,45 +121,217 @@ public class TempFileCleanupServiceTest {
|
||||
// Configure mock registry to say these files aren't registered
|
||||
when(registry.contains(any(File.class))).thenReturn(false);
|
||||
|
||||
// Create a file older than threshold
|
||||
Path oldFile = Files.createFile(systemTempDir.resolve("output_old.pdf"));
|
||||
Files.setLastModifiedTime(oldFile, FileTime.from(
|
||||
Files.getLastModifiedTime(oldFile).toMillis() - 5000000,
|
||||
TimeUnit.MILLISECONDS));
|
||||
// The set of files that will be deleted in our test
|
||||
Set<Path> deletedFiles = new HashSet<>();
|
||||
|
||||
// Act
|
||||
invokeCleanupDirectoryStreaming(systemTempDir, true, 0, 3600000);
|
||||
invokeCleanupDirectoryStreaming(customTempDir, true, 0, 3600000);
|
||||
invokeCleanupDirectoryStreaming(libreOfficeTempDir, true, 0, 3600000);
|
||||
// Use MockedStatic to mock Files operations
|
||||
try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
|
||||
// Mock Files.list for each directory we'll process
|
||||
mockedFiles.when(() -> Files.list(eq(systemTempDir)))
|
||||
.thenReturn(Stream.of(
|
||||
ourTempFile1, ourTempFile2, oldTempFile, sysTempFile1,
|
||||
jettyFile1, jettyFile2, regularFile, emptyFile, nestedDir));
|
||||
|
||||
// Assert - Our temp files and system temp files should be deleted (if old enough)
|
||||
assertFalse(Files.exists(oldFile), "Old temp file should be deleted");
|
||||
assertTrue(Files.exists(ourTempFile1), "Recent temp file should be preserved");
|
||||
assertTrue(Files.exists(sysTempFile1), "Recent system temp file should be preserved");
|
||||
mockedFiles.when(() -> Files.list(eq(customTempDir)))
|
||||
.thenReturn(Stream.of(ourTempFile3, ourTempFile4, sysTempFile2, sysTempFile3));
|
||||
|
||||
// Jetty files and regular files should never be deleted
|
||||
assertTrue(Files.exists(jettyFile1), "Jetty file should be preserved");
|
||||
assertTrue(Files.exists(jettyFile2), "File with jetty in name should be preserved");
|
||||
assertTrue(Files.exists(regularFile), "Regular file should be preserved");
|
||||
mockedFiles.when(() -> Files.list(eq(libreOfficeTempDir)))
|
||||
.thenReturn(Stream.of(ourTempFile5));
|
||||
|
||||
mockedFiles.when(() -> Files.list(eq(nestedDir)))
|
||||
.thenReturn(Stream.of(nestedTempFile));
|
||||
|
||||
// Configure Files.isDirectory for each path
|
||||
mockedFiles.when(() -> Files.isDirectory(eq(nestedDir))).thenReturn(true);
|
||||
mockedFiles.when(() -> Files.isDirectory(any(Path.class))).thenReturn(false);
|
||||
|
||||
// Configure Files.exists to return true for all paths
|
||||
mockedFiles.when(() -> Files.exists(any(Path.class))).thenReturn(true);
|
||||
|
||||
// Configure Files.getLastModifiedTime to return different times based on file names
|
||||
mockedFiles.when(() -> Files.getLastModifiedTime(any(Path.class)))
|
||||
.thenAnswer(invocation -> {
|
||||
Path path = invocation.getArgument(0);
|
||||
String fileName = path.getFileName().toString();
|
||||
|
||||
// For files with "old" in the name, return a timestamp older than maxAgeMillis
|
||||
if (fileName.contains("old")) {
|
||||
return FileTime.fromMillis(System.currentTimeMillis() - 5000000);
|
||||
}
|
||||
// For empty.tmp file, return a timestamp older than 5 minutes (for empty file test)
|
||||
else if (fileName.equals("empty.tmp")) {
|
||||
return FileTime.fromMillis(System.currentTimeMillis() - 6 * 60 * 1000);
|
||||
}
|
||||
// For all other files, return a recent timestamp
|
||||
else {
|
||||
return FileTime.fromMillis(System.currentTimeMillis() - 60000); // 1 minute ago
|
||||
}
|
||||
});
|
||||
|
||||
// Configure Files.size to return different sizes based on file names
|
||||
mockedFiles.when(() -> Files.size(any(Path.class)))
|
||||
.thenAnswer(invocation -> {
|
||||
Path path = invocation.getArgument(0);
|
||||
String fileName = path.getFileName().toString();
|
||||
|
||||
// Return 0 bytes for the empty file
|
||||
if (fileName.equals("empty.tmp")) {
|
||||
return 0L;
|
||||
}
|
||||
// Return normal size for all other files
|
||||
else {
|
||||
return 1024L; // 1 KB
|
||||
}
|
||||
});
|
||||
|
||||
// For deleteIfExists, track which files would be deleted
|
||||
mockedFiles.when(() -> Files.deleteIfExists(any(Path.class)))
|
||||
.thenAnswer(invocation -> {
|
||||
Path path = invocation.getArgument(0);
|
||||
deletedFiles.add(path);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Act - set containerMode to false for this test
|
||||
invokeCleanupDirectoryStreaming(systemTempDir, false, 0, 3600000);
|
||||
invokeCleanupDirectoryStreaming(customTempDir, false, 0, 3600000);
|
||||
invokeCleanupDirectoryStreaming(libreOfficeTempDir, false, 0, 3600000);
|
||||
|
||||
// Assert - Only old temp files and empty files should be deleted
|
||||
assertTrue(deletedFiles.contains(oldTempFile), "Old temp file should be deleted");
|
||||
assertTrue(deletedFiles.contains(emptyFile), "Empty file should be deleted");
|
||||
|
||||
// Regular temp files should not be deleted because they're too new
|
||||
assertFalse(deletedFiles.contains(ourTempFile1), "Recent temp file should be preserved");
|
||||
assertFalse(deletedFiles.contains(ourTempFile2), "Recent temp file should be preserved");
|
||||
assertFalse(deletedFiles.contains(ourTempFile3), "Recent temp file should be preserved");
|
||||
assertFalse(deletedFiles.contains(ourTempFile4), "Recent temp file should be preserved");
|
||||
assertFalse(deletedFiles.contains(ourTempFile5), "Recent temp file should be preserved");
|
||||
|
||||
// System temp files should not be deleted in non-container mode
|
||||
assertFalse(deletedFiles.contains(sysTempFile1), "System temp file should be preserved in non-container mode");
|
||||
assertFalse(deletedFiles.contains(sysTempFile2), "System temp file should be preserved in non-container mode");
|
||||
assertFalse(deletedFiles.contains(sysTempFile3), "System temp file should be preserved in non-container mode");
|
||||
|
||||
// Jetty files and regular files should never be deleted
|
||||
assertFalse(deletedFiles.contains(jettyFile1), "Jetty file should be preserved");
|
||||
assertFalse(deletedFiles.contains(jettyFile2), "File with jetty in name should be preserved");
|
||||
assertFalse(deletedFiles.contains(regularFile), "Regular file should be preserved");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContainerModeCleanup() throws IOException {
|
||||
// Arrange - Create various temp files
|
||||
Path ourTempFile = Files.createFile(systemTempDir.resolve("output_123.pdf"));
|
||||
Path sysTempFile = Files.createFile(systemTempDir.resolve("lu123abc.tmp"));
|
||||
Path regularFile = Files.createFile(systemTempDir.resolve("important.txt"));
|
||||
|
||||
// Configure mock registry to say these files aren't registered
|
||||
when(registry.contains(any(File.class))).thenReturn(false);
|
||||
|
||||
// The set of files that will be deleted in our test
|
||||
Set<Path> deletedFiles = new HashSet<>();
|
||||
|
||||
// Use MockedStatic to mock Files operations
|
||||
try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
|
||||
// Mock Files.list for systemTempDir
|
||||
mockedFiles.when(() -> Files.list(eq(systemTempDir)))
|
||||
.thenReturn(Stream.of(ourTempFile, sysTempFile, regularFile));
|
||||
|
||||
// Configure Files.isDirectory
|
||||
mockedFiles.when(() -> Files.isDirectory(any(Path.class))).thenReturn(false);
|
||||
|
||||
// Configure Files.exists
|
||||
mockedFiles.when(() -> Files.exists(any(Path.class))).thenReturn(true);
|
||||
|
||||
// Configure Files.getLastModifiedTime to return recent timestamps
|
||||
mockedFiles.when(() -> Files.getLastModifiedTime(any(Path.class)))
|
||||
.thenReturn(FileTime.fromMillis(System.currentTimeMillis() - 60000)); // 1 minute ago
|
||||
|
||||
// Configure Files.size to return normal size
|
||||
mockedFiles.when(() -> Files.size(any(Path.class)))
|
||||
.thenReturn(1024L); // 1 KB
|
||||
|
||||
// For deleteIfExists, track which files would be deleted
|
||||
mockedFiles.when(() -> Files.deleteIfExists(any(Path.class)))
|
||||
.thenAnswer(invocation -> {
|
||||
Path path = invocation.getArgument(0);
|
||||
deletedFiles.add(path);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Act - set containerMode to true and maxAgeMillis to 0 for container startup cleanup
|
||||
invokeCleanupDirectoryStreaming(systemTempDir, true, 0, 0);
|
||||
|
||||
// Assert - In container mode, both our temp files and system temp files should be deleted
|
||||
// regardless of age (when maxAgeMillis is 0)
|
||||
assertTrue(deletedFiles.contains(ourTempFile), "Our temp file should be deleted in container mode");
|
||||
assertTrue(deletedFiles.contains(sysTempFile), "System temp file should be deleted in container mode");
|
||||
assertFalse(deletedFiles.contains(regularFile), "Regular file should be preserved");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyFileHandling() throws IOException {
|
||||
// Arrange - Create an empty file
|
||||
Path emptyFile = Files.createFile(systemTempDir.resolve("empty.tmp"));
|
||||
// Make it "old enough" to be deleted (>5 minutes)
|
||||
Files.setLastModifiedTime(emptyFile, FileTime.from(
|
||||
Files.getLastModifiedTime(emptyFile).toMillis() - 6 * 60 * 1000,
|
||||
TimeUnit.MILLISECONDS));
|
||||
Path recentEmptyFile = Files.createFile(systemTempDir.resolve("recent_empty.tmp"));
|
||||
|
||||
// Configure mock registry to say this file isn't registered
|
||||
// Configure mock registry to say these files aren't registered
|
||||
when(registry.contains(any(File.class))).thenReturn(false);
|
||||
|
||||
// Act
|
||||
invokeCleanupDirectoryStreaming(systemTempDir, true, 0, 3600000);
|
||||
// The set of files that will be deleted in our test
|
||||
Set<Path> deletedFiles = new HashSet<>();
|
||||
|
||||
// Assert
|
||||
assertFalse(Files.exists(emptyFile), "Empty file older than 5 minutes should be deleted");
|
||||
// Use MockedStatic to mock Files operations
|
||||
try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
|
||||
// Mock Files.list for systemTempDir
|
||||
mockedFiles.when(() -> Files.list(eq(systemTempDir)))
|
||||
.thenReturn(Stream.of(emptyFile, recentEmptyFile));
|
||||
|
||||
// Configure Files.isDirectory
|
||||
mockedFiles.when(() -> Files.isDirectory(any(Path.class))).thenReturn(false);
|
||||
|
||||
// Configure Files.exists
|
||||
mockedFiles.when(() -> Files.exists(any(Path.class))).thenReturn(true);
|
||||
|
||||
// Configure Files.getLastModifiedTime to return different times based on file names
|
||||
mockedFiles.when(() -> Files.getLastModifiedTime(any(Path.class)))
|
||||
.thenAnswer(invocation -> {
|
||||
Path path = invocation.getArgument(0);
|
||||
String fileName = path.getFileName().toString();
|
||||
|
||||
if (fileName.equals("empty.tmp")) {
|
||||
// More than 5 minutes old
|
||||
return FileTime.fromMillis(System.currentTimeMillis() - 6 * 60 * 1000);
|
||||
} else {
|
||||
// Less than 5 minutes old
|
||||
return FileTime.fromMillis(System.currentTimeMillis() - 2 * 60 * 1000);
|
||||
}
|
||||
});
|
||||
|
||||
// Configure Files.size to return 0 for empty files
|
||||
mockedFiles.when(() -> Files.size(any(Path.class)))
|
||||
.thenReturn(0L);
|
||||
|
||||
// For deleteIfExists, track which files would be deleted
|
||||
mockedFiles.when(() -> Files.deleteIfExists(any(Path.class)))
|
||||
.thenAnswer(invocation -> {
|
||||
Path path = invocation.getArgument(0);
|
||||
deletedFiles.add(path);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Act
|
||||
invokeCleanupDirectoryStreaming(systemTempDir, false, 0, 3600000);
|
||||
|
||||
// Assert
|
||||
assertTrue(deletedFiles.contains(emptyFile),
|
||||
"Empty file older than 5 minutes should be deleted");
|
||||
assertFalse(deletedFiles.contains(recentEmptyFile),
|
||||
"Empty file newer than 5 minutes should not be deleted");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -168,23 +343,79 @@ public class TempFileCleanupServiceTest {
|
||||
|
||||
Path tempFile1 = Files.createFile(dir1.resolve("output_1.pdf"));
|
||||
Path tempFile2 = Files.createFile(dir2.resolve("output_2.pdf"));
|
||||
Path tempFile3 = Files.createFile(dir3.resolve("output_3.pdf"));
|
||||
|
||||
// Make the deepest file old enough to be deleted
|
||||
Files.setLastModifiedTime(tempFile3, FileTime.from(
|
||||
Files.getLastModifiedTime(tempFile3).toMillis() - 5000000,
|
||||
TimeUnit.MILLISECONDS));
|
||||
Path tempFile3 = Files.createFile(dir3.resolve("output_old_3.pdf"));
|
||||
|
||||
// Configure mock registry to say these files aren't registered
|
||||
when(registry.contains(any(File.class))).thenReturn(false);
|
||||
|
||||
// Act
|
||||
invokeCleanupDirectoryStreaming(systemTempDir, true, 0, 3600000);
|
||||
// The set of files that will be deleted in our test
|
||||
Set<Path> deletedFiles = new HashSet<>();
|
||||
|
||||
// Assert
|
||||
assertTrue(Files.exists(tempFile1), "Recent temp file should be preserved");
|
||||
assertTrue(Files.exists(tempFile2), "Recent temp file should be preserved");
|
||||
assertFalse(Files.exists(tempFile3), "Old temp file in nested directory should be deleted");
|
||||
// Use MockedStatic to mock Files operations
|
||||
try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
|
||||
// Mock Files.list for each directory
|
||||
mockedFiles.when(() -> Files.list(eq(systemTempDir)))
|
||||
.thenReturn(Stream.of(dir1));
|
||||
|
||||
mockedFiles.when(() -> Files.list(eq(dir1)))
|
||||
.thenReturn(Stream.of(tempFile1, dir2));
|
||||
|
||||
mockedFiles.when(() -> Files.list(eq(dir2)))
|
||||
.thenReturn(Stream.of(tempFile2, dir3));
|
||||
|
||||
mockedFiles.when(() -> Files.list(eq(dir3)))
|
||||
.thenReturn(Stream.of(tempFile3));
|
||||
|
||||
// Configure Files.isDirectory for each path
|
||||
mockedFiles.when(() -> Files.isDirectory(eq(dir1))).thenReturn(true);
|
||||
mockedFiles.when(() -> Files.isDirectory(eq(dir2))).thenReturn(true);
|
||||
mockedFiles.when(() -> Files.isDirectory(eq(dir3))).thenReturn(true);
|
||||
mockedFiles.when(() -> Files.isDirectory(eq(tempFile1))).thenReturn(false);
|
||||
mockedFiles.when(() -> Files.isDirectory(eq(tempFile2))).thenReturn(false);
|
||||
mockedFiles.when(() -> Files.isDirectory(eq(tempFile3))).thenReturn(false);
|
||||
|
||||
// Configure Files.exists to return true for all paths
|
||||
mockedFiles.when(() -> Files.exists(any(Path.class))).thenReturn(true);
|
||||
|
||||
// Configure Files.getLastModifiedTime to return different times based on file names
|
||||
mockedFiles.when(() -> Files.getLastModifiedTime(any(Path.class)))
|
||||
.thenAnswer(invocation -> {
|
||||
Path path = invocation.getArgument(0);
|
||||
String fileName = path.getFileName().toString();
|
||||
|
||||
if (fileName.contains("old")) {
|
||||
// Old file
|
||||
return FileTime.fromMillis(System.currentTimeMillis() - 5000000);
|
||||
} else {
|
||||
// Recent file
|
||||
return FileTime.fromMillis(System.currentTimeMillis() - 60000);
|
||||
}
|
||||
});
|
||||
|
||||
// Configure Files.size to return normal size
|
||||
mockedFiles.when(() -> Files.size(any(Path.class)))
|
||||
.thenReturn(1024L);
|
||||
|
||||
// For deleteIfExists, track which files would be deleted
|
||||
mockedFiles.when(() -> Files.deleteIfExists(any(Path.class)))
|
||||
.thenAnswer(invocation -> {
|
||||
Path path = invocation.getArgument(0);
|
||||
deletedFiles.add(path);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Act
|
||||
invokeCleanupDirectoryStreaming(systemTempDir, false, 0, 3600000);
|
||||
|
||||
// Debug - print what was deleted
|
||||
System.out.println("Deleted files: " + deletedFiles);
|
||||
System.out.println("Looking for: " + tempFile3);
|
||||
|
||||
// Assert
|
||||
assertFalse(deletedFiles.contains(tempFile1), "Recent temp file should be preserved");
|
||||
assertFalse(deletedFiles.contains(tempFile2), "Recent temp file should be preserved");
|
||||
assertTrue(deletedFiles.contains(tempFile3), "Old temp file in nested directory should be deleted");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -197,7 +428,7 @@ public class TempFileCleanupServiceTest {
|
||||
AtomicInteger deleteCount = new AtomicInteger(0);
|
||||
Consumer<Path> deleteCallback = path -> deleteCount.incrementAndGet();
|
||||
|
||||
// Get the new method with updated signature
|
||||
// Get the method with updated signature
|
||||
var method = TempFileCleanupService.class.getDeclaredMethod(
|
||||
"cleanupDirectoryStreaming",
|
||||
Path.class, boolean.class, int.class, long.class, boolean.class, Consumer.class);
|
||||
@ -209,4 +440,9 @@ public class TempFileCleanupServiceTest {
|
||||
throw new RuntimeException("Error invoking cleanupDirectoryStreaming", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Matcher for exact path equality
|
||||
private static Path eq(Path path) {
|
||||
return argThat(arg -> arg != null && arg.equals(path));
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
|
@ -11,7 +11,9 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import io.github.pixee.security.SystemCommand;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.common.service.TempFileCleanupService;
|
||||
import stirling.software.common.util.ApplicationContextProvider;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
@ -143,9 +145,7 @@ public class UnoconvServer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify that unoconv is being used, to reset the inactivity timer.
|
||||
*/
|
||||
/** Notify that unoconv is being used, to reset the inactivity timer. */
|
||||
public void notifyActivity() {
|
||||
lastActivityTime = System.currentTimeMillis();
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.common.model.ApplicationProperties;
|
||||
|
||||
@Service
|
||||
|
@ -35,8 +35,6 @@ import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.ProcessExecutor;
|
||||
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.TempFileUtil;
|
||||
import stirling.software.common.util.TempFileUtil.TempFile;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/misc")
|
||||
@ -112,18 +110,21 @@ public class OCRController {
|
||||
hasText = !stripper.getText(tempDoc).trim().isEmpty();
|
||||
}
|
||||
|
||||
boolean shouldOcr = switch (ocrType) {
|
||||
case "skip-text" -> !hasText;
|
||||
case "force-ocr" -> true;
|
||||
default -> true;
|
||||
};
|
||||
boolean shouldOcr =
|
||||
switch (ocrType) {
|
||||
case "skip-text" -> !hasText;
|
||||
case "force-ocr" -> true;
|
||||
default -> true;
|
||||
};
|
||||
|
||||
File pageOutputPath = new File(tempOutputDir, String.format("page_%d.pdf", pageNum));
|
||||
File pageOutputPath =
|
||||
new File(tempOutputDir, String.format("page_%d.pdf", pageNum));
|
||||
|
||||
if (shouldOcr) {
|
||||
// Convert page to image
|
||||
BufferedImage image = pdfRenderer.renderImageWithDPI(pageNum, 300);
|
||||
File imagePath = new File(tempImagesDir, String.format("page_%d.png", pageNum));
|
||||
File imagePath =
|
||||
new File(tempImagesDir, String.format("page_%d.png", pageNum));
|
||||
ImageIO.write(image, "png", imagePath);
|
||||
|
||||
// Build OCR command
|
||||
@ -140,16 +141,22 @@ public class OCRController {
|
||||
|
||||
// Use ProcessExecutor to run tesseract command
|
||||
try {
|
||||
ProcessExecutorResult result = ProcessExecutor.getInstance(ProcessExecutor.Processes.TESSERACT)
|
||||
.runCommandWithOutputHandling(command);
|
||||
ProcessExecutorResult result =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.TESSERACT)
|
||||
.runCommandWithOutputHandling(command);
|
||||
|
||||
log.debug("Tesseract OCR completed for page {} with exit code {}",
|
||||
pageNum, result.getRc());
|
||||
log.debug(
|
||||
"Tesseract OCR completed for page {} with exit code {}",
|
||||
pageNum,
|
||||
result.getRc());
|
||||
|
||||
// Add OCR'd PDF to merger
|
||||
merger.addSource(pageOutputPath);
|
||||
} catch (IOException | InterruptedException e) {
|
||||
log.error("Error processing page {} with tesseract: {}", pageNum, e.getMessage());
|
||||
log.error(
|
||||
"Error processing page {} with tesseract: {}",
|
||||
pageNum,
|
||||
e.getMessage());
|
||||
// If OCR fails, fall back to the original page
|
||||
try (PDDocument pageDoc = new PDDocument()) {
|
||||
pageDoc.addPage(page);
|
||||
|
@ -1,8 +1,6 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import java.io.IOException;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.TempFileUtil;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ -23,6 +21,8 @@ import stirling.software.common.model.api.PDFFile;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.ProcessExecutor;
|
||||
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.TempFileUtil;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
|
@ -6,8 +6,6 @@ import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.TempFileUtil;
|
||||
import java.util.List;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
@ -41,6 +39,8 @@ import lombok.RequiredArgsConstructor;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.AddStampRequest;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.TempFileUtil;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@ -52,8 +52,6 @@ public class StampController {
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/add-stamp")
|
||||
@Operation(
|
||||
summary = "Add stamp to a PDF file",
|
||||
@ -194,7 +192,8 @@ public class StampController {
|
||||
String fileExtension = resourceDir.substring(resourceDir.lastIndexOf("."));
|
||||
|
||||
// Use TempFileUtil.TempFile with try-with-resources for automatic cleanup
|
||||
try (TempFileUtil.TempFile tempFileWrapper = new TempFileUtil.TempFile(tempFileManager, fileExtension)) {
|
||||
try (TempFileUtil.TempFile tempFileWrapper =
|
||||
new TempFileUtil.TempFile(tempFileManager, fileExtension)) {
|
||||
File tempFile = tempFileWrapper.getFile();
|
||||
try (InputStream is = classPathResource.getInputStream();
|
||||
FileOutputStream os = new FileOutputStream(tempFile)) {
|
||||
|
Loading…
Reference in New Issue
Block a user