2062476252
- Sprach-Abfrage pro Instanz (Default-Vorschlag deu+eng), bewusst instanz-lokal: ein Hotfolder kann mit "deu" laufen, ein anderer mit "deu+eng+fra". Hinweis im Prompt, dass jede zusaetzliche Sprache Laufzeit und Erkennungsqualitaet kostet. - Jeder Sprachcode wird gegen "tesseract --list-langs" geprueft, fehlende Pakete (tesseract-ocr-<code>) werden zur Installation angeboten; lehnt der User ab oder scheitert apt, wird gewarnt und erneut gefragt. - Abfrage "Original archivieren?" mit $BASE/archive als Default; Archiv ausserhalb von $BASE wird eigens angelegt und gechownt. Ein Pfad auf incoming/outgoing/working/error wird abgewiesen. - sed-Kette der Config-Erzeugung jetzt verankert (^key =) und escaped, setzt zusaetzlich languages, original_on_success und archive_dir; die erzeugte Config wird gegen die Eingabe nachgeprueft. - README und Briefing um "Sprachen pro Instanz" ergaenzt Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
213 lines
12 KiB
Markdown
213 lines
12 KiB
Markdown
# Changelog
|
||
|
||
## [0.5.0] - 2026-09-22
|
||
|
||
### Added
|
||
- Der Installer weist einen Archiv-Pfad ab, der auf `incoming/`, `outgoing/`,
|
||
`working/` oder `error/` der Instanz zeigt — im Eingang wuerde das Original
|
||
sonst endlos neu aufgegriffen.
|
||
- **`install.sh` fragt beim Anlegen einer Instanz die OCR-Sprachen ab**
|
||
(`Tesseract-Sprachen [deu+eng]:`). Die Wahl gilt bewusst **pro Instanz** —
|
||
ein Hotfolder `buchhaltung` kann mit `deu` laufen, ein Hotfolder `export` mit
|
||
`deu+eng+fra`. Der Installer weist vorher darauf hin, dass jede zusaetzliche
|
||
Sprache Laufzeit **und** Erkennungsqualitaet kostet, die Liste also eng
|
||
gehalten werden sollte. Das Eingabeformat wird geprueft (Sprachcodes mit `+`
|
||
verbunden, `chi_sim` & Co. erlaubt); bei Unsinn wird erneut gefragt statt
|
||
abzubrechen.
|
||
- **Sprachpakete werden nachinstalliert.** Jeder eingegebene Code wird gegen
|
||
`tesseract --list-langs` geprueft. Fehlt eine Sprachdatei, bietet der
|
||
Installer das passende apt-Paket an (`tesseract-ocr-<code>`, Unterstrich wird
|
||
zum Bindestrich: `chi_sim` → `tesseract-ocr-chi-sim`). Lehnt der User ab oder
|
||
laesst sich das Paket nicht installieren, warnt der Installer, dass OCR mit
|
||
dieser Sprache **bei jeder Datei** scheitern wuerde, und fragt die Sprachen
|
||
erneut ab — so kann die Sprache einfach wieder rausgeworfen werden. Ist
|
||
`tesseract` nicht aufrufbar, wird die Pruefung uebersprungen und die Eingabe
|
||
unveraendert uebernommen.
|
||
- **Abfrage `Original nach erfolgreichem OCR archivieren? [j/N]:`** — Default
|
||
nein, also weiterhin `original_on_success = "delete"`. Bei ja wird der
|
||
Archiv-Pfad abgefragt (Vorschlag `<basis>/archive`), angelegt und auf den
|
||
Service-User gechownt; ein Archiv ausserhalb des Instanz-Basis-Pfads bekommt
|
||
ein eigenes `chown -R`.
|
||
|
||
### Changed
|
||
- Die Instanz-Config wird weiterhin per `sed` aus `config.example.toml`
|
||
erzeugt, substituiert jetzt aber zusaetzlich `[ocr].languages`,
|
||
`[output].original_on_success` und `[output].archive_dir` — bisher waren das
|
||
die Beispiel-Defaults, `archive_dir` musste von Hand nachgetragen werden.
|
||
Die Ausdruecke sind am Zeilenanfang verankert (`^key[[:space:]]*=`), damit die
|
||
deutschen Kommentarzeilen ueber den Keys unangetastet bleiben, und
|
||
Pfad-Variablen laufen durch `sed_escape_repl()` (maskiert `\`, `&`, `|`) —
|
||
Pfade mit Sonderzeichen landen damit korrekt in der Config.
|
||
- Nach dem sed-Lauf liest der Installer die drei Keys aus der erzeugten Config
|
||
zurueck und vergleicht sie mit der Eingabe. Erst wenn das passt, nennt die
|
||
Abschluss-Zusammenfassung zusaetzlich die gewaehlten **Sprachen** und (bei
|
||
Archivierung) das **Archiv-Verzeichnis**; sonst gibt es eine Warnung.
|
||
|
||
## [0.4.1] - 2026-09-22
|
||
|
||
### Fixed
|
||
- **veraPDF-FAIL hat das Original immer gelöscht.** Schlug die PDF/A-Validierung
|
||
fehl, wanderte das OCR-Ergebnis nach `error/` und das Original wurde per
|
||
`unlink()` entfernt — unabhängig von `[output].original_on_success`. Wer
|
||
`archive` konfiguriert hatte, verlor die Datei also ausgerechnet im
|
||
Fehlerfall. Der FAIL-Pfad nutzt jetzt dieselbe `_dispose_original()`-Logik
|
||
wie der Erfolgsfall: `archive` legt das Original samt
|
||
Timestamp-Kollisionsschutz im `archive_dir` ab, `delete` verhält sich wie
|
||
bisher. Die Log-Meldung nennt jetzt beides — wohin das OCR-Ergebnis ging und
|
||
was mit dem Original passiert ist.
|
||
|
||
### Removed
|
||
- Das nie benutzte Logverzeichnis `/var/log/pdf-ocr-hotfolder/` wird nicht mehr
|
||
vom Installer angelegt und ist aus README und Briefing entfernt. Es hat nie
|
||
ein Logfile enthalten: `_setup_logging()` nutzt `logging.basicConfig()` ohne
|
||
FileHandler, der Dienst loggt nach stdout → journald. **journald ist damit die
|
||
einzige Log-Quelle** (`journalctl -u pdf-ocr-hotfolder@<instanz> -f`).
|
||
Weder Installer noch Updater fassen das Verzeichnis an: ein vorhandenes,
|
||
leeres `/var/log/pdf-ocr-hotfolder/` kann auf bestehenden Installationen
|
||
gefahrlos von Hand entfernt werden (`sudo rmdir /var/log/pdf-ocr-hotfolder`).
|
||
|
||
### Added
|
||
- 3 neue Tests für den veraPDF-FAIL-Pfad (`delete`, `archive`,
|
||
Archiv-Namenskollision); veraPDF wird dabei gemockt. Suite jetzt 95 Tests.
|
||
|
||
## [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@<name>.service`
|
||
- Pro Instanz: eigene Config (`/etc/pdf-ocr-hotfolder/<name>.toml`), eigene Datenverzeichnisse (`/var/lib/pdf-ocr-hotfolder/<name>/…`), 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
|