Compare commits

..

3 Commits

Author SHA1 Message Date
Connor Yoh
835cc3c331 Added recommended extensions to dev container 2025-02-14 15:35:19 +00:00
Connor Yoh
5774026a1d Removed Version tag from dev container build 2025-02-14 15:34:39 +00:00
Connor Yoh
0efd37b076 Basic dockerfile and devcontainer setup. Supports building and running SPDF 2025-02-13 17:59:18 +00:00
643 changed files with 132084 additions and 2290 deletions

View File

@@ -0,0 +1,65 @@
# Main stage
FROM alpine:edge
LABEL org.opencontainers.image.title="Stirling-PDF"
LABEL org.opencontainers.image.description="A powerful locally hosted web-based PDF manipulation tool supporting 50+ operations including merging, splitting, conversion, OCR, watermarking, and more."
LABEL org.opencontainers.image.source="https://github.com/Stirling-Tools/Stirling-PDF"
LABEL org.opencontainers.image.licenses="MIT"
LABEL org.opencontainers.image.vendor="Stirling-Tools"
LABEL org.opencontainers.image.url="https://www.stirlingpdf.com"
LABEL org.opencontainers.image.documentation="https://docs.stirlingpdf.com"
LABEL maintainer="Stirling-Tools"
LABEL org.opencontainers.image.authors="Stirling-Tools"
LABEL org.opencontainers.image.keywords="PDF, manipulation, merge, split, convert, OCR, watermark"
# Set Environment Variables
ENV DOCKER_ENABLE_SECURITY=false \
JAVA_TOOL_OPTIONS="-XX:+UnlockExperimentalVMOptions \
-XX:MaxRAMPercentage=75 \
-XX:InitiatingHeapOccupancyPercent=20 \
-XX:+G1PeriodicGCInvokesConcurrent \
-XX:G1PeriodicGCInterval=10000 \
-XX:+UseStringDeduplication \
-XX:G1PeriodicGCSystemLoadThreshold=70" \
HOME=/home/stirlingpdfuser \
PUID=1000 \
PGID=1000 \
UMASK=022
# JDK for app
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories
RUN apk upgrade -a
RUN apk add ca-certificates
RUN apk add tzdata
RUN apk add tini
RUN apk add bash
RUN apk add curl
RUN apk add git
RUN apk add qpdf
RUN apk add shadow
RUN apk add su-exec
RUN apk add openssl
RUN apk add openssl-dev
RUN apk add openjdk21
# Doc conversion
RUN apk add libreoffice
# pdftohtml
RUN apk add poppler-utils
# OCR MY PDF (unpaper for descew and other advanced features)
RUN apk add tesseract-ocr-data-eng
# python3/pip
RUN apk add python3
RUN apk add py3-pip
RUN apk add py3-opencv
RUN apk add -X https://dl-cdn.alpinelinux.org/alpine/edge/testing py3-unoconv
RUN apk add -X https://dl-cdn.alpinelinux.org/alpine/edge/testing py3-pdf2image
RUN apk add -X https://dl-cdn.alpinelinux.org/alpine/edge/testing py3-pillow
RUN pip install --break-system-packages --upgrade WeasyPrint
EXPOSE 8080/tcp

View File

@@ -0,0 +1,49 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-dockerfile
{
"name": "Existing Dockerfile",
"build": {
// Sets the run context to one level up instead of the .devcontainer folder.
"context": "..",
// Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename.
"dockerfile": "./Dockerfile.dev"
},
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [
8080
],
// Uncomment the next line to run commands after the container is created.
// "postCreateCommand": "cat /etc/os-release",
// Configure tool-specific properties.
"customizations": {
"vscode": {
"extensions": [
"elagil.pre-commit-helper", // Support for pre-commit hooks to enforce code quality
"josevseb.google-java-format-for-vs-code", // Google Java code formatter to follow the Google Java Style Guide
"ms-python.black-formatter", // Python code formatter using Black
"ms-python.flake8", // Flake8 linter for Python to enforce code quality
"ms-python.python", // Official Microsoft Python extension with IntelliSense, debugging, and Jupyter support
// "ms-vscode-remote.remote-containers", // Support for remote development with containers (Docker, Dev Containers)
// "ms-vscode-remote.vscode-remote-extensionpack", // Remote Development Pack for SSH, WSL, and Containers
"Oracle.oracle-java", // Oracle Java extension with additional features for Java development
"redhat.java", // Java support by Red Hat with IntelliSense, debugging, and code navigation
"shengchen.vscode-checkstyle", // Checkstyle integration for Java code quality checks
"streetsidesoftware.code-spell-checker", // Spell checker for code to avoid typos
"vmware.vscode-boot-dev-pack", // Developer tools for Spring Boot by VMware
"vmware.vscode-spring-boot", // Spring Boot tools by VMware for enhanced Spring development
"vscjava.vscode-gradle", // Gradle extension for build and automation support
"vscjava.vscode-java-debug", // Debugging support for Java projects
"vscjava.vscode-java-dependency", // Java dependency management within VS Code
"vscjava.vscode-java-pack", // Java Extension Pack with essential Java tools for VS Code
"vscjava.vscode-java-test", // Java test framework for running and debugging tests in VS Code
"vscjava.vscode-spring-boot-dashboard", // Spring Boot dashboard for managing and visualizing Spring Boot applications
"vscjava.vscode-spring-initializr", // Support for Spring Initializr to create new Spring projects
"ms-azuretools.vscode-docker" // build, manage, and deploy containerized applications from Visual Studio Code
]
}
}
// Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "devcontainer"
}

View File

@@ -37,7 +37,7 @@ jobs:
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit
@@ -82,7 +82,7 @@ jobs:
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit
@@ -103,10 +103,9 @@ jobs:
run: ./gradlew clean build run: ./gradlew clean build
env: env:
DOCKER_ENABLE_SECURITY: false DOCKER_ENABLE_SECURITY: false
STIRLING_PDF_DESKTOP_UI: false
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0 uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
- name: Get version number - name: Get version number
id: versionNumber id: versionNumber
@@ -121,7 +120,7 @@ jobs:
password: ${{ secrets.DOCKER_HUB_API }} password: ${{ secrets.DOCKER_HUB_API }}
- name: Build and push PR-specific image - name: Build and push PR-specific image
uses: docker/build-push-action@0adf9959216b96bec444f325f1e493d4aa344497 # v6.14.0 uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile

View File

@@ -21,7 +21,7 @@ jobs:
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit

View File

@@ -13,7 +13,7 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit

View File

@@ -24,7 +24,7 @@ jobs:
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit
@@ -49,7 +49,7 @@ jobs:
- name: Upload Test Reports - name: Upload Test Reports
if: always() if: always()
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with: with:
name: test-reports-jdk-${{ matrix.jdk-version }} name: test-reports-jdk-${{ matrix.jdk-version }}
path: | path: |
@@ -62,7 +62,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit
@@ -80,7 +80,7 @@ jobs:
- name: FAILED - check the licenses for compatibility - name: FAILED - check the licenses for compatibility
if: failure() if: failure()
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with: with:
name: dependencies-without-allowed-license.json name: dependencies-without-allowed-license.json
path: | path: |
@@ -106,7 +106,7 @@ jobs:
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit
@@ -120,7 +120,7 @@ jobs:
distribution: "adopt" distribution: "adopt"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0 uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
- name: Install Docker Compose - name: Install Docker Compose
run: | run: |

View File

@@ -18,7 +18,7 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit

View File

@@ -18,13 +18,13 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit
- name: Generate GitHub App Token - name: Generate GitHub App Token
id: generate-token id: generate-token
uses: actions/create-github-app-token@0d564482f06ca65fa9e77e2510873638c82206f2 # v1.11.5 uses: actions/create-github-app-token@136412a57a7081aa63c935a2cc2918f76c34f514 # v1.11.2
with: with:
app-id: ${{ secrets.GH_APP_ID }} app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
@@ -45,7 +45,7 @@ jobs:
- name: FAILED - check the licenses for compatibility - name: FAILED - check the licenses for compatibility
if: failure() if: failure()
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with: with:
name: dependencies-without-allowed-license.json name: dependencies-without-allowed-license.json
path: | path: |

View File

@@ -15,7 +15,7 @@ jobs:
issues: write issues: write
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit

View File

@@ -16,7 +16,7 @@ jobs:
versionMac: ${{ steps.versionNumberMac.outputs.versionNumberMac }} versionMac: ${{ steps.versionNumberMac.outputs.versionNumberMac }}
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit
@@ -51,7 +51,7 @@ jobs:
file_suffix: "" file_suffix: ""
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit
@@ -80,7 +80,7 @@ jobs:
mv ./build/libs/Stirling-PDF-${{ needs.read_versions.outputs.version }}.jar ./binaries/Stirling-PDF${{ matrix.file_suffix }}.jar mv ./build/libs/Stirling-PDF-${{ needs.read_versions.outputs.version }}.jar ./binaries/Stirling-PDF${{ matrix.file_suffix }}.jar
- name: Upload build artifacts - name: Upload build artifacts
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with: with:
retention-days: 1 retention-days: 1
if-no-files-found: error if-no-files-found: error
@@ -101,7 +101,7 @@ jobs:
file_suffix: "" file_suffix: ""
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit
@@ -114,7 +114,7 @@ jobs:
run: ls -R run: ls -R
- name: Upload signed artifacts - name: Upload signed artifacts
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with: with:
retention-days: 1 retention-days: 1
if-no-files-found: error if-no-files-found: error
@@ -139,7 +139,7 @@ jobs:
contents: write contents: write
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit
@@ -188,7 +188,7 @@ jobs:
run: ls -R ./binaries run: ls -R ./binaries
- name: Upload build artifacts - name: Upload build artifacts
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with: with:
retention-days: 1 retention-days: 1
if-no-files-found: error if-no-files-found: error
@@ -210,7 +210,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit
@@ -224,7 +224,7 @@ jobs:
- name: Install Cosign - name: Install Cosign
if: matrix.os == 'windows-latest' if: matrix.os == 'windows-latest'
uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3.8.1 uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
- name: Generate key pair - name: Generate key pair
if: matrix.os == 'windows-latest' if: matrix.os == 'windows-latest'
@@ -255,7 +255,7 @@ jobs:
run: ls -R run: ls -R
- name: Upload signed artifacts - name: Upload signed artifacts
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with: with:
retention-days: 1 retention-days: 1
if-no-files-found: error if-no-files-found: error
@@ -271,7 +271,7 @@ jobs:
contents: write contents: write
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit

View File

@@ -16,13 +16,13 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit
- name: Generate GitHub App Token - name: Generate GitHub App Token
id: generate-token id: generate-token
uses: actions/create-github-app-token@0d564482f06ca65fa9e77e2510873638c82206f2 # v1.11.5 uses: actions/create-github-app-token@136412a57a7081aa63c935a2cc2918f76c34f514 # v1.11.2
with: with:
app-id: ${{ secrets.GH_APP_ID }} app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}

View File

@@ -18,7 +18,7 @@ jobs:
id-token: write id-token: write
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit
@@ -38,17 +38,16 @@ jobs:
run: ./gradlew clean build run: ./gradlew clean build
env: env:
DOCKER_ENABLE_SECURITY: false DOCKER_ENABLE_SECURITY: false
STIRLING_PDF_DESKTOP_UI: false
- name: Install cosign - name: Install cosign
if: github.ref == 'refs/heads/master' if: github.ref == 'refs/heads/master'
uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3.8.1 uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
with: with:
cosign-release: "v2.4.1" cosign-release: "v2.4.1"
- name: Set up Docker Buildx - name: Set up Docker Buildx
id: buildx id: buildx
uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0 uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
- name: Get version number - name: Get version number
id: versionNumber id: versionNumber
@@ -68,7 +67,7 @@ jobs:
password: ${{ github.token }} password: ${{ github.token }}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@4574d27a4764455b42196d70a065bc6853246a25 # v3.4.0 uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3.3.0
- name: Convert repository owner to lowercase - name: Convert repository owner to lowercase
id: repoowner id: repoowner
@@ -90,7 +89,7 @@ jobs:
- name: Build and push main Dockerfile - name: Build and push main Dockerfile
id: build-push-regular id: build-push-regular
uses: docker/build-push-action@0adf9959216b96bec444f325f1e493d4aa344497 # v6.14.0 uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
with: with:
builder: ${{ steps.buildx.outputs.name }} builder: ${{ steps.buildx.outputs.name }}
context: . context: .
@@ -135,7 +134,7 @@ jobs:
- name: Build and push Dockerfile-ultra-lite - name: Build and push Dockerfile-ultra-lite
id: build-push-lite id: build-push-lite
uses: docker/build-push-action@0adf9959216b96bec444f325f1e493d4aa344497 # v6.14.0 uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
if: github.ref != 'refs/heads/main' if: github.ref != 'refs/heads/main'
with: with:
context: . context: .
@@ -166,7 +165,7 @@ jobs:
- name: Build and push main Dockerfile fat - name: Build and push main Dockerfile fat
id: build-push-fat id: build-push-fat
uses: docker/build-push-action@0adf9959216b96bec444f325f1e493d4aa344497 # v6.14.0 uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
if: github.ref != 'refs/heads/main' if: github.ref != 'refs/heads/main'
with: with:
builder: ${{ steps.buildx.outputs.name }} builder: ${{ steps.buildx.outputs.name }}

View File

@@ -23,7 +23,7 @@ jobs:
version: ${{ steps.versionNumber.outputs.versionNumber }} version: ${{ steps.versionNumber.outputs.versionNumber }}
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit
@@ -63,7 +63,7 @@ jobs:
ls -R ./build/launch4j ls -R ./build/launch4j
- name: Upload build artifacts - name: Upload build artifacts
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with: with:
name: binaries${{ matrix.file_suffix }} name: binaries${{ matrix.file_suffix }}
path: | path: |
@@ -83,7 +83,7 @@ jobs:
file_suffix: "" file_suffix: ""
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit
@@ -95,7 +95,7 @@ jobs:
run: ls -R run: ls -R
- name: Install Cosign - name: Install Cosign
uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3.8.1 uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
- name: Generate key pair - name: Generate key pair
run: cosign generate-key-pair run: cosign generate-key-pair
@@ -139,7 +139,7 @@ jobs:
./launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe ./launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
- name: Upload signed artifacts - name: Upload signed artifacts
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with: with:
name: signed${{ matrix.file_suffix }} name: signed${{ matrix.file_suffix }}
path: | path: |
@@ -161,7 +161,7 @@ jobs:
file_suffix: "" file_suffix: ""
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit

View File

@@ -34,7 +34,7 @@ jobs:
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit
@@ -44,7 +44,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: "Run analysis" - name: "Run analysis"
uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
with: with:
results_file: results.sarif results_file: results.sarif
results_format: sarif results_format: sarif
@@ -66,7 +66,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab. # format to the repository Actions tab.
- name: "Upload artifact" - name: "Upload artifact"
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with: with:
name: SARIF file name: SARIF file
path: results.sarif path: results.sarif
@@ -74,6 +74,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard. # Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning" - name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10 uses: github/codeql-action/upload-sarif@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8
with: with:
sarif_file: results.sarif sarif_file: results.sarif

View File

@@ -1,33 +1,35 @@
name: Run Sonarqube
on: on:
push: push:
branches: branches:
- master - master
pull_request_target: pull_request:
branches: branches: [ "main" ]
- main
workflow_dispatch: workflow_dispatch:
permissions: permissions:
pull-requests: read pull-requests: read
actions: read actions: read
name: Run Sonarqube
jobs: jobs:
sonarqube: sonarqube:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup Gradle - name: Set up JDK
uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0 uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
with:
java-version: '17'
distribution: 'temurin'
- name: Build and analyze with Gradle - name: Build and analyze with Gradle
env: env:
@@ -40,13 +42,12 @@ jobs:
-Dsonar.projectKey=Stirling-Tools_Stirling-PDF \ -Dsonar.projectKey=Stirling-Tools_Stirling-PDF \
-Dsonar.organization=stirling-tools \ -Dsonar.organization=stirling-tools \
-Dsonar.host.url=https://sonarcloud.io \ -Dsonar.host.url=https://sonarcloud.io \
-Dsonar.login=${SONAR_TOKEN} \
-Dsonar.log.level=DEBUG \ -Dsonar.log.level=DEBUG \
--info --info
- name: Upload Problems Report on Failure - name: Upload Problems Report on Failure
if: failure() if: failure()
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with: with:
name: gradle-problems-report name: gradle-problems-report
path: build/reports/problems/problems-report.html path: build/reports/problems/problems-report.html
@@ -54,7 +55,7 @@ jobs:
- name: Upload Sonar Logs on Failure - name: Upload Sonar Logs on Failure
if: failure() if: failure()
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with: with:
name: sonar-logs name: sonar-logs
path: | path: |

View File

@@ -16,7 +16,7 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit

View File

@@ -24,13 +24,13 @@ jobs:
committer: ${{ steps.committer.outputs.committer }} committer: ${{ steps.committer.outputs.committer }}
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit
- name: Generate GitHub App Token - name: Generate GitHub App Token
id: generate-token id: generate-token
uses: actions/create-github-app-token@0d564482f06ca65fa9e77e2510873638c82206f2 # v1.11.5 uses: actions/create-github-app-token@136412a57a7081aa63c935a2cc2918f76c34f514 # v1.11.2
with: with:
app-id: ${{ secrets.GH_APP_ID }} app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
@@ -57,13 +57,13 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit
- name: Generate GitHub App Token - name: Generate GitHub App Token
id: generate-token id: generate-token
uses: actions/create-github-app-token@0d564482f06ca65fa9e77e2510873638c82206f2 # v1.11.5 uses: actions/create-github-app-token@136412a57a7081aa63c935a2cc2918f76c34f514 # v1.11.2
with: with:
app-id: ${{ vars.GH_APP_ID }} app-id: ${{ vars.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}

View File

@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit
@@ -31,7 +31,7 @@ jobs:
DOCKER_ENABLE_SECURITY: false DOCKER_ENABLE_SECURITY: false
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0 uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
- name: Get version number - name: Get version number
id: versionNumber id: versionNumber
@@ -46,7 +46,7 @@ jobs:
password: ${{ secrets.DOCKER_HUB_API }} password: ${{ secrets.DOCKER_HUB_API }}
- name: Build and push test image - name: Build and push test image
uses: docker/build-push-action@0adf9959216b96bec444f325f1e493d4aa344497 # v6.14.0 uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile
@@ -105,7 +105,7 @@ jobs:
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit
@@ -134,7 +134,7 @@ jobs:
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4
with: with:
egress-policy: audit egress-policy: audit

View File

@@ -9,6 +9,7 @@
// "ms-vscode-remote.vscode-remote-extensionpack", // Remote Development Pack for SSH, WSL, and Containers // "ms-vscode-remote.vscode-remote-extensionpack", // Remote Development Pack for SSH, WSL, and Containers
"Oracle.oracle-java", // Oracle Java extension with additional features for Java development "Oracle.oracle-java", // Oracle Java extension with additional features for Java development
"redhat.java", // Java support by Red Hat with IntelliSense, debugging, and code navigation "redhat.java", // Java support by Red Hat with IntelliSense, debugging, and code navigation
"shengchen.vscode-checkstyle", // Checkstyle integration for Java code quality checks
"streetsidesoftware.code-spell-checker", // Spell checker for code to avoid typos "streetsidesoftware.code-spell-checker", // Spell checker for code to avoid typos
"vmware.vscode-boot-dev-pack", // Developer tools for Spring Boot by VMware "vmware.vscode-boot-dev-pack", // Developer tools for Spring Boot by VMware
"vmware.vscode-spring-boot", // Spring Boot tools by VMware for enhanced Spring development "vmware.vscode-spring-boot", // Spring Boot tools by VMware for enhanced Spring development

121
.vscode/settings.json vendored
View File

@@ -2,147 +2,54 @@
"java.compile.nullAnalysis.mode": "automatic", "java.compile.nullAnalysis.mode": "automatic",
"files.eol": "auto", "files.eol": "auto",
"java.configuration.updateBuildConfiguration": "interactive", "java.configuration.updateBuildConfiguration": "interactive",
"black-formatter.args": [ "black-formatter.args": ["--line-length", "127"],
"--line-length", "flake8.args": ["--max-line-length", "127"],
"127" "pylint.args": ["max-line-length", "127"],
],
"flake8.args": [
"--max-line-length",
"127"
],
"[java]": { "[java]": {
"editor.tabSize": 4, "editor.tabSize": 4,
"editor.detectIndentation": false, "editor.detectIndentation": false,
"editor.rulers": [ "editor.rulers": [127]
127
],
"editor.defaultFormatter": "josevseb.google-java-format-for-vs-code"
}, },
"[python]": { "[python]": {
"editor.tabSize": 2, "editor.tabSize": 2,
"editor.detectIndentation": false, "editor.detectIndentation": false,
"editor.rulers": [ "editor.rulers": [127]
127
]
}, },
"[gradle-build]": { "[gradle-build]": {
"editor.tabSize": 4, "editor.tabSize": 4,
"editor.detectIndentation": false, "editor.detectIndentation": false,
"editor.rulers": [ "editor.rulers": [127]
127
]
}, },
"[gradle]": { "[gradle]": {
"editor.tabSize": 4, "editor.tabSize": 4,
"editor.detectIndentation": false, "editor.detectIndentation": false,
"editor.rulers": [ "editor.rulers": [127]
127
]
}, },
"[html]": { "[html]": {
"editor.tabSize": 2, "editor.tabSize": 2,
"editor.rulers": [ "editor.rulers": [127],
127
],
"files.trimFinalNewlines": false, "files.trimFinalNewlines": false,
"files.insertFinalNewline": false "files.insertFinalNewline": false
}, },
"[javascript]": { "[javascript]": {
"editor.tabSize": 2, "editor.tabSize": 2,
"editor.rulers": [ "editor.rulers": [127]
127
]
}, },
"[yaml]": { "[yaml]": {
"files.trimFinalNewlines": false, "files.trimFinalNewlines": false,
"files.insertFinalNewline": false "files.insertFinalNewline": false
}, },
"diffEditor.maxComputationTime": 0,
"editor.wordSegmenterLocales": null,
"editor.guides.bracketPairs": "active",
"editor.guides.bracketPairsHorizontal": "active",
"files.insertFinalNewline": true, "files.insertFinalNewline": true,
"files.trimFinalNewlines": true, "files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true, "files.trimTrailingWhitespace": true,
"files.autoSave": "onFocusChange",
"files.autoSaveWhenNoErrors": true,
"diffEditor.maxComputationTime": 0,
"editor.wordSegmenterLocales": "",
"editor.guides.bracketPairs": "active",
"editor.guides.bracketPairsHorizontal": "active",
"editor.indentSize": "tabSize", "editor.indentSize": "tabSize",
"editor.stickyScroll.enabled": false, "editor.stickyScroll.enabled": false,
"editor.minimap.enabled": false, "editor.minimap.enabled": false,
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.insertSpaces": true,
"java.format.enabled": true,
"java.format.settings.profile": "GoogleStyle",
"java.format.settings.google.version": "1.25.2",
"java.format.settings.google.mode": "jar-file", "java.format.settings.google.mode": "jar-file",
"java.format.settings.google.extra": "--aosp --skip-sorting-imports --skip-javadoc-formatting", "java.format.settings.google.extra": "--aosp --skip-sorting-imports"
// (DE) Aktiviert Kommentare im Java-Format.
// (EN) Enables comments in Java formatting.
// "java.format.comments.enabled": true,
// (DE) Generiert automatisch Kommentare im Code.
// (EN) Automatically generates comments in code.
// "java.codeGeneration.generateComments": true,
// https://github.com/redhat-developer/vscode-java/blob/master/document/_java.learnMoreAboutCleanUps.md#java-clean-ups
"java.saveActions.cleanup": true,
"java.cleanup.actions": [
"invertEquals", // Inverts calls to Object.equals(Object) and String.equalsIgnoreCase(String) to avoid useless null pointer exception.
"instanceofPatternMatch" // Replaces instanceof checks with pattern matching.
],
// (DE) Aktiviert die Code-Vervollständigung für Java.
// (EN) Enables code completion for Java.
"java.completion.engine": "dom",
"java.completion.enabled": true,
"java.completion.importOrder": [
"java",
"javax",
"org",
"com",
"net",
"io",
"jakarta",
"lombok",
"me",
"stirling",
],
"java.project.resourceFilters": [
".devcontainer/",
".git/",
".github/",
".gradle/",
".venv/",
".venv*/",
".vscode/",
"bin/",
"build/",
"configs/",
"customFiles/",
"docs/",
"exampleYmlFiles",
"gradle/",
"images/",
"logs/",
"pipeline/",
"scripts/",
"testings/",
".git-blame-ignore-revs",
".gitattributes",
".gitignore",
".pre-commit-config.yaml",
],
// Enables signature help in Java.
"java.signatureHelp.enabled": true,
// Enables detailed signature help descriptions.
"java.signatureHelp.description.enabled": true,
// Downloads sources for Maven dependencies.
"java.maven.downloadSources": true,
// Enables Gradle project import.
"java.import.gradle.enabled": true,
// Downloads sources for Eclipse projects.
"java.eclipse.downloadSources": true,
// Enables import of the Gradle wrapper.
"java.import.gradle.wrapper.enabled": true,
"spring.initializr.defaultLanguage": "Java",
"spring.initializr.defaultGroupId": "stirling.software.SPDF",
"spring.initializr.defaultArtifactId": "SPDF",
"cSpell.enabled": false,
} }

View File

@@ -1,5 +1,5 @@
# Main stage # Main stage
FROM alpine:3.21.3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c FROM alpine:3.21.2@sha256:56fa17d2a7e7f168a043a2712e63aed1f8543aeafdcee47c58dcffe38ed51099
# Copy necessary files # Copy necessary files
COPY scripts /scripts COPY scripts /scripts
@@ -35,56 +35,47 @@ ENV DOCKER_ENABLE_SECURITY=false \
HOME=/home/stirlingpdfuser \ HOME=/home/stirlingpdfuser \
PUID=1000 \ PUID=1000 \
PGID=1000 \ PGID=1000 \
UMASK=022 \ UMASK=022
PYTHONPATH=/usr/lib/libreoffice/program:/opt/venv/lib/python3.12/site-packages \
UNO_PATH=/usr/lib/libreoffice/program \
URE_BOOTSTRAP=file:///usr/lib/libreoffice/program/fundamentalrc
# JDK for app # JDK for app
RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
echo "@community https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \ echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \ echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
apk upgrade --no-cache -a && \ apk upgrade --no-cache -a && \
apk add --no-cache \ apk add --no-cache \
ca-certificates \ ca-certificates \
tzdata \ tzdata \
tini \ tini \
bash \ bash \
curl \ curl \
qpdf \ qpdf \
shadow \ shadow \
su-exec \ su-exec \
openssl \ openssl \
openssl-dev \ openssl-dev \
openjdk21-jre \ openjdk21-jre \
# Doc conversion # Doc conversion
gcompat \ gcompat \
libc6-compat \ libc6-compat \
libreoffice \ libreoffice \
# pdftohtml # pdftohtml
poppler-utils \ poppler-utils \
# OCR MY PDF (unpaper for descew and other advanced features) # OCR MY PDF (unpaper for descew and other advanced features)
tesseract-ocr-data-eng \ tesseract-ocr-data-eng \
# CV # CV
py3-opencv \ py3-opencv \
python3 \ # python3/pip
py3-pip \ python3 \
py3-pillow@testing \ py3-pip && \
py3-pdf2image@testing && \ # uno unoconv and HTML
python3 -m venv /opt/venv && \ pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint pdf2image pillow && \
export PATH="/opt/venv/bin:$PATH" && \
pip install --upgrade pip && \
pip install --no-cache-dir --upgrade unoserver weasyprint && \
ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \
ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \
ln -s /usr/lib/libreoffice/program /opt/venv/lib/python3.12/site-packages/LibreOffice && \
mv /usr/share/tessdata /usr/share/tessdata-original && \ mv /usr/share/tessdata /usr/share/tessdata-original && \
mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \ mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \
fc-cache -f -v && \ fc-cache -f -v && \
chmod +x /scripts/* && \ chmod +x /scripts/* && \
chmod +x /scripts/init.sh && \ chmod +x /scripts/init.sh && \
# User permissions # User permissions
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \ addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \ chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
chown stirlingpdfuser:stirlingpdfgroup /app.jar chown stirlingpdfuser:stirlingpdfgroup /app.jar
@@ -93,4 +84,4 @@ EXPOSE 8080/tcp
# Set user and run command # Set user and run command
ENTRYPOINT ["tini", "--", "/scripts/init.sh"] ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 0.0.0.0"] CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"]

View File

@@ -9,11 +9,10 @@ COPY . .
# Build the application with DOCKER_ENABLE_SECURITY=false # Build the application with DOCKER_ENABLE_SECURITY=false
RUN DOCKER_ENABLE_SECURITY=true \ RUN DOCKER_ENABLE_SECURITY=true \
STIRLING_PDF_DESKTOP_UI=false \ ./gradlew clean build
./gradlew clean build
# Main stage # Main stage
FROM alpine:3.21.3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c FROM alpine:3.21.2@sha256:56fa17d2a7e7f168a043a2712e63aed1f8543aeafdcee47c58dcffe38ed51099
# Copy necessary files # Copy necessary files
COPY scripts /scripts COPY scripts /scripts
@@ -38,63 +37,54 @@ ENV DOCKER_ENABLE_SECURITY=false \
PGID=1000 \ PGID=1000 \
UMASK=022 \ UMASK=022 \
FAT_DOCKER=true \ FAT_DOCKER=true \
INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false \ INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false
PYTHONPATH=/usr/lib/libreoffice/program:/opt/venv/lib/python3.12/site-packages \
UNO_PATH=/usr/lib/libreoffice/program \
URE_BOOTSTRAP=file:///usr/lib/libreoffice/program/fundamentalrc
# JDK for app # JDK for app
RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
echo "@community https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \ echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \ echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
apk upgrade --no-cache -a && \ apk upgrade --no-cache -a && \
apk add --no-cache \ apk add --no-cache \
ca-certificates \ ca-certificates \
tzdata \ tzdata \
tini \ tini \
bash \ bash \
curl \ curl \
shadow \ shadow \
su-exec \ su-exec \
openssl \ openssl \
openssl-dev \ openssl-dev \
openjdk21-jre \ openjdk21-jre \
# Doc conversion # Doc conversion
gcompat \ gcompat \
libc6-compat \ libc6-compat \
libreoffice \ libreoffice \
# pdftohtml # pdftohtml
poppler-utils \ poppler-utils \
# OCR MY PDF (unpaper for descew and other advanced featues) # OCR MY PDF (unpaper for descew and other advanced featues)
qpdf \ qpdf \
tesseract-ocr-data-eng \ tesseract-ocr-data-eng \
font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra \
font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra font-liberation font-linux-libertine \ # CV
# CV py3-opencv \
py3-opencv \ # python3/pip
python3 \ python3 \
py3-pip \ py3-pip && \
py3-pillow@testing \ # uno unoconv and HTML
py3-pdf2image@testing && \ pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint pdf2image pillow && \
python3 -m venv /opt/venv && \
export PATH="/opt/venv/bin:$PATH" && \
pip install --upgrade pip && \
pip install --no-cache-dir --upgrade unoserver weasyprint && \
ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \
ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \
ln -s /usr/lib/libreoffice/program /opt/venv/lib/python3.12/site-packages/LibreOffice && \
mv /usr/share/tessdata /usr/share/tessdata-original && \ mv /usr/share/tessdata /usr/share/tessdata-original && \
mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \ mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \
fc-cache -f -v && \ fc-cache -f -v && \
chmod +x /scripts/* && \ chmod +x /scripts/* && \
chmod +x /scripts/init.sh && \ chmod +x /scripts/init.sh && \
# User permissions # User permissions
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \ addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \ chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
chown stirlingpdfuser:stirlingpdfgroup /app.jar chown stirlingpdfuser:stirlingpdfgroup /app.jar
EXPOSE 8080/tcp EXPOSE 8080/tcp
# Set user and run command # Set user and run command
ENTRYPOINT ["tini", "--", "/scripts/init.sh"] ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 0.0.0.0"] CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"]

View File

@@ -1,5 +1,5 @@
# use alpine # use alpine
FROM alpine:3.21.3@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c FROM alpine:3.21.2@sha256:56fa17d2a7e7f168a043a2712e63aed1f8543aeafdcee47c58dcffe38ed51099
ARG VERSION_TAG ARG VERSION_TAG

View File

@@ -11,12 +11,14 @@ Fork Stirling-PDF and create a new branch out of `main`.
Then add a reference to the language in the navbar by adding a new language entry to the dropdown: Then add a reference to the language in the navbar by adding a new language entry to the dropdown:
- Edit the file: [languages.html](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/templates/fragments/languages.html) - Edit the file: [languages.html](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/templates/fragments/languages.html)
- Add a flag SVG file to: [flags directory](https://github.com/Stirling-Tools/Stirling-PDF/tree/main/src/main/resources/static/images/flags)
Any SVG flags are fine; most of the current ones were sourced from [here](https://flagicons.lipis.dev/). If your language isn't represented by a flag, choose a similar one, such as Saudi Arabia's flag for Arabic.
For example, to add Polish, you would add: For example, to add Polish, you would add:
```html ```html
<div th:replace="~{fragments/languageEntry :: languageEntry ('pl_PL', 'Polski')}" ></div> <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'pl_PL')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pl_PL"> <img th:src="@{'/images/flags/pl.svg'}" alt="icon" width="20" height="15"> Polski</a>
``` ```
The `data-bs-language-code` is the code used to reference the file in the next step. The `data-bs-language-code` is the code used to reference the file in the next step.

View File

@@ -3,6 +3,7 @@
[![Docker Pulls](https://img.shields.io/docker/pulls/frooodle/s-pdf)](https://hub.docker.com/r/frooodle/s-pdf) [![Docker Pulls](https://img.shields.io/docker/pulls/frooodle/s-pdf)](https://hub.docker.com/r/frooodle/s-pdf)
[![Discord](https://img.shields.io/discord/1068636748814483718?label=Discord)](https://discord.gg/HYmhKj45pU) [![Discord](https://img.shields.io/discord/1068636748814483718?label=Discord)](https://discord.gg/HYmhKj45pU)
[![Docker Image Version (tag latest semver)](https://img.shields.io/docker/v/frooodle/s-pdf/latest)](https://github.com/Stirling-Tools/Stirling-PDF/)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/Stirling-Tools/Stirling-PDF/badge)](https://scorecard.dev/viewer/?uri=github.com/Stirling-Tools/Stirling-PDF) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/Stirling-Tools/Stirling-PDF/badge)](https://scorecard.dev/viewer/?uri=github.com/Stirling-Tools/Stirling-PDF)
[![GitHub Repo stars](https://img.shields.io/github/stars/stirling-tools/stirling-pdf?style=social)](https://github.com/Stirling-Tools/stirling-pdf) [![GitHub Repo stars](https://img.shields.io/github/stars/stirling-tools/stirling-pdf?style=social)](https://github.com/Stirling-Tools/stirling-pdf)
@@ -119,40 +120,40 @@ Stirling-PDF currently supports 39 languages!
| Arabic (العربية) (ar_AR) | ![89%](https://geps.dev/progress/89) | | Arabic (العربية) (ar_AR) | ![89%](https://geps.dev/progress/89) |
| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![88%](https://geps.dev/progress/88) | | Azerbaijani (Azərbaycan Dili) (az_AZ) | ![88%](https://geps.dev/progress/88) |
| Basque (Euskara) (eu_ES) | ![51%](https://geps.dev/progress/51) | | Basque (Euskara) (eu_ES) | ![51%](https://geps.dev/progress/51) |
| Bulgarian (Български) (bg_BG) | ![99%](https://geps.dev/progress/99) | | Bulgarian (Български) (bg_BG) | ![85%](https://geps.dev/progress/85) |
| Catalan (Català) (ca_CA) | ![80%](https://geps.dev/progress/80) | | Catalan (Català) (ca_CA) | ![80%](https://geps.dev/progress/80) |
| Croatian (Hrvatski) (hr_HR) | ![86%](https://geps.dev/progress/86) | | Croatian (Hrvatski) (hr_HR) | ![87%](https://geps.dev/progress/87) |
| Czech (Česky) (cs_CZ) | ![97%](https://geps.dev/progress/97) | | Czech (Česky) (cs_CZ) | ![98%](https://geps.dev/progress/98) |
| Danish (Dansk) (da_DK) | ![85%](https://geps.dev/progress/85) | | Danish (Dansk) (da_DK) | ![86%](https://geps.dev/progress/86) |
| Dutch (Nederlands) (nl_NL) | ![85%](https://geps.dev/progress/85) | | Dutch (Nederlands) (nl_NL) | ![85%](https://geps.dev/progress/85) |
| English (English) (en_GB) | ![100%](https://geps.dev/progress/100) | | English (English) (en_GB) | ![100%](https://geps.dev/progress/100) |
| English (US) (en_US) | ![100%](https://geps.dev/progress/100) | | English (US) (en_US) | ![100%](https://geps.dev/progress/100) |
| French (Français) (fr_FR) | ![96%](https://geps.dev/progress/96) | | French (Français) (fr_FR) | ![96%](https://geps.dev/progress/96) |
| German (Deutsch) (de_DE) | ![99%](https://geps.dev/progress/99) | | German (Deutsch) (de_DE) | ![99%](https://geps.dev/progress/99) |
| Greek (Ελληνικά) (el_GR) | ![97%](https://geps.dev/progress/97) | | Greek (Ελληνικά) (el_GR) | ![98%](https://geps.dev/progress/98) |
| Hindi (हिंदी) (hi_IN) | ![98%](https://geps.dev/progress/98) | | Hindi (हिंदी) (hi_IN) | ![98%](https://geps.dev/progress/98) |
| Hungarian (Magyar) (hu_HU) | ![95%](https://geps.dev/progress/95) | | Hungarian (Magyar) (hu_HU) | ![95%](https://geps.dev/progress/95) |
| Indonesian (Bahasa Indonesia) (id_ID) | ![86%](https://geps.dev/progress/86) | | Indonesian (Bahasa Indonesia) (id_ID) | ![86%](https://geps.dev/progress/86) |
| Irish (Gaeilge) (ga_IE) | ![98%](https://geps.dev/progress/98) | | Irish (Gaeilge) (ga_IE) | ![98%](https://geps.dev/progress/98) |
| Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) | | Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) |
| Japanese (日本語) (ja_JP) | ![92%](https://geps.dev/progress/92) | | Japanese (日本語) (ja_JP) | ![93%](https://geps.dev/progress/93) |
| Korean (한국어) (ko_KR) | ![98%](https://geps.dev/progress/98) | | Korean (한국어) (ko_KR) | ![99%](https://geps.dev/progress/99) |
| Norwegian (Norsk) (no_NB) | ![78%](https://geps.dev/progress/78) | | Norwegian (Norsk) (no_NB) | ![79%](https://geps.dev/progress/79) |
| Persian (فارسی) (fa_IR) | ![94%](https://geps.dev/progress/94) | | Persian (فارسی) (fa_IR) | ![94%](https://geps.dev/progress/94) |
| Polish (Polski) (pl_PL) | ![85%](https://geps.dev/progress/85) | | Polish (Polski) (pl_PL) | ![86%](https://geps.dev/progress/86) |
| Portuguese (Português) (pt_PT) | ![97%](https://geps.dev/progress/97) | | Portuguese (Português) (pt_PT) | ![97%](https://geps.dev/progress/97) |
| Portuguese Brazilian (Português) (pt_BR) | ![98%](https://geps.dev/progress/98) | | Portuguese Brazilian (Português) (pt_BR) | ![98%](https://geps.dev/progress/98) |
| Romanian (Română) (ro_RO) | ![80%](https://geps.dev/progress/80) | | Romanian (Română) (ro_RO) | ![81%](https://geps.dev/progress/81) |
| Russian (Русский) (ru_RU) | ![97%](https://geps.dev/progress/97) | | Russian (Русский) (ru_RU) | ![98%](https://geps.dev/progress/98) |
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![63%](https://geps.dev/progress/63) | | Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![64%](https://geps.dev/progress/64) |
| Simplified Chinese (简体中文) (zh_CN) | ![99%](https://geps.dev/progress/99) | | Simplified Chinese (简体中文) (zh_CN) | ![89%](https://geps.dev/progress/89) |
| Slovakian (Slovensky) (sk_SK) | ![74%](https://geps.dev/progress/74) | | Slovakian (Slovensky) (sk_SK) | ![74%](https://geps.dev/progress/74) |
| Slovenian (Slovenščina) (sl_SI) | ![96%](https://geps.dev/progress/96) | | Slovenian (Slovenščina) (sl_SI) | ![97%](https://geps.dev/progress/97) |
| Spanish (Español) (es_ES) | ![98%](https://geps.dev/progress/98) | | Spanish (Español) (es_ES) | ![87%](https://geps.dev/progress/87) |
| Swedish (Svenska) (sv_SE) | ![92%](https://geps.dev/progress/92) | | Swedish (Svenska) (sv_SE) | ![87%](https://geps.dev/progress/87) |
| Thai (ไทย) (th_TH) | ![85%](https://geps.dev/progress/85) | | Thai (ไทย) (th_TH) | ![86%](https://geps.dev/progress/86) |
| Tibetan (བོད་ཡིག་) (zh_BO) | ![94%](https://geps.dev/progress/94) | | Tibetan (བོད་ཡིག་) (zh_BO) | ![95%](https://geps.dev/progress/95) |
| Traditional Chinese (繁體中文) (zh_TW) | ![99%](https://geps.dev/progress/99) | | Traditional Chinese (繁體中文) (zh_TW) | ![98%](https://geps.dev/progress/98) |
| Turkish (Türkçe) (tr_TR) | ![82%](https://geps.dev/progress/82) | | Turkish (Türkçe) (tr_TR) | ![82%](https://geps.dev/progress/82) |
| Ukrainian (Українська) (uk_UA) | ![72%](https://geps.dev/progress/72) | | Ukrainian (Українська) (uk_UA) | ![72%](https://geps.dev/progress/72) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![79%](https://geps.dev/progress/79) | | Vietnamese (Tiếng Việt) (vi_VN) | ![79%](https://geps.dev/progress/79) |

View File

@@ -1,6 +1,6 @@
plugins { plugins {
id "java" id "java"
id "org.springframework.boot" version "3.4.3" id "org.springframework.boot" version "3.4.1"
id "io.spring.dependency-management" version "1.1.7" id "io.spring.dependency-management" version "1.1.7"
id "org.springdoc.openapi-gradle-plugin" version "1.8.0" id "org.springdoc.openapi-gradle-plugin" version "1.8.0"
id "io.swagger.swaggerhub" version "1.3.2" id "io.swagger.swaggerhub" version "1.3.2"
@@ -15,17 +15,18 @@ plugins {
import com.github.jk1.license.render.* import com.github.jk1.license.render.*
ext { ext {
springBootVersion = "3.4.3" springBootVersion = "3.4.1"
pdfboxVersion = "3.0.4" pdfboxVersion = "3.0.4"
logbackVersion = "1.5.7"
imageioVersion = "3.12.0" imageioVersion = "3.12.0"
lombokVersion = "1.18.36" lombokVersion = "1.18.36"
bouncycastleVersion = "1.80" bouncycastleVersion = "1.80"
springSecuritySamlVersion = "6.4.3" springSecuritySamlVersion = "6.4.2"
openSamlVersion = "4.3.2" openSamlVersion = "4.3.2"
} }
group = "stirling.software" group = "stirling.software"
version = "0.42.0" version = "0.41.0"
java { java {
// 17 is lowest but we support and recommend 21 // 17 is lowest but we support and recommend 21
@@ -260,7 +261,7 @@ spotless {
googleJavaFormat("1.25.2").aosp().reorderImports(false) googleJavaFormat("1.25.2").aosp().reorderImports(false)
importOrder("java", "javax", "org", "com", "net", "io", "jakarta", "lombok", "me", "stirling") importOrder("java", "javax", "org", "com", "net", "io")
toggleOffOn() toggleOffOn()
trimTrailingWhitespace() trimTrailingWhitespace()
leadingTabsToSpaces() leadingTabsToSpaces()
@@ -272,7 +273,7 @@ sonar {
properties { properties {
property "sonar.projectKey", "Stirling-Tools_Stirling-PDF" property "sonar.projectKey", "Stirling-Tools_Stirling-PDF"
property "sonar.organization", "stirling-tools" property "sonar.organization", "stirling-tools"
property "sonar.exclusions", "**/build-wrapper-dump.json, src/main/java/org/apache/**, src/main/resources/static/pdfjs/**, src/main/resources/static/pdfjs-legacy/**, src/main/resources/static/js/thirdParty/**" property "sonar.exclusions", "**/build-wrapper-dump.json, src/main/java/org/apache/**, src/main/resources/static/pdfjs/**, src/main/resources/static/pdfjs-legacy/**, src/main/resources/static/js/thirdParty/**"
property "sonar.coverage.exclusions", "src/main/java/org/apache/**, src/main/resources/static/pdfjs/**, src/main/resources/static/pdfjs-legacy/**, src/main/resources/static/js/thirdParty/**" property "sonar.coverage.exclusions", "src/main/java/org/apache/**, src/main/resources/static/pdfjs/**, src/main/resources/static/pdfjs-legacy/**, src/main/resources/static/js/thirdParty/**"
property "sonar.cpd.exclusions", "src/main/java/org/apache/**, src/main/resources/static/pdfjs/**, src/main/resources/static/pdfjs-legacy/**, src/main/resources/static/js/thirdParty/**" property "sonar.cpd.exclusions", "src/main/java/org/apache/**, src/main/resources/static/pdfjs/**, src/main/resources/static/pdfjs-legacy/**, src/main/resources/static/js/thirdParty/**"
@@ -293,26 +294,14 @@ configurations.all {
} }
dependencies { dependencies {
//tmp for security bumps
implementation 'ch.qos.logback:logback-core:1.5.16'
implementation 'ch.qos.logback:logback-classic:1.5.16'
// Exclude vulnerable BouncyCastle version used in tableau
configurations.all {
exclude group: 'org.bouncycastle', module: 'bcpkix-jdk15on'
exclude group: 'org.bouncycastle', module: 'bcutil-jdk15on'
exclude group: 'org.bouncycastle', module: 'bcmail-jdk15on'
}
if (System.getenv("STIRLING_PDF_DESKTOP_UI") != "false") { if (System.getenv("STIRLING_PDF_DESKTOP_UI") != "false") {
implementation "me.friwi:jcefmaven:132.3.1" implementation "me.friwi:jcefmaven:127.3.1"
implementation "org.openjfx:javafx-controls:21" implementation "org.openjfx:javafx-controls:21"
implementation "org.openjfx:javafx-swing:21" implementation "org.openjfx:javafx-swing:21"
} }
//security updates //security updates
implementation "org.springframework:spring-webmvc:6.2.3" implementation "org.springframework:spring-webmvc:6.2.2"
implementation("io.github.pixee:java-security-toolkit:1.2.1") implementation("io.github.pixee:java-security-toolkit:1.2.1")
@@ -331,8 +320,8 @@ dependencies {
implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion" implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion" implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
implementation "org.springframework.session:spring-session-core:3.4.2" implementation "org.springframework.session:spring-session-core:$springBootVersion"
implementation "org.springframework:spring-jdbc:6.2.3" implementation "org.springframework:spring-jdbc:6.2.2"
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5' implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
// Don't upgrade h2database // Don't upgrade h2database
@@ -407,7 +396,7 @@ dependencies {
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion" implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion" implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion" implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
implementation "io.micrometer:micrometer-core:1.14.4" implementation "io.micrometer:micrometer-core:1.14.3"
implementation group: "com.google.zxing", name: "core", version: "3.5.3" implementation group: "com.google.zxing", name: "core", version: "3.5.3"
// https://mvnrepository.com/artifact/org.commonmark/commonmark // https://mvnrepository.com/artifact/org.commonmark/commonmark
implementation "org.commonmark:commonmark:0.24.0" implementation "org.commonmark:commonmark:0.24.0"

View File

@@ -6,7 +6,6 @@ import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
@Configuration @Configuration

View File

@@ -13,7 +13,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.posthog.java.shaded.org.json.JSONObject; import com.posthog.java.shaded.org.json.JSONObject;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.GeneralUtils;

View File

@@ -7,7 +7,6 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.GeneralUtils;

View File

@@ -1,6 +1,7 @@
package stirling.software.SPDF; package stirling.software.SPDF;
import java.io.IOException; import java.io.IOException;
import java.net.ServerSocket;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@@ -21,14 +22,11 @@ import io.github.pixee.security.SystemCommand;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy; import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.UI.WebBrowser; import stirling.software.SPDF.UI.WebBrowser;
import stirling.software.SPDF.config.ConfigInitializer; import stirling.software.SPDF.config.ConfigInitializer;
import stirling.software.SPDF.config.InstallationPathConfig; import stirling.software.SPDF.config.InstallationPathConfig;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.utils.UrlUtils;
@Slf4j @Slf4j
@EnableScheduling @EnableScheduling
@@ -64,12 +62,6 @@ public class SPDFApplication {
app.setHeadless(false); app.setHeadless(false);
props.put("java.awt.headless", "false"); props.put("java.awt.headless", "false");
props.put("spring.main.web-application-type", "servlet"); props.put("spring.main.web-application-type", "servlet");
int desiredPort = 8080;
String port = UrlUtils.findAvailablePort(desiredPort);
props.put("server.port", port);
System.setProperty("server.port", port);
log.info("Desktop UI mode: Using port {}", port);
} }
app.setAdditionalProfiles(getActiveProfile(args)); app.setAdditionalProfiles(getActiveProfile(args));
@@ -166,17 +158,7 @@ public class SPDFApplication {
} }
@Value("${server.port:8080}") @Value("${server.port:8080}")
public void setServerPort(String port) { public void setServerPortStatic(String port) {
if ("auto".equalsIgnoreCase(port)) {
// Use Spring Boot's automatic port assignment (server.port=0)
SPDFApplication.serverPortStatic =
"0"; // This will let Spring Boot assign an available port
} else {
SPDFApplication.serverPortStatic = port;
}
}
public static void setServerPortStatic(String port) {
if ("auto".equalsIgnoreCase(port)) { if ("auto".equalsIgnoreCase(port)) {
// Use Spring Boot's automatic port assignment (server.port=0) // Use Spring Boot's automatic port assignment (server.port=0)
SPDFApplication.serverPortStatic = SPDFApplication.serverPortStatic =
@@ -213,11 +195,36 @@ public class SPDFApplication {
return new String[] {"default"}; return new String[] {"default"};
} }
private static boolean isPortAvailable(int port) {
try (ServerSocket socket = new ServerSocket(port)) {
return true;
} catch (IOException e) {
return false;
}
}
// Optionally keep this method if you want to provide a manual port-incrementation fallback.
private static String findAvailablePort(int startPort) {
int port = startPort;
while (!isPortAvailable(port)) {
port++;
}
return String.valueOf(port);
}
public static String getStaticBaseUrl() { public static String getStaticBaseUrl() {
return baseUrlStatic; return baseUrlStatic;
} }
public String getNonStaticBaseUrl() {
return baseUrlStatic;
}
public static String getStaticPort() { public static String getStaticPort() {
return serverPortStatic; return serverPortStatic;
} }
public String getNonStaticPort() {
return serverPortStatic;
}
} }

View File

@@ -34,17 +34,13 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import jakarta.annotation.PreDestroy; import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import me.friwi.jcefmaven.CefAppBuilder; import me.friwi.jcefmaven.CefAppBuilder;
import me.friwi.jcefmaven.EnumProgress; import me.friwi.jcefmaven.EnumProgress;
import me.friwi.jcefmaven.MavenCefAppHandlerAdapter; import me.friwi.jcefmaven.MavenCefAppHandlerAdapter;
import me.friwi.jcefmaven.impl.progress.ConsoleProgressHandler; import me.friwi.jcefmaven.impl.progress.ConsoleProgressHandler;
import stirling.software.SPDF.UI.WebBrowser; import stirling.software.SPDF.UI.WebBrowser;
import stirling.software.SPDF.config.InstallationPathConfig; import stirling.software.SPDF.config.InstallationPathConfig;
import stirling.software.SPDF.utils.UIScaling;
@Component @Component
@Slf4j @Slf4j
@@ -219,7 +215,7 @@ public class DesktopBrowser implements WebBrowser {
} }
}); });
frame.setSize(UIScaling.scaleWidth(1280), UIScaling.scaleHeight(800)); frame.setSize(1280, 768);
frame.setLocationRelativeTo(null); frame.setLocationRelativeTo(null);
loadIcon(); loadIcon();
@@ -268,9 +264,7 @@ public class DesktopBrowser implements WebBrowser {
frame.setOpacity(1.0f); frame.setOpacity(1.0f);
frame.setUndecorated(false); frame.setUndecorated(false);
frame.pack(); frame.pack();
frame.setSize( frame.setSize(1280, 800);
UIScaling.scaleWidth(1280),
UIScaling.scaleHeight(800));
frame.setLocationRelativeTo(null); frame.setLocationRelativeTo(null);
log.debug("Frame reconfigured"); log.debug("Frame reconfigured");

View File

@@ -1,22 +1,13 @@
package stirling.software.SPDF.UI.impl; package stirling.software.SPDF.UI.impl;
import java.awt.*; import java.awt.*;
import java.io.BufferedReader;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.swing.*; import javax.swing.*;
import io.github.pixee.security.BoundedLineReader;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.utils.UIScaling;
@Slf4j @Slf4j
public class LoadingWindow extends JDialog { public class LoadingWindow extends JDialog {
private final JProgressBar progressBar; private final JProgressBar progressBar;
@@ -25,13 +16,6 @@ public class LoadingWindow extends JDialog {
private final JLabel brandLabel; private final JLabel brandLabel;
private long startTime; private long startTime;
private Timer stuckTimer;
private long stuckThreshold = 4000;
private long timeAt90Percent = -1;
private volatile Process explorerProcess;
private static final boolean IS_WINDOWS =
System.getProperty("os.name").toLowerCase().contains("win");
public LoadingWindow(Frame parent, String initialUrl) { public LoadingWindow(Frame parent, String initialUrl) {
super(parent, "Initializing Stirling-PDF", true); super(parent, "Initializing Stirling-PDF", true);
startTime = System.currentTimeMillis(); startTime = System.currentTimeMillis();
@@ -57,12 +41,12 @@ public class LoadingWindow extends JDialog {
if (is != null) { if (is != null) {
Image img = ImageIO.read(is); Image img = ImageIO.read(is);
if (img != null) { if (img != null) {
Image scaledImg = UIScaling.scaleIcon(img, 48, 48); Image scaledImg = img.getScaledInstance(48, 48, Image.SCALE_SMOOTH);
JLabel iconLabel = new JLabel(new ImageIcon(scaledImg)); JLabel iconLabel = new JLabel(new ImageIcon(scaledImg));
iconLabel.setHorizontalAlignment(SwingConstants.CENTER); iconLabel.setHorizontalAlignment(SwingConstants.CENTER);
gbc.gridy = 0; gbc.gridy = 0;
mainPanel.add(iconLabel, gbc); mainPanel.add(iconLabel, gbc);
log.info("Icon loaded and scaled successfully"); log.debug("Icon loaded and scaled successfully");
} }
} }
} }
@@ -99,8 +83,7 @@ public class LoadingWindow extends JDialog {
setUndecorated(false); setUndecorated(false);
// Set size and position // Set size and position
setSize(UIScaling.scaleWidth(400), UIScaling.scaleHeight(200)); setSize(400, 200);
setLocationRelativeTo(parent); setLocationRelativeTo(parent);
setAlwaysOnTop(true); setAlwaysOnTop(true);
setProgress(0); setProgress(0);
@@ -111,163 +94,6 @@ public class LoadingWindow extends JDialog {
System.currentTimeMillis() - startTime); System.currentTimeMillis() - startTime);
} }
private void checkAndRefreshExplorer() {
if (!IS_WINDOWS) {
return;
}
if (timeAt90Percent == -1) {
timeAt90Percent = System.currentTimeMillis();
stuckTimer =
new Timer(
1000,
e -> {
long currentTime = System.currentTimeMillis();
if (currentTime - timeAt90Percent > stuckThreshold) {
try {
log.debug(
"Attempting Windows explorer refresh due to 90% stuck state");
String currentDir = System.getProperty("user.dir");
// Store current explorer PIDs before we start new one
Set<String> existingPids = new HashSet<>();
ProcessBuilder listExplorer =
new ProcessBuilder(
"cmd",
"/c",
"wmic",
"process",
"where",
"name='explorer.exe'",
"get",
"ProcessId",
"/format:csv");
Process process = listExplorer.start();
BufferedReader reader =
new BufferedReader(
new InputStreamReader(
process.getInputStream()));
String line;
while ((line =
BoundedLineReader.readLine(
reader, 5_000_000))
!= null) {
if (line.matches(".*\\d+.*")) { // Contains numbers
String[] parts = line.trim().split(",");
if (parts.length >= 2) {
existingPids.add(
parts[parts.length - 1].trim());
}
}
}
process.waitFor(2, TimeUnit.SECONDS);
// Start new explorer
ProcessBuilder pb =
new ProcessBuilder(
"cmd",
"/c",
"start",
"/min",
"/b",
"explorer.exe",
currentDir);
pb.redirectErrorStream(true);
explorerProcess = pb.start();
// Schedule cleanup
Timer cleanupTimer =
new Timer(
2000,
cleanup -> {
try {
// Find new explorer processes
ProcessBuilder findNewExplorer =
new ProcessBuilder(
"cmd",
"/c",
"wmic",
"process",
"where",
"name='explorer.exe'",
"get",
"ProcessId",
"/format:csv");
Process newProcess =
findNewExplorer.start();
BufferedReader newReader =
new BufferedReader(
new InputStreamReader(
newProcess
.getInputStream()));
String newLine;
while ((newLine =
BoundedLineReader
.readLine(
newReader,
5_000_000))
!= null) {
if (newLine.matches(
".*\\d+.*")) {
String[] parts =
newLine.trim()
.split(",");
if (parts.length >= 2) {
String pid =
parts[
parts.length
- 1]
.trim();
if (!existingPids
.contains(
pid)) {
log.debug(
"Found new explorer.exe with PID: "
+ pid);
ProcessBuilder
killProcess =
new ProcessBuilder(
"taskkill",
"/PID",
pid,
"/F");
killProcess
.redirectErrorStream(
true);
Process killResult =
killProcess
.start();
killResult.waitFor(
2,
TimeUnit
.SECONDS);
log.debug(
"Explorer process terminated: "
+ pid);
}
}
}
}
newProcess.waitFor(
2, TimeUnit.SECONDS);
} catch (Exception ex) {
log.error(
"Error cleaning up Windows explorer process",
ex);
}
});
cleanupTimer.setRepeats(false);
cleanupTimer.start();
stuckTimer.stop();
} catch (Exception ex) {
log.error("Error refreshing Windows explorer", ex);
}
}
});
stuckTimer.setRepeats(true);
stuckTimer.start();
}
}
public void setProgress(final int progress) { public void setProgress(final int progress) {
SwingUtilities.invokeLater( SwingUtilities.invokeLater(
() -> { () -> {
@@ -289,23 +115,11 @@ public class LoadingWindow extends JDialog {
// Add thread state logging // Add thread state logging
Thread currentThread = Thread.currentThread(); Thread currentThread = Thread.currentThread();
log.info( log.debug(
"Current thread state - Name: {}, State: {}, Priority: {}", "Current thread state - Name: {}, State: {}, Priority: {}",
currentThread.getName(), currentThread.getName(),
currentThread.getState(), currentThread.getState(),
currentThread.getPriority()); currentThread.getPriority());
if (validProgress >= 90 && validProgress < 95) {
checkAndRefreshExplorer();
} else {
// Reset the timer if we move past 95%
if (validProgress >= 95) {
if (stuckTimer != null) {
stuckTimer.stop();
}
timeAt90Percent = -1;
}
}
} }
progressBar.setValue(validProgress); progressBar.setValue(validProgress);
@@ -331,7 +145,7 @@ public class LoadingWindow extends JDialog {
statusLabel.setText(validStatus); statusLabel.setText(validStatus);
// Log UI state when status changes // Log UI state when status changes
log.info( log.debug(
"UI State - Window visible: {}, Progress: {}%, Status: {}", "UI State - Window visible: {}, Progress: {}%, Status: {}",
isVisible(), progressBar.getValue(), validStatus); isVisible(), progressBar.getValue(), validStatus);

View File

@@ -20,7 +20,6 @@ import org.springframework.core.io.ResourceLoader;
import org.thymeleaf.spring6.SpringTemplateEngine; import org.thymeleaf.spring6.SpringTemplateEngine;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
@Configuration @Configuration
@@ -128,6 +127,15 @@ public class AppConfig {
} }
} }
@Bean(name = "bookAndHtmlFormatsInstalled")
public boolean bookAndHtmlFormatsInstalled() {
String installOps = System.getProperty("INSTALL_BOOK_AND_ADVANCED_HTML_OPS");
if (installOps == null) {
installOps = System.getenv("INSTALL_BOOK_AND_ADVANCED_HTML_OPS");
}
return "true".equalsIgnoreCase(installOps);
}
@ConditionalOnMissingClass("stirling.software.SPDF.config.security.SecurityConfiguration") @ConditionalOnMissingClass("stirling.software.SPDF.config.security.SecurityConfiguration")
@Bean(name = "activSecurity") @Bean(name = "activSecurity")
public boolean missingActivSecurity() { public boolean missingActivSecurity() {

View File

@@ -2,13 +2,13 @@ package stirling.software.SPDF.config;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import stirling.software.SPDF.config.interfaces.ShowAdminInterface; import stirling.software.SPDF.config.interfaces.ShowAdminInterface;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
@Configuration @Service
class AppUpdateService { class AppUpdateService {
private final ApplicationProperties applicationProperties; private final ApplicationProperties applicationProperties;

View File

@@ -1,5 +1,6 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -8,24 +9,30 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
@Service @Service
@Slf4j @Slf4j
@DependsOn({"bookAndHtmlFormatsInstalled"})
public class EndpointConfiguration { public class EndpointConfiguration {
private static final String REMOVE_BLANKS = "remove-blanks"; private static final String REMOVE_BLANKS = "remove-blanks";
private final ApplicationProperties applicationProperties; private final ApplicationProperties applicationProperties;
private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>(); private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>();
private Map<String, Set<String>> endpointGroups = new ConcurrentHashMap<>(); private Map<String, Set<String>> endpointGroups = new ConcurrentHashMap<>();
private boolean bookAndHtmlFormatsInstalled;
@Autowired @Autowired
public EndpointConfiguration(ApplicationProperties applicationProperties) { public EndpointConfiguration(
ApplicationProperties applicationProperties,
@Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled) {
this.applicationProperties = applicationProperties; this.applicationProperties = applicationProperties;
this.bookAndHtmlFormatsInstalled = bookAndHtmlFormatsInstalled;
init(); init();
processEnvironmentConfigs(); processEnvironmentConfigs();
} }
@@ -190,8 +197,8 @@ public class EndpointConfiguration {
addEndpointToGroup("LibreOffice", "pdf-to-html"); addEndpointToGroup("LibreOffice", "pdf-to-html");
addEndpointToGroup("LibreOffice", "pdf-to-xml"); addEndpointToGroup("LibreOffice", "pdf-to-xml");
// Unoconvert // Unoconv
addEndpointToGroup("Unoconvert", "file-to-pdf"); addEndpointToGroup("Unoconv", "file-to-pdf");
// qpdf // qpdf
addEndpointToGroup("qpdf", "compress-pdf"); addEndpointToGroup("qpdf", "compress-pdf");
@@ -265,6 +272,12 @@ public class EndpointConfiguration {
List<String> endpointsToRemove = applicationProperties.getEndpoints().getToRemove(); List<String> endpointsToRemove = applicationProperties.getEndpoints().getToRemove();
List<String> groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove(); List<String> groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove();
if (!bookAndHtmlFormatsInstalled) {
if (groupsToRemove == null) {
groupsToRemove = new ArrayList<>();
}
groupsToRemove.add("Calibre");
}
if (endpointsToRemove != null) { if (endpointsToRemove != null) {
for (String endpoint : endpointsToRemove) { for (String endpoint : endpointsToRemove) {
disableEndpoint(endpoint.trim()); disableEndpoint(endpoint.trim());

View File

@@ -9,7 +9,6 @@ import java.util.stream.Collectors;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@Configuration @Configuration
@@ -17,29 +16,21 @@ import lombok.extern.slf4j.Slf4j;
public class ExternalAppDepConfig { public class ExternalAppDepConfig {
private final EndpointConfiguration endpointConfiguration; private final EndpointConfiguration endpointConfiguration;
private final Map<String, List<String>> commandToGroupMapping =
new HashMap<>() {
private final String weasyprintPath; {
private final String unoconvPath; put("soffice", List.of("LibreOffice"));
private final Map<String, List<String>> commandToGroupMapping; put("weasyprint", List.of("Weasyprint"));
put("pdftohtml", List.of("Pdftohtml"));
put("unoconv", List.of("Unoconv"));
put("qpdf", List.of("qpdf"));
put("tesseract", List.of("tesseract"));
}
};
public ExternalAppDepConfig( public ExternalAppDepConfig(EndpointConfiguration endpointConfiguration) {
EndpointConfiguration endpointConfiguration, RuntimePathConfig runtimePathConfig) {
this.endpointConfiguration = endpointConfiguration; this.endpointConfiguration = endpointConfiguration;
weasyprintPath = runtimePathConfig.getWeasyPrintPath();
unoconvPath = runtimePathConfig.getUnoConvertPath();
commandToGroupMapping =
new HashMap<>() {
{
put("soffice", List.of("LibreOffice"));
put(weasyprintPath, List.of("Weasyprint"));
put("pdftohtml", List.of("Pdftohtml"));
put(unoconvPath, List.of("Unoconvert"));
put("qpdf", List.of("qpdf"));
put("tesseract", List.of("tesseract"));
}
};
} }
private boolean isCommandAvailable(String command) { private boolean isCommandAvailable(String command) {
@@ -110,9 +101,9 @@ public class ExternalAppDepConfig {
checkDependencyAndDisableGroup("tesseract"); checkDependencyAndDisableGroup("tesseract");
checkDependencyAndDisableGroup("soffice"); checkDependencyAndDisableGroup("soffice");
checkDependencyAndDisableGroup("qpdf"); checkDependencyAndDisableGroup("qpdf");
checkDependencyAndDisableGroup(weasyprintPath); checkDependencyAndDisableGroup("weasyprint");
checkDependencyAndDisableGroup("pdftohtml"); checkDependencyAndDisableGroup("pdftohtml");
checkDependencyAndDisableGroup(unoconvPath); checkDependencyAndDisableGroup("unoconv");
// Special handling for Python/OpenCV dependencies // Special handling for Python/OpenCV dependencies
boolean pythonAvailable = isCommandAvailable("python3") || isCommandAvailable("python"); boolean pythonAvailable = isCommandAvailable("python3") || isCommandAvailable("python");
if (!pythonAvailable) { if (!pythonAvailable) {

View File

@@ -13,9 +13,7 @@ import org.springframework.stereotype.Component;
import io.micrometer.common.util.StringUtils; import io.micrometer.common.util.StringUtils;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.GeneralUtils;

View File

@@ -11,6 +11,7 @@ public class InstallationPathConfig {
// Root paths // Root paths
private static final String LOG_PATH; private static final String LOG_PATH;
private static final String CONFIG_PATH; private static final String CONFIG_PATH;
private static final String PIPELINE_PATH;
private static final String CUSTOM_FILES_PATH; private static final String CUSTOM_FILES_PATH;
private static final String CLIENT_WEBUI_PATH; private static final String CLIENT_WEBUI_PATH;
@@ -18,6 +19,11 @@ public class InstallationPathConfig {
private static final String SETTINGS_PATH; private static final String SETTINGS_PATH;
private static final String CUSTOM_SETTINGS_PATH; private static final String CUSTOM_SETTINGS_PATH;
// Pipeline paths
private static final String PIPELINE_WATCHED_FOLDERS_PATH;
private static final String PIPELINE_FINISHED_FOLDERS_PATH;
private static final String PIPELINE_DEFAULT_WEB_UI_CONFIGS;
// Custom file paths // Custom file paths
private static final String STATIC_PATH; private static final String STATIC_PATH;
private static final String TEMPLATES_PATH; private static final String TEMPLATES_PATH;
@@ -29,6 +35,7 @@ public class InstallationPathConfig {
// Initialize root paths // Initialize root paths
LOG_PATH = BASE_PATH + "logs" + File.separator; LOG_PATH = BASE_PATH + "logs" + File.separator;
CONFIG_PATH = BASE_PATH + "configs" + File.separator; CONFIG_PATH = BASE_PATH + "configs" + File.separator;
PIPELINE_PATH = BASE_PATH + "pipeline" + File.separator;
CUSTOM_FILES_PATH = BASE_PATH + "customFiles" + File.separator; CUSTOM_FILES_PATH = BASE_PATH + "customFiles" + File.separator;
CLIENT_WEBUI_PATH = BASE_PATH + "clientWebUI" + File.separator; CLIENT_WEBUI_PATH = BASE_PATH + "clientWebUI" + File.separator;
@@ -36,6 +43,11 @@ public class InstallationPathConfig {
SETTINGS_PATH = CONFIG_PATH + "settings.yml"; SETTINGS_PATH = CONFIG_PATH + "settings.yml";
CUSTOM_SETTINGS_PATH = CONFIG_PATH + "custom_settings.yml"; CUSTOM_SETTINGS_PATH = CONFIG_PATH + "custom_settings.yml";
// Initialize pipeline paths
PIPELINE_WATCHED_FOLDERS_PATH = PIPELINE_PATH + "watchedFolders" + File.separator;
PIPELINE_FINISHED_FOLDERS_PATH = PIPELINE_PATH + "finishedFolders" + File.separator;
PIPELINE_DEFAULT_WEB_UI_CONFIGS = PIPELINE_PATH + "defaultWebUIConfigs" + File.separator;
// Initialize custom file paths // Initialize custom file paths
STATIC_PATH = CUSTOM_FILES_PATH + "static" + File.separator; STATIC_PATH = CUSTOM_FILES_PATH + "static" + File.separator;
TEMPLATES_PATH = CUSTOM_FILES_PATH + "templates" + File.separator; TEMPLATES_PATH = CUSTOM_FILES_PATH + "templates" + File.separator;
@@ -80,6 +92,10 @@ public class InstallationPathConfig {
return CONFIG_PATH; return CONFIG_PATH;
} }
public static String getPipelinePath() {
return PIPELINE_PATH;
}
public static String getCustomFilesPath() { public static String getCustomFilesPath() {
return CUSTOM_FILES_PATH; return CUSTOM_FILES_PATH;
} }
@@ -96,6 +112,18 @@ public class InstallationPathConfig {
return CUSTOM_SETTINGS_PATH; return CUSTOM_SETTINGS_PATH;
} }
public static String getPipelineWatchedFoldersDir() {
return PIPELINE_WATCHED_FOLDERS_PATH;
}
public static String getPipelineFinishedFoldersDir() {
return PIPELINE_FINISHED_FOLDERS_PATH;
}
public static String getPipelineDefaultWebUIConfigsDir() {
return PIPELINE_DEFAULT_WEB_UI_CONFIGS;
}
public static String getStaticPath() { public static String getStaticPath() {
return STATIC_PATH; return STATIC_PATH;
} }

View File

@@ -14,7 +14,6 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import stirling.software.SPDF.utils.RequestUriUtils; import stirling.software.SPDF.utils.RequestUriUtils;
@Component @Component

View File

@@ -7,7 +7,6 @@ import org.springframework.context.annotation.Configuration;
import com.posthog.java.PostHog; import com.posthog.java.PostHog;
import jakarta.annotation.PreDestroy; import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@Configuration @Configuration

View File

@@ -1,84 +0,0 @@
package stirling.software.SPDF.config;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Configuration;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties.CustomPaths.Operations;
import stirling.software.SPDF.model.ApplicationProperties.CustomPaths.Pipeline;
@Slf4j
@Configuration
@Getter
public class RuntimePathConfig {
private final ApplicationProperties properties;
private final String basePath;
private final String weasyPrintPath;
private final String unoConvertPath;
// Pipeline paths
private final String pipelineWatchedFoldersPath;
private final String pipelineFinishedFoldersPath;
private final String pipelineDefaultWebUiConfigs;
private final String pipelinePath;
public RuntimePathConfig(ApplicationProperties properties) {
this.properties = properties;
this.basePath = InstallationPathConfig.getPath();
String pipelinePath = basePath + "pipeline" + File.separator;
String watchedFoldersPath = pipelinePath + "watchedFolders" + File.separator;
String finishedFoldersPath = pipelinePath + "finishedFolders" + File.separator;
String webUiConfigsPath = pipelinePath + "defaultWebUIConfigs" + File.separator;
Pipeline pipeline = properties.getSystem().getCustomPaths().getPipeline();
if (pipeline != null) {
if (!StringUtils.isEmpty(pipeline.getWatchedFoldersDir())) {
watchedFoldersPath = pipeline.getWatchedFoldersDir();
}
if (!StringUtils.isEmpty(pipeline.getFinishedFoldersDir())) {
finishedFoldersPath = pipeline.getFinishedFoldersDir();
}
if (!StringUtils.isEmpty(pipeline.getWebUIConfigsDir())) {
webUiConfigsPath = pipeline.getWebUIConfigsDir();
}
}
this.pipelinePath = pipelinePath;
this.pipelineWatchedFoldersPath = watchedFoldersPath;
this.pipelineFinishedFoldersPath = finishedFoldersPath;
this.pipelineDefaultWebUiConfigs = webUiConfigsPath;
boolean isDocker = isRunningInDocker();
// Initialize Operation paths
String weasyPrintPath = isDocker ? "/opt/venv/bin/weasyprint" : "weasyprint";
String unoConvertPath = isDocker ? "/opt/venv/bin/unoconvert" : "unoconvert";
// Check for custom operation paths
Operations operations = properties.getSystem().getCustomPaths().getOperations();
if (operations != null) {
if (!StringUtils.isEmpty(operations.getWeasyprint())) {
weasyPrintPath = operations.getWeasyprint();
}
if (!StringUtils.isEmpty(operations.getUnoconvert())) {
unoConvertPath = operations.getUnoconvert();
}
}
// Assign operations final fields
this.weasyPrintPath = weasyPrintPath;
this.unoConvertPath = unoConvertPath;
}
private boolean isRunningInDocker() {
return Files.exists(Paths.get("/.dockerenv"));
}
}

View File

@@ -14,9 +14,7 @@ import org.springframework.security.web.authentication.SimpleUrlAuthenticationFa
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.User; import stirling.software.SPDF.model.User;
@Slf4j @Slf4j

View File

@@ -10,9 +10,7 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.utils.RequestUriUtils; import stirling.software.SPDF.utils.RequestUriUtils;
@Slf4j @Slf4j

View File

@@ -18,10 +18,8 @@ import com.coveo.saml.SamlClient;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.SPDFApplication; import stirling.software.SPDF.SPDFApplication;
import stirling.software.SPDF.config.security.saml2.CertificateUtils; import stirling.software.SPDF.config.security.saml2.CertificateUtils;
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal; import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;

View File

@@ -16,9 +16,7 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.User; import stirling.software.SPDF.model.User;
import stirling.software.SPDF.utils.RequestUriUtils; import stirling.software.SPDF.utils.RequestUriUtils;

View File

@@ -6,7 +6,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import jakarta.servlet.*; import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import stirling.software.SPDF.utils.RequestUriUtils; import stirling.software.SPDF.utils.RequestUriUtils;
public class IPRateLimitingFilter implements Filter { public class IPRateLimitingFilter implements Filter {

View File

@@ -6,9 +6,7 @@ import java.util.UUID;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.interfaces.DatabaseInterface; import stirling.software.SPDF.config.interfaces.DatabaseInterface;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.Role; import stirling.software.SPDF.model.Role;

View File

@@ -6,9 +6,7 @@ import java.util.concurrent.TimeUnit;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.AttemptCounter; import stirling.software.SPDF.model.AttemptCounter;

View File

@@ -29,7 +29,6 @@ import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler; import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler; import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService; import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;

View File

@@ -22,9 +22,7 @@ import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal; import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry; import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
import stirling.software.SPDF.model.ApiKeyAuthenticationToken; import stirling.software.SPDF.model.ApiKeyAuthenticationToken;

View File

@@ -23,7 +23,6 @@ import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import stirling.software.SPDF.model.Role; import stirling.software.SPDF.model.Role;
@Component @Component

View File

@@ -21,7 +21,6 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.interfaces.DatabaseInterface; import stirling.software.SPDF.config.interfaces.DatabaseInterface;
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal; import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry; import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
@@ -140,9 +139,6 @@ public class UserService implements UserServiceInterface {
User user = User user =
findByUsernameIgnoreCase(username) findByUsernameIgnoreCase(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found")); .orElseThrow(() -> new UsernameNotFoundException("User not found"));
if (user.getApiKey() == null || user.getApiKey().length() == 0) {
user = addApiKeyToUser(username);
}
return user.getApiKey(); return user.getApiKey();
} }

View File

@@ -11,7 +11,6 @@ import org.springframework.context.annotation.Configuration;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.InstallationPathConfig; import stirling.software.SPDF.config.InstallationPathConfig;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.provider.UnsupportedProviderException; import stirling.software.SPDF.model.provider.UnsupportedProviderException;

View File

@@ -26,7 +26,6 @@ import org.springframework.jdbc.datasource.init.ScriptException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.InstallationPathConfig; import stirling.software.SPDF.config.InstallationPathConfig;
import stirling.software.SPDF.config.interfaces.DatabaseInterface; import stirling.software.SPDF.config.interfaces.DatabaseInterface;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;

View File

@@ -13,7 +13,6 @@ import org.springframework.security.web.authentication.SimpleUrlAuthenticationFa
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j

View File

@@ -14,7 +14,6 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import stirling.software.SPDF.config.security.LoginAttemptService; import stirling.software.SPDF.config.security.LoginAttemptService;
import stirling.software.SPDF.config.security.UserService; import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;

View File

@@ -12,7 +12,6 @@ import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.LoginAttemptService; import stirling.software.SPDF.config.security.LoginAttemptService;
import stirling.software.SPDF.config.security.UserService; import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;

View File

@@ -20,7 +20,6 @@ import org.springframework.security.oauth2.client.registration.InMemoryClientReg
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.UserService; import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2; import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;

View File

@@ -11,7 +11,6 @@ import org.springframework.security.web.authentication.SimpleUrlAuthenticationFa
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j

View File

@@ -12,10 +12,8 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.LoginAttemptService; import stirling.software.SPDF.config.security.LoginAttemptService;
import stirling.software.SPDF.config.security.UserService; import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;

View File

@@ -13,7 +13,6 @@ import org.springframework.security.saml2.provider.service.authentication.OpenSa
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.UserService; import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.model.User; import stirling.software.SPDF.model.User;

View File

@@ -18,9 +18,7 @@ import org.springframework.security.saml2.provider.service.registration.Saml2Mes
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver; import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2; import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;

View File

@@ -5,7 +5,6 @@ import org.springframework.stereotype.Component;
import jakarta.servlet.http.HttpSessionEvent; import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener; import jakarta.servlet.http.HttpSessionListener;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@Component @Component

View File

@@ -11,7 +11,6 @@ import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal; import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
import stirling.software.SPDF.model.SessionEntity; import stirling.software.SPDF.model.SessionEntity;

View File

@@ -10,7 +10,6 @@ import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import stirling.software.SPDF.model.SessionEntity; import stirling.software.SPDF.model.SessionEntity;
@Repository @Repository

View File

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

View File

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

View File

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

View File

@@ -21,7 +21,6 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.SortTypes; import stirling.software.SPDF.model.SortTypes;
import stirling.software.SPDF.model.api.PDFWithPageNums; import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.SPDF.model.api.general.RearrangePagesRequest; import stirling.software.SPDF.model.api.general.RearrangePagesRequest;
@@ -175,38 +174,7 @@ public class RearrangePagesPDFController {
return newPageOrderZeroBased; return newPageOrderZeroBased;
} }
private List<Integer> duplicate(int totalPages, String pageOrder) { private List<Integer> processSortTypes(String sortTypes, int totalPages) {
List<Integer> newPageOrder = new ArrayList<>();
int duplicateCount;
try {
// Parse the duplicate count from pageOrder
duplicateCount =
pageOrder != null && !pageOrder.isEmpty()
? Integer.parseInt(pageOrder.trim())
: 2; // Default to 2 if not specified
} catch (NumberFormatException e) {
log.error("Invalid duplicate count specified", e);
duplicateCount = 2; // Default to 2 if invalid input
}
// Validate duplicate count
if (duplicateCount < 1) {
duplicateCount = 2; // Default to 2 if invalid input
}
// For each page in the document
for (int pageNum = 0; pageNum < totalPages; pageNum++) {
// Add the current page index duplicateCount times
for (int dupCount = 0; dupCount < duplicateCount; dupCount++) {
newPageOrder.add(pageNum);
}
}
return newPageOrder;
}
private List<Integer> processSortTypes(String sortTypes, int totalPages, String pageOrder) {
try { try {
SortTypes mode = SortTypes.valueOf(sortTypes.toUpperCase()); SortTypes mode = SortTypes.valueOf(sortTypes.toUpperCase());
switch (mode) { switch (mode) {
@@ -228,8 +196,6 @@ public class RearrangePagesPDFController {
return removeLast(totalPages); return removeLast(totalPages);
case REMOVE_FIRST_AND_LAST: case REMOVE_FIRST_AND_LAST:
return removeFirstAndLast(totalPages); return removeFirstAndLast(totalPages);
case DUPLICATE:
return duplicate(totalPages, pageOrder);
default: default:
throw new IllegalArgumentException("Unsupported custom mode"); throw new IllegalArgumentException("Unsupported custom mode");
} }
@@ -257,10 +223,8 @@ public class RearrangePagesPDFController {
String[] pageOrderArr = pageOrder != null ? pageOrder.split(",") : new String[0]; String[] pageOrderArr = pageOrder != null ? pageOrder.split(",") : new String[0];
int totalPages = document.getNumberOfPages(); int totalPages = document.getNumberOfPages();
List<Integer> newPageOrder; List<Integer> newPageOrder;
if (sortType != null if (sortType != null && sortType.length() > 0) {
&& sortType.length() > 0 newPageOrder = processSortTypes(sortType, totalPages);
&& !"custom".equals(sortType.toLowerCase())) {
newPageOrder = processSortTypes(sortType, totalPages, pageOrder);
} else { } else {
newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages, false); newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages, false);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,7 +22,6 @@ import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; 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.model.api.GeneralFile;
import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor;
@@ -35,13 +34,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
public class ConvertOfficeController { public class ConvertOfficeController {
private final CustomPDDocumentFactory pdfDocumentFactory; private final CustomPDDocumentFactory pdfDocumentFactory;
private final RuntimePathConfig runtimePathConfig;
@Autowired @Autowired
public ConvertOfficeController( public ConvertOfficeController(CustomPDDocumentFactory pdfDocumentFactory) {
CustomPDDocumentFactory pdfDocumentFactory, RuntimePathConfig runtimePathConfig) {
this.pdfDocumentFactory = pdfDocumentFactory; this.pdfDocumentFactory = pdfDocumentFactory;
this.runtimePathConfig = runtimePathConfig;
} }
public File convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException { public File convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
@@ -65,13 +61,13 @@ public class ConvertOfficeController {
List<String> command = List<String> command =
new ArrayList<>( new ArrayList<>(
Arrays.asList( Arrays.asList(
runtimePathConfig.getUnoConvertPath(), "unoconv",
"--port", "-vvv",
"2003", "-f",
"--convert-to",
"pdf", "pdf",
tempInputFile.toString(), "-o",
tempOutputFile.toString())); tempOutputFile.toString(),
tempInputFile.toString()));
ProcessExecutorResult returnCode = ProcessExecutorResult returnCode =
ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE) ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)
.runCommandWithOutputHandling(command); .runCommandWithOutputHandling(command);

View File

@@ -0,0 +1,95 @@
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,7 +21,6 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.converters.PdfToPdfARequest; import stirling.software.SPDF.model.api.converters.PdfToPdfARequest;
import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;

View File

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

View File

@@ -1,14 +1,7 @@
package stirling.software.SPDF.controller.api.converters; package stirling.software.SPDF.controller.api.converters;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringWriter; import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.QuoteMode; import org.apache.commons.csv.QuoteMode;
@@ -26,20 +19,17 @@ import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j; import stirling.software.SPDF.model.api.extract.PDFFilePage;
import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.SPDF.pdf.FlexibleCSVWriter; import stirling.software.SPDF.pdf.FlexibleCSVWriter;
import technology.tabula.ObjectExtractor; import technology.tabula.ObjectExtractor;
import technology.tabula.Page; import technology.tabula.Page;
import technology.tabula.Table; import technology.tabula.Table;
import technology.tabula.extractors.SpreadsheetExtractionAlgorithm; import technology.tabula.extractors.SpreadsheetExtractionAlgorithm;
import technology.tabula.writers.Writer;
@RestController @RestController
@RequestMapping("/api/v1/convert") @RequestMapping("/api/v1/convert")
@Tag(name = "Convert", description = "Convert APIs") @Tag(name = "Convert", description = "Convert APIs")
@Slf4j
public class ExtractCSVController { public class ExtractCSVController {
@PostMapping(value = "/pdf/csv", consumes = "multipart/form-data") @PostMapping(value = "/pdf/csv", consumes = "multipart/form-data")
@@ -47,83 +37,31 @@ public class ExtractCSVController {
summary = "Extracts a CSV document from a PDF", summary = "Extracts a CSV document from a PDF",
description = description =
"This operation takes an input PDF file and returns CSV file of whole page. Input:PDF Output:CSV Type:SISO") "This operation takes an input PDF file and returns CSV file of whole page. Input:PDF Output:CSV Type:SISO")
public ResponseEntity<?> pdfToCsv(@ModelAttribute PDFWithPageNums form) throws Exception { public ResponseEntity<String> PdfToCsv(@ModelAttribute PDFFilePage form) throws Exception {
String baseName = getBaseName(form.getFileInput().getOriginalFilename()); StringWriter writer = new StringWriter();
List<CsvEntry> csvEntries = new ArrayList<>();
try (PDDocument document = Loader.loadPDF(form.getFileInput().getBytes())) { try (PDDocument document = Loader.loadPDF(form.getFileInput().getBytes())) {
List<Integer> pages = form.getPageNumbersList(document, true);
SpreadsheetExtractionAlgorithm sea = new SpreadsheetExtractionAlgorithm();
CSVFormat format = CSVFormat format =
CSVFormat.EXCEL.builder().setEscape('"').setQuoteMode(QuoteMode.ALL).build(); CSVFormat.EXCEL.builder().setEscape('"').setQuoteMode(QuoteMode.ALL).build();
Writer csvWriter = new FlexibleCSVWriter(format);
for (int pageNum : pages) { SpreadsheetExtractionAlgorithm sea = new SpreadsheetExtractionAlgorithm();
try (ObjectExtractor extractor = new ObjectExtractor(document)) { try (ObjectExtractor extractor = new ObjectExtractor(document)) {
log.info("{}", pageNum); Page page = extractor.extract(form.getPageId());
Page page = extractor.extract(pageNum); List<Table> tables = sea.extract(page);
List<Table> tables = sea.extract(page); csvWriter.write(writer, tables);
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)));
String entryName = generateEntryName(baseName, pageNum, i + 1);
csvEntries.add(new CsvEntry(entryName, sw.toString()));
}
}
}
if (csvEntries.isEmpty()) {
return ResponseEntity.noContent().build();
} else if (csvEntries.size() == 1) {
return createCsvResponse(csvEntries.get(0), baseName);
} else {
return createZipResponse(csvEntries, baseName);
}
}
}
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) {
ZipEntry zipEntry = new ZipEntry(entry.filename());
zipOut.putNextEntry(zipEntry);
zipOut.write(entry.content().getBytes(StandardCharsets.UTF_8));
zipOut.closeEntry();
} }
} }
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
headers.setContentDisposition( headers.setContentDisposition(
ContentDisposition.builder("attachment") ContentDisposition.builder("attachment")
.filename(baseName + "_extracted.zip") .filename(
.build()); form.getFileInput()
headers.setContentType(MediaType.parseMediaType("application/zip")); .getOriginalFilename()
.replaceFirst("[.][^.]+$", "")
return ResponseEntity.ok().headers(headers).body(baos.toByteArray()); + "_extracted.csv")
}
private ResponseEntity<String> createCsvResponse(CsvEntry entry, String baseName) {
HttpHeaders headers = new HttpHeaders();
headers.setContentDisposition(
ContentDisposition.builder("attachment")
.filename(baseName + "_extracted.csv")
.build()); .build());
headers.setContentType(MediaType.parseMediaType("text/csv")); headers.setContentType(MediaType.parseMediaType("text/csv"));
return ResponseEntity.ok().headers(headers).body(entry.content()); return ResponseEntity.ok().headers(headers).body(writer.toString());
} }
private String generateEntryName(String baseName, int pageNum, int tableIndex) {
return String.format("%s_p%d_t%d.csv", baseName, pageNum, tableIndex);
}
private String getBaseName(String filename) {
return filename.replaceFirst("[.][^.]+$", "");
}
private record CsvEntry(String filename, String content) {}
} }

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,6 @@ import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -31,7 +30,6 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.OptimizePdfRequest; import stirling.software.SPDF.model.api.misc.OptimizePdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.GeneralUtils;
@@ -52,8 +50,7 @@ public class CompressController {
this.pdfDocumentFactory = pdfDocumentFactory; this.pdfDocumentFactory = pdfDocumentFactory;
} }
private void compressImagesInPDF(Path pdfFile, double initialScaleFactor, boolean grayScale) private void compressImagesInPDF(Path pdfFile, double initialScaleFactor) throws Exception {
throws Exception {
byte[] fileBytes = Files.readAllBytes(pdfFile); byte[] fileBytes = Files.readAllBytes(pdfFile);
try (PDDocument doc = Loader.loadPDF(fileBytes)) { try (PDDocument doc = Loader.loadPDF(fileBytes)) {
double scaleFactor = initialScaleFactor; double scaleFactor = initialScaleFactor;
@@ -78,23 +75,11 @@ public class CompressController {
bufferedImage.getScaledInstance( bufferedImage.getScaledInstance(
newWidth, newHeight, Image.SCALE_SMOOTH); newWidth, newHeight, Image.SCALE_SMOOTH);
BufferedImage scaledBufferedImage; BufferedImage scaledBufferedImage =
if (grayScale new BufferedImage(
|| bufferedImage.getType() == BufferedImage.TYPE_BYTE_GRAY) { newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
scaledBufferedImage = scaledBufferedImage.getGraphics().drawImage(scaledImage, 0, 0, null);
new BufferedImage(
newWidth, newHeight, BufferedImage.TYPE_BYTE_GRAY);
scaledBufferedImage
.getGraphics()
.drawImage(scaledImage, 0, 0, null);
} else {
scaledBufferedImage =
new BufferedImage(
newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
scaledBufferedImage
.getGraphics()
.drawImage(scaledImage, 0, 0, null);
}
ByteArrayOutputStream compressedImageStream = ByteArrayOutputStream compressedImageStream =
new ByteArrayOutputStream(); new ByteArrayOutputStream();
ImageIO.write(scaledBufferedImage, "jpeg", compressedImageStream); ImageIO.write(scaledBufferedImage, "jpeg", compressedImageStream);
@@ -109,9 +94,7 @@ public class CompressController {
} }
} }
} }
Path tempOutput = Files.createTempFile("output_", ".pdf"); doc.save(pdfFile.toString());
doc.save(tempOutput.toString());
Files.move(tempOutput, pdfFile, StandardCopyOption.REPLACE_EXISTING);
} }
} }
@@ -153,7 +136,6 @@ public class CompressController {
} }
boolean sizeMet = false; boolean sizeMet = false;
boolean grayscaleEnabled = Boolean.TRUE.equals(request.getGrayscale());
while (!sizeMet && optimizeLevel <= 9) { while (!sizeMet && optimizeLevel <= 9) {
// Apply additional image compression for levels 6-9 // Apply additional image compression for levels 6-9
@@ -167,7 +149,7 @@ public class CompressController {
case 9 -> 0.5; // 60% of original size case 9 -> 0.5; // 60% of original size
default -> 1.0; default -> 1.0;
}; };
compressImagesInPDF(tempInputFile, scaleFactor, grayscaleEnabled); compressImagesInPDF(tempInputFile, scaleFactor);
} }
// Run QPDF optimization // Run QPDF optimization
@@ -184,7 +166,6 @@ public class CompressController {
command.add("--compression-level=" + optimizeLevel); command.add("--compression-level=" + optimizeLevel);
command.add("--compress-streams=y"); command.add("--compress-streams=y");
command.add("--object-streams=generate"); command.add("--object-streams=generate");
command.add("--no-warn");
command.add(tempInputFile.toString()); command.add(tempInputFile.toString());
command.add(tempOutputFile.toString()); command.add(tempOutputFile.toString());
@@ -207,7 +188,7 @@ public class CompressController {
optimizeLevel = optimizeLevel =
incrementOptimizeLevel( incrementOptimizeLevel(
optimizeLevel, outputFileSize, expectedOutputSize); optimizeLevel, outputFileSize, expectedOutputSize);
if (autoMode && optimizeLevel >= 9) { if (autoMode && optimizeLevel > 9) {
log.info("Maximum compression level reached in auto mode"); log.info("Maximum compression level reached in auto mode");
sizeMet = true; sizeMet = true;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,14 +5,9 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystemException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; 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.LocalDate;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
@@ -29,8 +24,7 @@ import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j; 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.PipelineConfig;
import stirling.software.SPDF.model.PipelineOperation; import stirling.software.SPDF.model.PipelineOperation;
import stirling.software.SPDF.model.PipelineResult; import stirling.software.SPDF.model.PipelineResult;
@@ -56,19 +50,18 @@ public class PipelineDirectoryProcessor {
ObjectMapper objectMapper, ObjectMapper objectMapper,
ApiDocService apiDocService, ApiDocService apiDocService,
PipelineProcessor processor, PipelineProcessor processor,
FileMonitor fileMonitor, FileMonitor fileMonitor) {
RuntimePathConfig runtimePathConfig) {
this.objectMapper = objectMapper; this.objectMapper = objectMapper;
this.apiDocService = apiDocService; this.apiDocService = apiDocService;
this.watchedFoldersDir = runtimePathConfig.getPipelineWatchedFoldersPath(); this.watchedFoldersDir = InstallationPathConfig.getPipelineWatchedFoldersDir();
this.finishedFoldersDir = runtimePathConfig.getPipelineFinishedFoldersPath(); this.finishedFoldersDir = InstallationPathConfig.getPipelineFinishedFoldersDir();
this.processor = processor; this.processor = processor;
this.fileMonitor = fileMonitor; this.fileMonitor = fileMonitor;
} }
@Scheduled(fixedRate = 60000) @Scheduled(fixedRate = 60000)
public void scanFolders() { public void scanFolders() {
Path watchedFolderPath = Paths.get(watchedFoldersDir).toAbsolutePath(); Path watchedFolderPath = Paths.get(watchedFoldersDir);
if (!Files.exists(watchedFolderPath)) { if (!Files.exists(watchedFolderPath)) {
try { try {
Files.createDirectories(watchedFolderPath); Files.createDirectories(watchedFolderPath);
@@ -78,33 +71,19 @@ public class PipelineDirectoryProcessor {
return; return;
} }
} }
try (Stream<Path> paths = Files.walk(watchedFolderPath)) {
try { paths.filter(Files::isDirectory)
Files.walkFileTree( .forEach(
watchedFolderPath, t -> {
new SimpleFileVisitor<>() { try {
@Override if (!t.equals(watchedFolderPath) && !t.endsWith("processing")) {
public FileVisitResult preVisitDirectory( handleDirectory(t);
Path dir, BasicFileAttributes attrs) { }
try { } catch (Exception e) {
// Skip root directory and "processing" subdirectories log.error("Error handling directory: {}", t, e);
if (!dir.equals(watchedFolderPath) && !dir.endsWith("processing")) {
handleDirectory(dir);
} }
} catch (Exception e) { });
log.error("Error handling directory: {}", dir, e); } catch (Exception 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); log.error("Error walking through directory: {}", watchedFolderPath, e);
} }
} }
@@ -208,7 +187,6 @@ public class PipelineDirectoryProcessor {
} }
return isAllowed; return isAllowed;
}) })
.map(Path::toAbsolutePath)
.filter( .filter(
path -> { path -> {
boolean isReady = boolean isReady =
@@ -222,10 +200,7 @@ public class PipelineDirectoryProcessor {
}) })
.map(Path::toFile) .map(Path::toFile)
.toArray(File[]::new); .toArray(File[]::new);
log.info( log.info("Collected {} files for processing", files.length);
"Collected {} files for processing for {}",
files.length,
dir.toAbsolutePath().toString());
return files; return files;
} }
} }
@@ -235,35 +210,8 @@ public class PipelineDirectoryProcessor {
List<File> filesToProcess = new ArrayList<>(); List<File> filesToProcess = new ArrayList<>();
for (File file : files) { for (File file : files) {
Path targetPath = resolveUniqueFilePath(processingDir, file.getName()); Path targetPath = resolveUniqueFilePath(processingDir, file.getName());
Files.move(file.toPath(), targetPath);
// Retry with exponential backoff filesToProcess.add(targetPath.toFile());
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; return filesToProcess;
} }

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More