# Changelog ## [0.4.0] - 2026-09-22 ### Added - `[ocr].timeout` ist jetzt wirksam: der Wert wird als `tesseract_timeout` (Sekunden pro Seite) an ocrmypdf durchgereicht. Bisher war der Key zwar dokumentiert, wurde aber nirgends gelesen. - `check_output_config()` validiert zusätzlich `[output].name_mode`. Ein Tippfehler führt jetzt beim Start zum Abbruch mit Exit-Code 2, statt erst pro Datei zuzuschlagen — und zwar bisher **nach** dem Verschieben nach `working/`, wo die Datei dann liegen blieb. - Neue Exception `ConfigError` in `pdf_ocr_hotfolder.config` — fehlende `[paths]`-Sektion oder ein fehlender Pfad-Eintrag liefern eine deutsche Fehlermeldung mit Datei- und Key-Nennung statt eines nackten `KeyError`-Tracebacks. Die CLI bricht damit sauber mit Exit-Code 2 ab. - `pytest.ini` mit `testpaths = tests`, damit `pytest` aus dem Repo-Root läuft. - 35 neue Tests: Fehlerzählung (Exception, Upload, Stabilitäts-Timeout), Config-Fehlermeldungen, `tesseract_timeout`-Durchreichung (ocrmypdf gemockt) und `upload_folder()`. ### Changed - **`[ocr].timeout` hat eine neue Bedeutung — für bestehende Installationen relevant!** Der Wert ist kein (nie implementiertes) Gesamt-Timeout pro PDF mehr, sondern das Limit **pro Seite** für Tesseract. Der Default sinkt entsprechend von `1800` auf `300`. Wer den alten Wert `1800` in seiner `config.toml` stehen hat, gibt Tesseract damit 30 Minuten **je Seite** — bitte auf einen Seiten-Wert anpassen (Richtwert 300). `0` bedeutet "kein eigenes Limit": der Wert wird dann gar nicht erst durchgereicht, weil ocrmypdf `tesseract_timeout=0` als "OCR komplett überspringen" interpretiert. - `_dispatch_uploads()` liefert jetzt die Namen der fehlgeschlagenen Upload-Ziele zurück; die doppelte `enabled`-Prüfung (Service + Uploader) ist entfallen — die Uploader prüfen das selbst. - `upload_folder()` kopiert mit `shutil.copyfile()` statt `read_bytes()`/`write_bytes()` — große PDFs landen nicht mehr komplett im Speicher. Die Selbst-Ziel-Erkennung bleibt unverändert. ### Fixed - `OcrConfig.pdfa_level` hatte im Code noch den Default `"2"`, obwohl `config.example.toml` seit 0.2.2 bewusst `""` setzt (Ghostscript-Bug, Issue #3). Eine Config ohne `[ocr]`-Sektion bzw. ohne den Key lief damit ungewollt in PDF/A. Default im Code jetzt ebenfalls `""`. - Eine Exception **nach** dem OCR (z.B. ein fehlgeschlagener `shutil.move()` nach `outgoing/`) wurde nur im Worker-Callback geloggt. `error_count` blieb 0 und `--once` lieferte trotz Fehlschlag Exit-Code 0. Jede Exception aus `process_pdf()` zählt jetzt als Fehler, wird geloggt, löst eine Fehler-Mail aus und die Datei wandert — soweit noch auffindbar (`incoming/` oder `working/`) — nach `error/`. - Fehlgeschlagene Uploads waren folgenlos: die Rückgabewerte der Uploader wurden verworfen, es ging sogar eine Erfolgs-Mail raus. Jetzt zählt mindestens ein fehlgeschlagenes Ziel als Fehler und die E-Mail geht als **FEHLER** raus, mit Nennung der betroffenen Ziele. Das OCR-PDF bleibt bewusst in `outgoing/` liegen (das OCR selbst war ja erfolgreich) — das steht so auch im Log. - Lief der Stabilitäts-Check einer Datei in den 60-Sekunden-Timeout, gab es nur ein `log.warning`; `--once` meldete Exit-Code 0. Jetzt `log.error` + `error_count`. Die Datei bleibt bewusst in `incoming/` liegen und wird beim nächsten Lauf erneut versucht. Eine zwischenzeitlich *verschwundene* Datei wird davon unterschieden und zählt weiterhin nicht als Fehler. ## [0.3.1] - 2026-04-10 ### Fixed - **Issue #4**: LXC/Container-Kompatibilität — systemd-Hardening (`PrivateTmp`, `ProtectSystem`, etc.) verursacht Error 226/NAMESPACE in LXC-Containern. Installer erkennt Container-Umgebung automatisch und bietet ein Drop-in an. Zusätzlich liegt `systemd/lxc-compat.conf` als Vorlage im Repo. - **Issue #5**: `WorkingDirectory=/opt/pdf-ocr-hotfolder` in der systemd Template-Unit ergänzt — ohne diesen Eintrag konnte das Python-Modul nicht gefunden werden. - **Issue #6**: Auf Debian 12 bietet der Installer bei betroffenen Ghostscript-Versionen (10.0.0–10.02.0) jetzt automatisch an, bookworm-backports zu aktivieren und GS zu upgraden (statt nur zu warnen). ## [0.3.0] - 2026-04-09 ### Added - Neue Config-Sektion `[output]` mit: - `name_mode` — Platzierung des Tags im Dateinamen: `"prefix"`, `"suffix"` (vor Extension), `"none"` - `name_tag` — verbatim einzufügender String, z.B. `"OCR_"` oder `"_OCR"` - `original_on_success` — `"delete"` (alter Default) oder `"archive"` - `archive_dir` — Zielverzeichnis für `"archive"`, mit Kollisions-Schutz (Timestamp-Suffix) - Runtime-Validierung der Output-Config in `check_output_config()` - 20 neue Tests für `build_output_name()`, `check_output_config()` und `process_pdf()` mit allen Kombinationen aus Modus + Original-Behandlung ### Changed - `process_pdf()` nimmt jetzt `output_cfg: OutputConfig` als Pflicht-Argument ## [0.2.2] - 2026-04-09 ### Fixed - **Issue #3**: Ghostscript 10.0.0–10.02.0 (Debian 12 default) zerschießen OCR mit PDF/A + `skip_text=true`. - `config.example.toml`: `pdfa_level = ""` als sicherer Default - Runtime-Preflight: Prüft `gs --version` wenn `pdfa_level` gesetzt ist, bricht mit klarer Fehlermeldung ab - `install.sh`: warnt bei betroffenen GS-Versionen mit Upgrade-Hinweis auf bookworm-backports ### Added - `is_ghostscript_broken()` / `detect_ghostscript_version()` in `pdf_ocr_hotfolder.service` - 19 weitere pytest-Tests für GS-Versions-Detection (parametrisiert) und Preflight-Kombinationen ## [0.2.1] - 2026-04-09 ### Fixed - **Issue #1**: Preflight-Check beim Start prüft jetzt `tesseract` und `gs` (Ghostscript). Fehlt eine Abhängigkeit, beendet sich der Service sofort mit Exit-Code 2 und klarer Fehlermeldung statt erst bei der ersten Datei. - **Issue #2**: `--once`-Modus liefert jetzt Exit-Code `1`, sobald **mindestens ein** PDF fehlgeschlagen ist. Exit-Code `0` nur bei vollständigem Erfolg (inkl. "keine Dateien vorhanden"). Exit-Code `2` bei Preflight-Fehler. ### Added - Public API: `HotfolderService.run_once()`, `.success_count`, `.error_count`, `.ensure_dirs()` - `check_preflight()` / `PreflightError` in `pdf_ocr_hotfolder.service` - pytest-Test-Suite (`tests/`) mit 11 Tests — deckt alle Szenarien aus Issue #1 und #2 ab - `ocrmypdf`-Import in `processor.py` ist jetzt lazy (Tests ohne ocrmypdf-Installation möglich) ## [0.2.0] - 2026-04-08 ### Added - **Multi-Instanz-Support** via systemd Template-Unit `pdf-ocr-hotfolder@.service` - Pro Instanz: eigene Config (`/etc/pdf-ocr-hotfolder/.toml`), eigene Datenverzeichnisse (`/var/lib/pdf-ocr-hotfolder//…`), optional eigener Service-User via Drop-in - **Instanz-Manager in `install.sh`**: erkennt bestehende Instanzen bei Re-Run, fragt nach weiteren, listet Namen + Status - `update.sh` stoppt/startet automatisch **alle** laufenden Instanzen ### Changed - Single-Unit `pdf-ocr-hotfolder.service` durch Template-Unit `pdf-ocr-hotfolder@.service` ersetzt - Installer fragt nicht mehr einmalig nach Service-User, sondern **pro Instanz** ### Removed - Alte Single-Config unter `/etc/pdf-ocr-hotfolder/config.toml` — wird nicht mehr erzeugt ## [0.1.0] - 2026-04-08 ### Added - Initiale Version (Komplettes Rewrite des alten Bash-Tools `pdf-tool`) - Python-Implementation auf Basis von `ocrmypdf` (Library, kein Subprozess) - Hotfolder-Watcher mit `watchdog` (created/moved/closed Events) - File-Stability-Check (wartet bis Scanner fertig geschrieben hat) - ThreadPool für parallele PDF-Verarbeitung (`max_workers`) - Upload-Targets: lokaler Ordner, Nextcloud (WebDAV via `requests`), SFTP (`paramiko`) - E-Mail-Notify (`smtplib`, immer / nur Fehler / nie) - Optional veraPDF-Validierung - TOML-Konfiguration (`tomllib` aus stdlib, Python ≥3.11) - systemd-Unit mit Hardening-Optionen - `install.sh` mit interaktivem Service-User-Prompt (lokal anlegen oder bestehenden lokalen/AD-User übernehmen) - `update.sh` mit Backup, Code-Sync und Service-Reload - README.md, AI_AGENT_BRIEFING.md