Dynamic paths for tools and removal of unused book endpoints (#3018)

# Description of Changes

This pull request includes several changes primarily focused on
improving configuration management, removing deprecated methods, and
updating paths for external dependencies. The most important changes are
summarized below:

### Configuration Management Improvements:
* Added a new `RuntimePathConfig` class to manage dynamic paths for
operations and pipeline configurations
(`src/main/java/stirling/software/SPDF/config/RuntimePathConfig.java`).
* Removed the `bookAndHtmlFormatsInstalled` bean and its associated
logic from `AppConfig` and `EndpointConfiguration`
(`src/main/java/stirling/software/SPDF/config/AppConfig.java`,
`src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java`).
[[1]](diffhunk://#diff-4d774ec79aa55750c0a4739bee971b68877078b73654e863fd40ee924347e143L130-L138)
[[2]](diffhunk://#diff-750f31f6ecbd64b025567108a33775cad339e835a04360affff82a09410b697dL12-L35)
[[3]](diffhunk://#diff-750f31f6ecbd64b025567108a33775cad339e835a04360affff82a09410b697dL275-L280)

### External Dependency Path Updates:
* Updated paths for `weasyprint` and `unoconvert` in
`ExternalAppDepConfig` to use values from `RuntimePathConfig`
(`src/main/java/stirling/software/SPDF/config/ExternalAppDepConfig.java`).
[[1]](diffhunk://#diff-c47af298c07c2622aa98b038b78822c56bdb002de71081e102d344794e7832a6R12-L33)
[[2]](diffhunk://#diff-c47af298c07c2622aa98b038b78822c56bdb002de71081e102d344794e7832a6L104-R115)


### Minor Adjustments:
* Corrected a typo from "Unoconv" to "Unoconvert" in
`EndpointConfiguration`
(`src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java`).

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing)
for more details.
This commit is contained in:
Anthony Stirling
2025-02-23 13:36:21 +00:00
committed by GitHub
parent 1e29cf43fb
commit f5ca02df1d
136 changed files with 465 additions and 349 deletions

View File

@@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Hidden;
import jakarta.servlet.http.HttpServletResponse;
import stirling.software.SPDF.service.LanguageService;
@RestController

View File

@@ -25,6 +25,7 @@ import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.database.DatabaseService;
@Slf4j

View File

@@ -32,6 +32,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.general.MergePdfsRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils;

View File

@@ -21,6 +21,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.SortTypes;
import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.SPDF.model.api.general.RearrangePagesRequest;

View File

@@ -27,6 +27,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;

View File

@@ -31,6 +31,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.PdfMetadata;
import stirling.software.SPDF.model.api.SplitPdfByChaptersRequest;
import stirling.software.SPDF.service.PdfMetadataService;

View File

@@ -24,6 +24,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.general.SplitPdfBySizeOrCountRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils;

View File

@@ -26,7 +26,9 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;

View File

@@ -1,77 +0,0 @@
package stirling.software.SPDF.controller.api.converters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import stirling.software.SPDF.model.api.GeneralFile;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.FileToPdf;
import stirling.software.SPDF.utils.WebResponseUtils;
// @RestController
// @Tag(name = "Convert", description = "Convert APIs")
// @RequestMapping("/api/v1/convert")
public class ConvertBookToPDFController {
private final boolean bookAndHtmlFormatsInstalled;
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
public ConvertBookToPDFController(
CustomPDDocumentFactory pdfDocumentFactory,
@Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled) {
this.pdfDocumentFactory = pdfDocumentFactory;
this.bookAndHtmlFormatsInstalled = bookAndHtmlFormatsInstalled;
}
@PostMapping(consumes = "multipart/form-data", value = "/book/pdf")
@Operation(
summary =
"Convert a BOOK/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx) to PDF",
description =
"(Requires bookAndHtmlFormatsInstalled flag and Calibre installed) This endpoint takes an BOOK/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx) input and converts it to PDF format.")
public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute GeneralFile request) throws Exception {
MultipartFile fileInput = request.getFileInput();
if (!bookAndHtmlFormatsInstalled) {
throw new IllegalArgumentException(
"bookAndHtmlFormatsInstalled flag is False, this functionality is not available");
}
if (fileInput == null) {
throw new IllegalArgumentException("Please provide a file for conversion.");
}
String originalFilename = Filenames.toSimpleFileName(fileInput.getOriginalFilename());
if (originalFilename != null) {
String originalFilenameLower = originalFilename.toLowerCase();
if (!originalFilenameLower.endsWith(".epub")
&& !originalFilenameLower.endsWith(".mobi")
&& !originalFilenameLower.endsWith(".azw3")
&& !originalFilenameLower.endsWith(".fb2")
&& !originalFilenameLower.endsWith(".txt")
&& !originalFilenameLower.endsWith(".docx")) {
throw new IllegalArgumentException(
"File must be in .epub, .mobi, .azw3, .fb2, .txt, or .docx format.");
}
}
byte[] pdfBytes = FileToPdf.convertBookTypeToPdf(fileInput.getBytes(), originalFilename);
pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes);
String outputFilename =
originalFilename.replaceFirst("[.][^.]+$", "")
+ ".pdf"; // Remove file extension and append .pdf
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
}
}

View File

@@ -1,7 +1,6 @@
package stirling.software.SPDF.controller.api.converters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
@@ -13,6 +12,7 @@ import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.config.RuntimePathConfig;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.api.converters.HTMLToPdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
@@ -24,20 +24,21 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RequestMapping("/api/v1/convert")
public class ConvertHtmlToPDF {
private final boolean bookAndHtmlFormatsInstalled;
private final CustomPDDocumentFactory pdfDocumentFactory;
private final ApplicationProperties applicationProperties;
private final RuntimePathConfig runtimePathConfig;
@Autowired
public ConvertHtmlToPDF(
CustomPDDocumentFactory pdfDocumentFactory,
@Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled,
ApplicationProperties applicationProperties) {
ApplicationProperties applicationProperties,
RuntimePathConfig runtimePathConfig) {
this.pdfDocumentFactory = pdfDocumentFactory;
this.bookAndHtmlFormatsInstalled = bookAndHtmlFormatsInstalled;
this.applicationProperties = applicationProperties;
this.runtimePathConfig = runtimePathConfig;
}
@PostMapping(consumes = "multipart/form-data", value = "/html/pdf")
@@ -65,10 +66,10 @@ public class ConvertHtmlToPDF {
byte[] pdfBytes =
FileToPdf.convertHtmlToPdf(
runtimePathConfig.getWeasyPrintPath(),
request,
fileInput.getBytes(),
originalFilename,
bookAndHtmlFormatsInstalled,
disableSanitize);
pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes);

View File

@@ -31,6 +31,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.converters.ConvertToImageRequest;
import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;

View File

@@ -11,7 +11,6 @@ import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.AttributeProvider;
import org.commonmark.renderer.html.HtmlRenderer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
@@ -23,6 +22,7 @@ import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.config.RuntimePathConfig;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.api.GeneralFile;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
@@ -34,20 +34,20 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RequestMapping("/api/v1/convert")
public class ConvertMarkdownToPdf {
private final boolean bookAndHtmlFormatsInstalled;
private final CustomPDDocumentFactory pdfDocumentFactory;
private final ApplicationProperties applicationProperties;
private final RuntimePathConfig runtimePathConfig;
@Autowired
public ConvertMarkdownToPdf(
CustomPDDocumentFactory pdfDocumentFactory,
@Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled,
ApplicationProperties applicationProperties) {
ApplicationProperties applicationProperties,
RuntimePathConfig runtimePathConfig) {
this.pdfDocumentFactory = pdfDocumentFactory;
this.bookAndHtmlFormatsInstalled = bookAndHtmlFormatsInstalled;
this.applicationProperties = applicationProperties;
this.runtimePathConfig = runtimePathConfig;
}
@PostMapping(consumes = "multipart/form-data", value = "/markdown/pdf")
@@ -86,10 +86,10 @@ public class ConvertMarkdownToPdf {
byte[] pdfBytes =
FileToPdf.convertHtmlToPdf(
runtimePathConfig.getWeasyPrintPath(),
null,
htmlContent.getBytes(),
"converted.html",
bookAndHtmlFormatsInstalled,
disableSanitize);
pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes);
String outputFilename =

View File

@@ -22,6 +22,7 @@ import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.config.RuntimePathConfig;
import stirling.software.SPDF.model.api.GeneralFile;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.ProcessExecutor;
@@ -34,10 +35,13 @@ import stirling.software.SPDF.utils.WebResponseUtils;
public class ConvertOfficeController {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final RuntimePathConfig runtimePathConfig;
@Autowired
public ConvertOfficeController(CustomPDDocumentFactory pdfDocumentFactory) {
public ConvertOfficeController(
CustomPDDocumentFactory pdfDocumentFactory, RuntimePathConfig runtimePathConfig) {
this.pdfDocumentFactory = pdfDocumentFactory;
this.runtimePathConfig = runtimePathConfig;
}
public File convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
@@ -61,7 +65,7 @@ public class ConvertOfficeController {
List<String> command =
new ArrayList<>(
Arrays.asList(
"/opt/venv/bin/unoconvert",
runtimePathConfig.getUnoConvertPath(),
"--port",
"2003",
"--convert-to",

View File

@@ -1,95 +0,0 @@
package stirling.software.SPDF.controller.api.converters;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import stirling.software.SPDF.model.api.converters.PdfToBookRequest;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
import stirling.software.SPDF.utils.WebResponseUtils;
// @RestController
// @Tag(name = "Convert", description = "Convert APIs")
// @RequestMapping("/api/v1/convert")
public class ConvertPDFToBookController {
@Qualifier("bookAndHtmlFormatsInstalled")
private final boolean bookAndHtmlFormatsInstalled;
public ConvertPDFToBookController(
@Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled) {
this.bookAndHtmlFormatsInstalled = bookAndHtmlFormatsInstalled;
}
@PostMapping(consumes = "multipart/form-data", value = "/pdf/book")
@Operation(
summary =
"Convert a PDF to a Book/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx .. (others to include by chatgpt) to PDF",
description =
"(Requires bookAndHtmlFormatsInstalled flag and Calibre installed) This endpoint Convert a PDF to a Book/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx .. (others to include by chatgpt) to PDF")
public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute PdfToBookRequest request)
throws Exception {
MultipartFile fileInput = request.getFileInput();
if (!bookAndHtmlFormatsInstalled) {
throw new IllegalArgumentException(
"bookAndHtmlFormatsInstalled flag is False, this functionality is not available");
}
if (fileInput == null) {
throw new IllegalArgumentException("Please provide a file for conversion.");
}
// Validate the output format
String outputFormat = request.getOutputFormat().toLowerCase();
List<String> allowedFormats =
Arrays.asList(
"epub", "mobi", "azw3", "docx", "rtf", "txt", "html", "lit", "fb2", "pdb",
"lrf");
if (!allowedFormats.contains(outputFormat)) {
throw new IllegalArgumentException("Invalid output format: " + outputFormat);
}
byte[] outputFileBytes;
List<String> command = new ArrayList<>();
Path tempOutputFile =
Files.createTempFile(
"output_", // Use the output format for the file extension
"." + outputFormat);
Path tempInputFile = null;
try {
// Create temp input file from the provided PDF
// Assuming input is always PDF
tempInputFile = Files.createTempFile("input_", ".pdf");
Files.write(tempInputFile, fileInput.getBytes());
command.add("ebook-convert");
command.add(tempInputFile.toString());
command.add(tempOutputFile.toString());
ProcessExecutorResult returnCode =
ProcessExecutor.getInstance(ProcessExecutor.Processes.CALIBRE)
.runCommandWithOutputHandling(command);
outputFileBytes = Files.readAllBytes(tempOutputFile);
} finally {
// Clean up temporary files
if (tempInputFile != null) {
Files.deleteIfExists(tempInputFile);
}
Files.deleteIfExists(tempOutputFile);
}
String outputFilename =
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "."
+ // Remove file extension and append .pdf
outputFormat;
return WebResponseUtils.bytesToWebResponse(outputFileBytes, outputFilename);
}
}

View File

@@ -21,6 +21,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.converters.PdfToPdfARequest;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;

View File

@@ -18,6 +18,8 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.RuntimePathConfig;
import stirling.software.SPDF.model.api.converters.UrlToPdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils;
@@ -32,10 +34,13 @@ import stirling.software.SPDF.utils.WebResponseUtils;
public class ConvertWebsiteToPDF {
private final CustomPDDocumentFactory pdfDocumentFactory;
private final RuntimePathConfig runtimePathConfig;
@Autowired
public ConvertWebsiteToPDF(CustomPDDocumentFactory pdfDocumentFactory) {
public ConvertWebsiteToPDF(
CustomPDDocumentFactory pdfDocumentFactory, RuntimePathConfig runtimePathConfig) {
this.pdfDocumentFactory = pdfDocumentFactory;
this.runtimePathConfig = runtimePathConfig;
}
@PostMapping(consumes = "multipart/form-data", value = "/url/pdf")
@@ -65,7 +70,7 @@ public class ConvertWebsiteToPDF {
// Prepare the WeasyPrint command
List<String> command = new ArrayList<>();
command.add("/opt/venv/bin/weasyprint");
command.add(runtimePathConfig.getWeasyPrintPath());
command.add(URL);
command.add(tempOutputFile.toString());

View File

@@ -25,9 +25,12 @@ import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.SPDF.pdf.FlexibleCSVWriter;
import technology.tabula.ObjectExtractor;
import technology.tabula.Page;
import technology.tabula.Table;
@@ -51,22 +54,20 @@ public class ExtractCSVController {
try (PDDocument document = Loader.loadPDF(form.getFileInput().getBytes())) {
List<Integer> pages = form.getPageNumbersList(document, true);
SpreadsheetExtractionAlgorithm sea = new SpreadsheetExtractionAlgorithm();
CSVFormat format = CSVFormat.EXCEL.builder()
.setEscape('"')
.setQuoteMode(QuoteMode.ALL)
.build();
CSVFormat format =
CSVFormat.EXCEL.builder().setEscape('"').setQuoteMode(QuoteMode.ALL).build();
for (int pageNum : pages) {
try (ObjectExtractor extractor = new ObjectExtractor(document)) {
log.info("{}",pageNum);
log.info("{}", pageNum);
Page page = extractor.extract(pageNum);
List<Table> tables = sea.extract(page);
for (int i = 0; i < tables.size(); i++) {
StringWriter sw = new StringWriter();
FlexibleCSVWriter csvWriter = new FlexibleCSVWriter(format);
csvWriter.write(sw, Collections.singletonList(tables.get(i)));
csvWriter.write(sw, Collections.singletonList(tables.get(i)));
String entryName = generateEntryName(baseName, pageNum, i + 1);
csvEntries.add(new CsvEntry(entryName, sw.toString()));
}
@@ -83,7 +84,8 @@ public class ExtractCSVController {
}
}
private ResponseEntity<byte[]> createZipResponse(List<CsvEntry> entries, String baseName) throws IOException {
private ResponseEntity<byte[]> createZipResponse(List<CsvEntry> entries, String baseName)
throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (ZipOutputStream zipOut = new ZipOutputStream(baos)) {
for (CsvEntry entry : entries) {
@@ -93,21 +95,25 @@ public class ExtractCSVController {
zipOut.closeEntry();
}
}
HttpHeaders headers = new HttpHeaders();
headers.setContentDisposition(ContentDisposition.builder("attachment")
.filename(baseName + "_extracted.zip").build());
headers.setContentDisposition(
ContentDisposition.builder("attachment")
.filename(baseName + "_extracted.zip")
.build());
headers.setContentType(MediaType.parseMediaType("application/zip"));
return ResponseEntity.ok().headers(headers).body(baos.toByteArray());
}
private ResponseEntity<String> createCsvResponse(CsvEntry entry, String baseName) {
HttpHeaders headers = new HttpHeaders();
headers.setContentDisposition(ContentDisposition.builder("attachment")
.filename(baseName + "_extracted.csv").build());
headers.setContentDisposition(
ContentDisposition.builder("attachment")
.filename(baseName + "_extracted.csv")
.build());
headers.setContentType(MediaType.parseMediaType("text/csv"));
return ResponseEntity.ok().headers(headers).body(entry.content());
}

View File

@@ -21,6 +21,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.ExtractHeaderRequest;
import stirling.software.SPDF.utils.WebResponseUtils;

View File

@@ -33,6 +33,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.AutoSplitPdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;

View File

@@ -29,6 +29,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.RemoveBlankPagesRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.PdfUtils;

View File

@@ -30,6 +30,7 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.ExtractImageScansRequest;
import stirling.software.SPDF.utils.CheckProgramInstall;
import stirling.software.SPDF.utils.ProcessExecutor;

View File

@@ -38,6 +38,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.PDFExtractImagesRequest;
import stirling.software.SPDF.utils.ImageProcessingUtils;
import stirling.software.SPDF.utils.WebResponseUtils;

View File

@@ -25,6 +25,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.FlattenRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;

View File

@@ -21,6 +21,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.MetadataRequest;
import stirling.software.SPDF.utils.WebResponseUtils;
import stirling.software.SPDF.utils.propertyeditor.StringToMapPropertyEditor;

View File

@@ -30,6 +30,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.api.misc.ProcessPdfWithOcrRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;

View File

@@ -16,6 +16,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.OverlayImageRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.PdfUtils;

View File

@@ -26,6 +26,7 @@ import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.PrintFileRequest;
@RestController

View File

@@ -19,7 +19,9 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletContext;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.SPDFApplication;
import stirling.software.SPDF.model.ApiEndpoint;
import stirling.software.SPDF.model.Role;

View File

@@ -24,6 +24,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.PipelineConfig;
import stirling.software.SPDF.model.PipelineResult;
import stirling.software.SPDF.model.api.HandleDataRequest;

View File

@@ -5,9 +5,14 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystemException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
@@ -24,7 +29,8 @@ import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.InstallationPathConfig;
import stirling.software.SPDF.config.RuntimePathConfig;
import stirling.software.SPDF.model.PipelineConfig;
import stirling.software.SPDF.model.PipelineOperation;
import stirling.software.SPDF.model.PipelineResult;
@@ -50,18 +56,19 @@ public class PipelineDirectoryProcessor {
ObjectMapper objectMapper,
ApiDocService apiDocService,
PipelineProcessor processor,
FileMonitor fileMonitor) {
FileMonitor fileMonitor,
RuntimePathConfig runtimePathConfig) {
this.objectMapper = objectMapper;
this.apiDocService = apiDocService;
this.watchedFoldersDir = InstallationPathConfig.getPipelineWatchedFoldersDir();
this.finishedFoldersDir = InstallationPathConfig.getPipelineFinishedFoldersDir();
this.watchedFoldersDir = runtimePathConfig.getPipelineWatchedFoldersPath();
this.finishedFoldersDir = runtimePathConfig.getPipelineFinishedFoldersPath();
this.processor = processor;
this.fileMonitor = fileMonitor;
}
@Scheduled(fixedRate = 60000)
public void scanFolders() {
Path watchedFolderPath = Paths.get(watchedFoldersDir);
Path watchedFolderPath = Paths.get(watchedFoldersDir).toAbsolutePath();
if (!Files.exists(watchedFolderPath)) {
try {
Files.createDirectories(watchedFolderPath);
@@ -71,19 +78,33 @@ public class PipelineDirectoryProcessor {
return;
}
}
try (Stream<Path> paths = Files.walk(watchedFolderPath)) {
paths.filter(Files::isDirectory)
.forEach(
t -> {
try {
if (!t.equals(watchedFolderPath) && !t.endsWith("processing")) {
handleDirectory(t);
}
} catch (Exception e) {
log.error("Error handling directory: {}", t, e);
try {
Files.walkFileTree(
watchedFolderPath,
new SimpleFileVisitor<>() {
@Override
public FileVisitResult preVisitDirectory(
Path dir, BasicFileAttributes attrs) {
try {
// Skip root directory and "processing" subdirectories
if (!dir.equals(watchedFolderPath) && !dir.endsWith("processing")) {
handleDirectory(dir);
}
});
} catch (Exception e) {
} catch (Exception e) {
log.error("Error handling directory: {}", dir, e);
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path path, IOException exc) {
// Handle broken symlinks or inaccessible directories
log.error("Error accessing path: {}", path, exc);
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
log.error("Error walking through directory: {}", watchedFolderPath, e);
}
}
@@ -187,6 +208,7 @@ public class PipelineDirectoryProcessor {
}
return isAllowed;
})
.map(Path::toAbsolutePath)
.filter(
path -> {
boolean isReady =
@@ -200,7 +222,10 @@ public class PipelineDirectoryProcessor {
})
.map(Path::toFile)
.toArray(File[]::new);
log.info("Collected {} files for processing", files.length);
log.info(
"Collected {} files for processing for {}",
files.length,
dir.toAbsolutePath().toString());
return files;
}
}
@@ -210,8 +235,35 @@ public class PipelineDirectoryProcessor {
List<File> filesToProcess = new ArrayList<>();
for (File file : files) {
Path targetPath = resolveUniqueFilePath(processingDir, file.getName());
Files.move(file.toPath(), targetPath);
filesToProcess.add(targetPath.toFile());
// Retry with exponential backoff
int maxRetries = 3;
int retryDelayMs = 500;
boolean moved = false;
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
Files.move(file.toPath(), targetPath, StandardCopyOption.REPLACE_EXISTING);
moved = true;
break;
} catch (FileSystemException e) {
if (attempt < maxRetries) {
log.info("File move failed (attempt {}), retrying...", attempt);
try {
Thread.sleep(retryDelayMs * (int) Math.pow(2, attempt - 1));
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
}
if (moved) {
filesToProcess.add(targetPath.toFile());
} else {
log.error("Failed to move file after {} attempts: {}", maxRetries, file.getName());
}
}
return filesToProcess;
}

View File

@@ -29,7 +29,9 @@ import io.github.pixee.security.Filenames;
import io.github.pixee.security.ZipSecurity;
import jakarta.servlet.ServletContext;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.SPDFApplication;
import stirling.software.SPDF.model.PipelineConfig;
import stirling.software.SPDF.model.PipelineOperation;

View File

@@ -66,6 +66,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;

View File

@@ -60,6 +60,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.PDFFile;
import stirling.software.SPDF.utils.WebResponseUtils;

View File

@@ -26,6 +26,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.PDFText;
import stirling.software.SPDF.model.api.security.ManualRedactPdfRequest;
import stirling.software.SPDF.model.api.security.RedactPdfRequest;

View File

@@ -19,7 +19,9 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
import stirling.software.SPDF.model.*;

View File

@@ -1,6 +1,5 @@
package stirling.software.SPDF.controller.web;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@@ -15,14 +14,6 @@ import stirling.software.SPDF.utils.CheckProgramInstall;
@Tag(name = "Convert", description = "Convert APIs")
public class ConverterWebController {
@ConditionalOnExpression("${bookAndHtmlFormatsInstalled}")
@GetMapping("/book-to-pdf")
@Hidden
public String convertBookToPdfForm(Model model) {
model.addAttribute("currentPage", "book-to-pdf");
return "convert/book-to-pdf";
}
@GetMapping("/img-to-pdf")
@Hidden
public String convertImgToPdfForm(Model model) {
@@ -67,14 +58,6 @@ public class ConverterWebController {
// PDF TO......
@ConditionalOnExpression("${bookAndHtmlFormatsInstalled}")
@GetMapping("/pdf-to-book")
@Hidden
public String convertPdfToBookForm(Model model) {
model.addAttribute("currentPage", "pdf-to-book");
return "convert/pdf-to-book";
}
@GetMapping("/pdf-to-img")
@Hidden
public String pdfToimgForm(Model model) {

View File

@@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import stirling.software.SPDF.config.security.database.DatabaseService;
import stirling.software.SPDF.utils.FileInfo;

View File

@@ -25,7 +25,9 @@ import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.InstallationPathConfig;
import stirling.software.SPDF.config.RuntimePathConfig;
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
import stirling.software.SPDF.model.SignatureFile;
import stirling.software.SPDF.service.SignatureService;
@@ -38,14 +40,17 @@ public class GeneralWebController {
private final SignatureService signatureService;
private final UserServiceInterface userService;
private final ResourceLoader resourceLoader;
private final RuntimePathConfig runtimePathConfig;
public GeneralWebController(
SignatureService signatureService,
@Autowired(required = false) UserServiceInterface userService,
ResourceLoader resourceLoader) {
ResourceLoader resourceLoader,
RuntimePathConfig runtimePathConfig) {
this.signatureService = signatureService;
this.userService = userService;
this.resourceLoader = resourceLoader;
this.runtimePathConfig = runtimePathConfig;
}
@GetMapping("/pipeline")
@@ -54,11 +59,9 @@ public class GeneralWebController {
model.addAttribute("currentPage", "pipeline");
List<String> pipelineConfigs = new ArrayList<>();
List<Map<String, String>> pipelineConfigsWithNames = new ArrayList<>();
if (new File(InstallationPathConfig.getPipelineDefaultWebUIConfigsDir()).exists()) {
if (new File(runtimePathConfig.getPipelineDefaultWebUiConfigs()).exists()) {
try (Stream<Path> paths =
Files.walk(
Paths.get(
InstallationPathConfig.getPipelineDefaultWebUIConfigsDir()))) {
Files.walk(Paths.get(runtimePathConfig.getPipelineDefaultWebUiConfigs()))) {
List<Path> jsonFiles =
paths.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".json"))

View File

@@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Hidden;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.Dependency;

View File

@@ -19,7 +19,9 @@ import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.StartupApplicationListener;
import stirling.software.SPDF.model.ApplicationProperties;