Compare commits

..
17 Commits
Author SHA1 Message Date
Terranom674 8023f7b5d7 Bump version to 9.7.1.2 2026-08-20 19:47:45 +02:00
Terranom674 8a5435ea6b Keep WebDAV mapping readable by Piwigo runtime 2026-08-20 19:47:23 +02:00
Terranom674 880ff2a2b1 Fix WebDAV state permissions for runtime preview access 2026-08-20 19:46:56 +02:00
Terranom674 062d38ab08 Bump version to 9.7.1.1 2026-08-20 19:40:51 +02:00
Terranom674 10065ece46 Make WebDAV mapping lookup robust by webdav_path 2026-08-20 19:40:29 +02:00
Terranom674 bccc5cafda Set current status rebuild baseline to 9.7.1.0 2026-08-20 19:33:28 +02:00
Terranom674 2c52228265 Set README rebuild baseline to 9.7.1.0 2026-08-20 19:33:07 +02:00
Terranom674 7277a8b287 Start connector rebuild at version 9.7.1.0 2026-08-20 19:32:38 +02:00
Terranom674 9155a52290 Refresh README for preview-backed WebDAV derivatives 2026-08-20 19:30:34 +02:00
Terranom674 afa6d02c46 Update current status for preview-backed derivatives 2026-08-20 19:30:02 +02:00
Terranom674 9c94953267 Bump version to 0.9.6.8.25 2026-08-20 19:29:24 +02:00
Terranom674 66d62321d0 Use Nextcloud previews for small WebDAV derivatives 2026-08-20 19:28:55 +02:00
Terranom674 cb9a4b7d2a Expose WebDAV file metadata to materialize runtime 2026-08-20 19:27:48 +02:00
Terranom674 388351c356 Bump version to 0.9.6.8.24 2026-08-20 19:20:32 +02:00
Terranom674 ea71b54c94 Avoid HTTP self-request for WebDAV derivative generation 2026-08-20 19:20:06 +02:00
Terranom674 99edfc84d8 Bump version to 0.9.6.8.23 2026-08-20 19:06:30 +02:00
Terranom674 b224cd40f6 Fix Nextcloud image dimension XML parsing 2026-08-20 19:05:54 +02:00
8 changed files with 364 additions and 95 deletions

No files matched your search

+25 -19
View File
@@ -2,7 +2,7 @@
## Version ## Version
**Bratonien Tools 0.9.6.8.19** **Bratonien Tools 9.7.1.0**
Zielplattform der produktiven NC-Connector-Synchronisierung ist derzeit **Piwigo 16.4.0**. Zielplattform der produktiven NC-Connector-Synchronisierung ist derzeit **Piwigo 16.4.0**.
@@ -40,10 +40,12 @@ Die verbindungsspezifischen Runtime-Konfigurationen liegen unter `/etc/bratonien
Der Reconcile- und Shell-Pfad unterstützt den leeren WebDAV-Root ausdrücklich und gibt ihn als `--root ""` an den Platzhalter-Builder weiter. Der Reconcile- und Shell-Pfad unterstützt den leeren WebDAV-Root ausdrücklich und gibt ihn als `--root ""` an den Platzhalter-Builder weiter.
### Platzhalterquelle und Shadow Tree ### Platzhalterquelle, Metadaten und Shadow Tree
`runtime/lib/build_webdav_placeholder_source.py` bildet die Nextcloud-Inhalte als lokale Platzhalterquelle ab. Für Bilder existieren physische 1×1-Platzhalterdateien; die Originale werden dabei nicht dauerhaft heruntergeladen. `runtime/lib/build_webdav_placeholder_source.py` bildet die Nextcloud-Inhalte als lokale Platzhalterquelle ab. Für Bilder existieren physische 1×1-Platzhalterdateien; die Originale werden dabei nicht dauerhaft heruntergeladen.
Der WebDAV-Scan übernimmt zusätzlich die von Nextcloud gelieferten Bildmaße aus `nc:metadata-photos-size`. `width`, `height` und `fileid` werden im `webdav-map.json` geführt. Nach dem normalen Piwigo-Dateisync überträgt `runtime/lib/sync-webdav-metadata.php` die Originalmaße in `piwigo_images`, damit Piwigo seine Derivatgrößen und Lazy-Loading-Geometrie auf Basis der echten Bildabmessungen berechnet.
`runtime/lib/shadow_tree.py` erzeugt atomar die Dateisystemstruktur, die Piwigo als physische Site sieht. Shadow-Verzeichnisse werden so angelegt, dass der Webserver die für die On-Demand-Materialisierung nötigen Dateisystemoperationen ausführen kann. `runtime/lib/shadow_tree.py` erzeugt atomar die Dateisystemstruktur, die Piwigo als physische Site sieht. Shadow-Verzeichnisse werden so angelegt, dass der Webserver die für die On-Demand-Materialisierung nötigen Dateisystemoperationen ausführen kann.
Jede WebDAV-Verbindung besitzt ihre eigene Piwigo-Site. Es gibt keinen zusätzlichen Sync auf Site 1. Jede WebDAV-Verbindung besitzt ihre eigene Piwigo-Site. Es gibt keinen zusätzlichen Sync auf Site 1.
@@ -56,23 +58,26 @@ Danach wird `bratonien.nc.syncOrphans` für die konkrete WebDAV-Site ausgeführt
Der Shadow Tree stellt Piwigo ausschließlich die physische Album-/Bildstruktur bereit. Albumdatensätze, Bilddatensätze, Bild-Kategorie-Verknüpfungen und Piwigo-Derivate bleiben Aufgabe von Piwigo. Der Shadow Tree stellt Piwigo ausschließlich die physische Album-/Bildstruktur bereit. Albumdatensätze, Bilddatensätze, Bild-Kategorie-Verknüpfungen und Piwigo-Derivate bleiben Aufgabe von Piwigo.
### On-Demand-Materialisierung vor `i.php` ### On-Demand-Materialisierung und Derivaterzeugung
Die produktive WebDAV-Derivaterzeugung sitzt vor Piwigos `i.php`. `include/webdav_materialize_runtime.inc.php` löst aus dem Piwigo-Bildpfad die konkrete Verbindung, den ausgewählten Nextcloud-Root, den relativen WebDAV-Pfad sowie die Mapping-Metadaten `fileid`, `width`, `height`, MIME-Typ, Größe und ETag auf.
`include/webdav_materialize_runtime.inc.php` löst aus dem Piwigo-Bildpfad die konkrete Verbindung, den ausgewählten Nextcloud-Root und den relativen WebDAV-Pfad auf.
Für ein noch nicht vorhandenes Derivat läuft `webdav-derivative.php`: Für ein noch nicht vorhandenes Derivat läuft `webdav-derivative.php`:
1. Zugriffsprüfung für das Piwigo-Bild; 1. Zugriffsprüfung für das Piwigo-Bild;
2. Download des Originals aus Nextcloud in eine temporäre Datei; 2. Auswahl einer temporären Nextcloud-Quelle;
3. temporäres Bereitstellen des Originals am exakten von Piwigo erwarteten Quellpfad; 3. temporäres Bereitstellen dieser Quelle am exakten von Piwigo erwarteten Quellpfad;
4. Aufruf des nativen Piwigo-`i.php`; 4. Erzeugung des normalen Piwigo-Derivats mit Piwigos eigener `pwg_image`-/Derivative-Logik im selben PHP-Prozess;
5. Piwigo erzeugt sein normales Derivat; 5. Wiederherstellung des Shadow-/Platzhalterzustands;
6. Wiederherstellung des Shadow-/Platzhalterzustands; 6. Auslieferung des Piwigo-Derivats.
7. Auslieferung des Piwigo-Derivats.
Das Original verbleibt in Nextcloud. Es gibt keine dauerhafte vollständige Originalkopie in Piwigo. Ein interner HTTP-Self-Request auf `i.php` wird nicht mehr verwendet. Damit wird kein zusätzlicher PHP-FPM-Worker pro Derivaterzeugung benötigt.
Für die erste Preview-Stufe verwenden ausschließlich `custom:s9999x250` und `standard:square` den authentifizierten Nextcloud-Endpunkt `/core/preview` anhand der Nextcloud-`fileid`. Die Preview wird mit etwa doppelter benötigter Piwigo-Auflösung und erhaltenem Seitenverhältnis angefordert. Die Berechnung berücksichtigt das Original-Seitenverhältnis, damit auch Crop-Derivate genügend Pixel erhalten. Varianten mit Bildrotation werden in dieser Stufe nicht über Preview erzeugt.
Ist keine geeignete Preview planbar oder schlägt der Preview-Abruf fehl, fällt der Gate automatisch auf den vollständigen WebDAV-Originaldownload zurück. Große/originale Anforderungen verwenden weiterhin die Originalquelle.
Die temporäre Preview bzw. das temporäre Original wird nach der Derivaterzeugung entfernt. Das fertige Derivat verbleibt ausschließlich in Piwigos normalem `_data/i`-Cache. Der Connector führt keinen parallelen permanenten Derivat-Cache.
### Orphan-Logik ### Orphan-Logik
@@ -104,13 +109,13 @@ Eine WebDAV-Verbindung kann unabhängig von ihrer Reihenfolge gelöscht werden.
### Diagnose ### Diagnose
Der WebDAV-Derivative-Gate schreibt Request-bezogene `[BRAT-WD ...]`-Diagnoseeinträge. Der Verbindungsstatus wird pro Verbindung geführt. Runtime-Probleme lassen sich zusätzlich über `bratonien-nc-connector.service` nachvollziehen. Der WebDAV-Derivative-Gate schreibt Request-bezogene `[BRAT-WD ...]`-Diagnoseeinträge. Für neue Derivate zeigen `preview_start`, `preview_failed`, `download_done mode=preview|original`, `generate_start` und `generate_done` den verwendeten Quellpfad und die Laufzeiten. Der Verbindungsstatus wird pro Verbindung geführt. Runtime-Probleme lassen sich zusätzlich über `bratonien-nc-connector.service` nachvollziehen.
## Aktuell relevante Dateien ## Aktuell relevante Dateien
- `main.inc.php` – Plugin-Hooks, Connector-Sync-Hilfen, Dashboard-Prefilter - `main.inc.php` – Plugin-Hooks, Connector-Sync-Hilfen, Dashboard-Prefilter
- `include/webdav_materialize_runtime.inc.php` – WebDAV-Quellauflösung und Materialisierung - `include/webdav_materialize_runtime.inc.php` – WebDAV-Quellauflösung einschließlich `fileid` und Originalmaßen
- `webdav-derivative.php` – On-Demand-Gate vor Piwigos `i.php` - `webdav-derivative.php` – On-Demand-Gate, Nextcloud-Preview/Original-Fallback und Piwigo-Derivaterzeugung
- `webdav-image.php` – direkte WebDAV-Originalauslieferung - `webdav-image.php` – direkte WebDAV-Originalauslieferung
- `include/nc_productive_ws.inc.php` – direkter Piwigo-Core-Sync für 16.4.0 - `include/nc_productive_ws.inc.php` – direkter Piwigo-Core-Sync für 16.4.0
- `include/nc_orphan_ws.inc.php` – Root-Orphan-Synchronisierung - `include/nc_orphan_ws.inc.php` – Root-Orphan-Synchronisierung
@@ -119,15 +124,16 @@ Der WebDAV-Derivative-Gate schreibt Request-bezogene `[BRAT-WD ...]`-Diagnoseein
- `runtime/cleanup-webdav-piwigo.php` – Bereinigung verwaister Piwigo-Sites - `runtime/cleanup-webdav-piwigo.php` – Bereinigung verwaister Piwigo-Sites
- `runtime/sync-webdav.sh` – verbindungsspezifischer Sync - `runtime/sync-webdav.sh` – verbindungsspezifischer Sync
- `runtime/run-all.sh` – gemeinsamer Runner - `runtime/run-all.sh` – gemeinsamer Runner
- `runtime/lib/build_webdav_placeholder_source.py` – WebDAV-Scan und Platzhalterquelle - `runtime/lib/build_webdav_placeholder_source.py` – WebDAV-Scan, Bildmetadaten und Platzhalterquelle
- `runtime/lib/sync-webdav-metadata.php` – Übernahme der Originalmaße nach Piwigo
- `runtime/lib/shadow_tree.py` – atomarer Shadow Tree - `runtime/lib/shadow_tree.py` – atomarer Shadow Tree
- `runtime/lib/piwigo-sync.php` – Piwigo-API/Fallback-Sync - `runtime/lib/piwigo-sync.php` – Piwigo-API/Fallback-Sync
## Nicht mehr das produktive WebDAV-Modell ## Nicht mehr das produktive WebDAV-Modell
Nicht mehr maßgeblich für die produktive WebDAV-Derivaterzeugung ist das frühere Modell, bei dem Bild-URLs einfach auf einen separaten WebDAV-Bildstream umgebogen wurden oder der Connector selbst einen parallelen vollständigen Derivatbestand verwalten sollte. Nicht mehr maßgeblich sind das frühere Modell eines separaten WebDAV-Bildstreams als Derivatquelle, ein paralleler vollständiger Connector-Derivatbestand oder ein interner HTTP-Aufruf von `webdav-derivative.php` zurück auf Piwigos `i.php`.
Maßgeblich ist: **physischer Platzhalter + On-Demand-Original + natives Piwigo-`i.php` + Wiederherstellung des Platzhalters**. Maßgeblich ist: **physischer Platzhalter + passende temporäre Nextcloud-Preview bzw. Original-Fallback + Piwigos Bildverarbeitung im selben PHP-Prozess + normales Piwigo-Derivat + Wiederherstellung des Platzhalters**.
## Prüfpflicht vor Merge ## Prüfpflicht vor Merge
+17 -15
View File
@@ -2,7 +2,7 @@
Modulares Piwigo-Plugin für Administration, Bildverarbeitung, geschützte Freigaben, Fotoauswahl und die Anbindung von Nextcloud an Piwigo. Modulares Piwigo-Plugin für Administration, Bildverarbeitung, geschützte Freigaben, Fotoauswahl und die Anbindung von Nextcloud an Piwigo.
Aktuelle Plugin-Version: **0.9.6.8.19** Aktuelle Plugin-Version: **9.7.1.0**
## Grundprinzip ## Grundprinzip
@@ -64,27 +64,27 @@ Nicht benötigt werden Nextcloud-Datenbankzugriff, `occ`-Adminzugriff, Storage-I
1. Der Assistent prüft Nextcloud und liest die sichtbaren Verzeichnisse per WebDAV. 1. Der Assistent prüft Nextcloud und liest die sichtbaren Verzeichnisse per WebDAV.
2. Ausgewählte Verzeichnisse werden rekursiv per PROPFIND eingelesen. 2. Ausgewählte Verzeichnisse werden rekursiv per PROPFIND eingelesen.
3. Ordner, Dateiname, Nextcloud-Datei-ID, MIME-Typ, Größe, ETag und WebDAV-Pfad werden erfasst. 3. Ordner, Dateiname, Nextcloud-Datei-ID, MIME-Typ, Größe, ETag, Originalabmessungen und WebDAV-Pfad werden erfasst.
4. `runtime/lib/build_webdav_placeholder_source.py` erzeugt eine lokale Platzhalterquelle ohne dauerhafte Originalkopie. 4. `runtime/lib/build_webdav_placeholder_source.py` erzeugt eine lokale Platzhalterquelle ohne dauerhafte Originalkopie.
5. `runtime/lib/shadow_tree.py` baut atomar den verbindungseigenen Shadow Tree. 5. `runtime/lib/shadow_tree.py` baut atomar den verbindungseigenen Shadow Tree.
6. Der WebDAV-Galeriebaum liegt unter `_data/bratonien-tools/nc-webdav-gallery/connection-ID`. 6. Der WebDAV-Galeriebaum liegt unter `_data/bratonien-tools/nc-webdav-gallery/connection-ID`.
7. Jede WebDAV-Verbindung wird als eigene physische Piwigo-Site registriert. 7. Jede WebDAV-Verbindung wird als eigene physische Piwigo-Site registriert.
8. Piwigos Dateisynchronisierung erhält ausschließlich diese verbindungseigene Site. 8. Piwigos Dateisynchronisierung erhält ausschließlich diese verbindungseigene Site.
9. `runtime/lib/sync-webdav-metadata.php` übernimmt die von Nextcloud gelieferten Originalmaße in Piwigos Bilddatensätze.
### On-Demand-Bildauslieferung und Piwigo-Derivate ### On-Demand-Bildauslieferung und Piwigo-Derivate
Die produktive Bildauslieferung sitzt vor Piwigos `i.php`. `include/webdav_materialize_runtime.inc.php` erkennt anhand des Piwigo-Bildpfads die zugehörige WebDAV-Verbindung, den Nextcloud-Root und den relativen WebDAV-Pfad. Aus dem Mapping stehen zusätzlich `fileid`, Originalbreite/-höhe, MIME-Typ, Dateigröße und ETag zur Verfügung. Der Hook `get_derivative_url` leitet fehlende WebDAV-Derivate an `webdav-derivative.php` um.
`include/webdav_materialize_runtime.inc.php` erkennt anhand des Piwigo-Bildpfads die zugehörige WebDAV-Verbindung, den Nextcloud-Root und den relativen WebDAV-Pfad. Der Hook `get_derivative_url` leitet fehlende WebDAV-Derivate an `webdav-derivative.php` um.
Wenn ein benötigtes Piwigo-Derivat noch nicht existiert: Wenn ein benötigtes Piwigo-Derivat noch nicht existiert:
1. `webdav-derivative.php` prüft den Piwigo-Zugriff auf das Bild. 1. `webdav-derivative.php` prüft den Piwigo-Zugriff auf das Bild.
2. Das Original wird temporär direkt aus Nextcloud geladen. 2. Für `custom:s9999x250` und `standard:square` wird bevorzugt eine passende authentifizierte Nextcloud-Preview anhand der `fileid` geladen. Die Preview wird mit ungefähr doppelter benötigter Piwigo-Auflösung und erhaltenem Seitenverhältnis angefordert.
3. Die physische 1×1-Platzhalterdatei bzw. der Shadow-Link am von Piwigo erwarteten Quellpfad wird für die Dauer der Erzeugung ersetzt. 3. Ist keine passende Preview planbar oder schlägt der Preview-Abruf fehl, wird automatisch das vollständige Original per WebDAV geladen. Große bzw. originale Anforderungen verwenden weiterhin die Originalquelle.
4. Piwigos eigenes `i.php` erzeugt das normale Piwigo-Derivat. 4. Die temporäre Bildquelle ersetzt für die Dauer der Erzeugung die physische 1×1-Platzhalterdatei bzw. den Shadow-Link am von Piwigo erwarteten Quellpfad.
5. Danach wird der temporäre Originalzustand entfernt und der Platzhalter-/Shadow-Zustand wiederhergestellt. 5. Piwigos eigene `pwg_image`-/Derivative-Logik erzeugt das normale Piwigo-Derivat direkt im selben PHP-Prozess. Ein interner HTTP-Self-Request auf `i.php` wird nicht verwendet.
6. Das von Piwigo erzeugte Derivat wird ausgeliefert und künftig normal aus Piwigos Derivat-Cache verwendet. 6. Danach wird die temporäre Bildquelle entfernt und der Platzhalter-/Shadow-Zustand wiederhergestellt.
7. Das fertige Derivat wird ausgeliefert und künftig normal aus Piwigos `_data/i`-Derivat-Cache verwendet.
Damit bleibt Piwigo für Größen, Derivate und Lazy-Loading verantwortlich. Der Connector hält keinen parallelen eigenen vollständigen Derivat-Cache vor. Damit bleibt Piwigo für Größen, Derivate und Lazy-Loading verantwortlich. Der Connector hält keinen parallelen eigenen vollständigen Derivat-Cache vor.
@@ -133,7 +133,7 @@ Der gemeinsame Lauf besteht aktuell aus:
3. `runtime/cleanup-webdav-piwigo.php` – verwaiste WebDAV-Piwigo-Daten bereinigen; 3. `runtime/cleanup-webdav-piwigo.php` – verwaiste WebDAV-Piwigo-Daten bereinigen;
4. `runtime/sync-webdav.sh` – jede vorhandene WebDAV-Verbindung synchronisieren. 4. `runtime/sync-webdav.sh` – jede vorhandene WebDAV-Verbindung synchronisieren.
Der verbindungsspezifische Lauf erzeugt Platzhalterquelle und Shadow Tree, führt Piwigos Sync und Orphan-Sync aus und enthält weiterhin die vorhandenen Preview-/Derivative-Schritte. Die produktive Auslieferung fehlender WebDAV-Derivate erfolgt jedoch über den On-Demand-Gate vor `i.php`. Der verbindungsspezifische Lauf erzeugt Platzhalterquelle und Shadow Tree, führt Piwigos Sync und Orphan-Sync aus und überträgt anschließend die Originalabmessungen aus dem WebDAV-Mapping in Piwigo. Eine vollständige Vorab-Erzeugung von Connector-Derivaten findet nicht statt; fehlende Derivate werden On-Demand erzeugt.
## Bildcache ## Bildcache
@@ -192,16 +192,17 @@ Der integrierte Updater liest den Zielstand aus GitHub, bindet ein Update an ein
- `include/nc_connector_delete_safe.inc.php` – Löschen einschließlich WebDAV-Piwigo-Inhalten - `include/nc_connector_delete_safe.inc.php` – Löschen einschließlich WebDAV-Piwigo-Inhalten
- `include/nc_productive_ws.inc.php` – direkter Piwigo-Core-Dateisync für Piwigo 16.4.0 - `include/nc_productive_ws.inc.php` – direkter Piwigo-Core-Dateisync für Piwigo 16.4.0
- `include/nc_orphan_ws.inc.php` – Orphan-Synchronisierung der konkreten WebDAV-Site - `include/nc_orphan_ws.inc.php` – Orphan-Synchronisierung der konkreten WebDAV-Site
- `include/webdav_materialize_runtime.inc.php` – WebDAV-Quellauflösung und On-Demand-Materialisierung - `include/webdav_materialize_runtime.inc.php` – WebDAV-Quellauflösung und On-Demand-Materialisierung einschließlich `fileid` und Originalmaßen
- `include/webdav_image_runtime.inc.php` – vorhandene WebDAV-Bildzuordnung/Originalauslieferung - `include/webdav_image_runtime.inc.php` – vorhandene WebDAV-Bildzuordnung/Originalauslieferung
- `webdav-derivative.php` – Gate vor `i.php` für fehlende WebDAV-Derivate - `webdav-derivative.php` – On-Demand-Gate, Nextcloud-Preview/Original-Fallback und Piwigo-Derivaterzeugung
- `webdav-image.php` – serverseitige WebDAV-Originalauslieferung - `webdav-image.php` – serverseitige WebDAV-Originalauslieferung
- `runtime/reconcile-webdav.php` – WebDAV-Runtime-Reconcile, einschließlich gültigem leerem Root - `runtime/reconcile-webdav.php` – WebDAV-Runtime-Reconcile, einschließlich gültigem leerem Root
- `runtime/repair-webdav-orphans.php` – Reparatur vorhandener Orphan-Zustände - `runtime/repair-webdav-orphans.php` – Reparatur vorhandener Orphan-Zustände
- `runtime/cleanup-webdav-piwigo.php` – Bereinigung verwaister WebDAV-Piwigo-Sites - `runtime/cleanup-webdav-piwigo.php` – Bereinigung verwaister WebDAV-Piwigo-Sites
- `runtime/sync-webdav.sh` – Ablauf einer WebDAV-Verbindung - `runtime/sync-webdav.sh` – Ablauf einer WebDAV-Verbindung
- `runtime/run-all.sh` – gemeinsamer WebDAV-Runner - `runtime/run-all.sh` – gemeinsamer WebDAV-Runner
- `runtime/lib/build_webdav_placeholder_source.py` – rekursiver WebDAV-Scan und physische Platzhalterquelle - `runtime/lib/build_webdav_placeholder_source.py` – rekursiver WebDAV-Scan, Bildmetadaten und physische Platzhalterquelle
- `runtime/lib/sync-webdav-metadata.php` – Übernahme der Originalabmessungen in Piwigo
- `runtime/lib/shadow_tree.py` – atomarer, Piwigo-kompatibler Shadow Tree - `runtime/lib/shadow_tree.py` – atomarer, Piwigo-kompatibler Shadow Tree
- `runtime/lib/piwigo-sync.php` – Piwigo-Sync mit API/Fallback - `runtime/lib/piwigo-sync.php` – Piwigo-Sync mit API/Fallback
- `main-cache-build.php` – allgemeiner Piwigo-Derivat-/Cache-Builder - `main-cache-build.php` – allgemeiner Piwigo-Derivat-/Cache-Builder
@@ -222,5 +223,6 @@ Der integrierte Updater liest den Zielstand aus GitHub, bindet ein Update an ein
- produktive Piwigo-API ist versionsgebunden; - produktive Piwigo-API ist versionsgebunden;
- Originalbilder werden vom Connector nicht gelöscht; - Originalbilder werden vom Connector nicht gelöscht;
- WebDAV-Originalbilder werden nur bei Bedarf temporär lokal materialisiert; - WebDAV-Originalbilder werden nur bei Bedarf temporär lokal materialisiert;
- temporäre Originale werden nach der Piwigo-Derivaterzeugung wieder entfernt; - kleine Derivate können stattdessen eine temporäre authentifizierte Nextcloud-Preview verwenden;
- temporäre Preview-/Originalquellen werden nach der Piwigo-Derivaterzeugung wieder entfernt;
- Update-Pakete werden an Commit und Hash gebunden. - Update-Pakete werden an Commit und Hash gebunden.
+21 -1
View File
@@ -75,6 +75,9 @@ function bratonien_tools_webdav_materialize_source_info($image_id)
$content_type = ''; $content_type = '';
$size = 0; $size = 0;
$etag = ''; $etag = '';
$fileid = 0;
$width = 0;
$height = 0;
$state_dir = rtrim((string)($config['state_dir'] ?? ''), '/'); $state_dir = rtrim((string)($config['state_dir'] ?? ''), '/');
if ($state_dir !== '') if ($state_dir !== '')
{ {
@@ -84,7 +87,18 @@ function bratonien_tools_webdav_materialize_source_info($image_id)
$mapping = json_decode((string)file_get_contents($mapping_file), true); $mapping = json_decode((string)file_get_contents($mapping_file), true);
if (is_array($mapping) && isset($mapping['files']) && is_array($mapping['files'])) if (is_array($mapping) && isset($mapping['files']) && is_array($mapping['files']))
{ {
$entry = $mapping['files'][$resolved] ?? $mapping['files'][$normalized] ?? null; $files = $mapping['files'];
$entry = $files[$resolved] ?? $files[$normalized] ?? null;
if (!is_array($entry))
{
foreach ($files as $candidate)
{
if (!is_array($candidate) || (string)($candidate['kind'] ?? '') !== 'file') continue;
if (trim((string)($candidate['webdav_path'] ?? ''), '/') !== $webdav_path) continue;
$entry = $candidate;
break;
}
}
if (is_array($entry) && (string)($entry['kind'] ?? '') === 'file') if (is_array($entry) && (string)($entry['kind'] ?? '') === 'file')
{ {
$mapped_path = trim((string)($entry['webdav_path'] ?? ''), '/'); $mapped_path = trim((string)($entry['webdav_path'] ?? ''), '/');
@@ -92,6 +106,9 @@ function bratonien_tools_webdav_materialize_source_info($image_id)
$content_type = (string)($entry['content_type'] ?? ''); $content_type = (string)($entry['content_type'] ?? '');
$size = (int)($entry['size'] ?? 0); $size = (int)($entry['size'] ?? 0);
$etag = (string)($entry['etag'] ?? ''); $etag = (string)($entry['etag'] ?? '');
$fileid = (int)($entry['fileid'] ?? 0);
$width = (int)($entry['width'] ?? 0);
$height = (int)($entry['height'] ?? 0);
} }
} }
} }
@@ -106,6 +123,9 @@ function bratonien_tools_webdav_materialize_source_info($image_id)
'content_type'=>$content_type, 'content_type'=>$content_type,
'size'=>$size, 'size'=>$size,
'etag'=>$etag, 'etag'=>$etag,
'fileid'=>$fileid,
'width'=>$width,
'height'=>$height,
'coi'=>$row['coi'] ?? null, 'coi'=>$row['coi'] ?? null,
); );
} }
+1 -1
View File
@@ -1,7 +1,7 @@
<?php <?php
/* /*
Plugin Name: Bratonien Tools Plugin Name: Bratonien Tools
Version: 0.9.6.8.22 Version: 9.7.1.2
Description: Erweiterbare Administrationswerkzeuge fuer die Bratonien-Piwigo-Installation. Description: Erweiterbare Administrationswerkzeuge fuer die Bratonien-Piwigo-Installation.
Plugin URI: https://github.com/Terranom674/Piwigo_Bratonien_Tools Plugin URI: https://github.com/Terranom674/Piwigo_Bratonien_Tools
Author: Bratonien Author: Bratonien
+24 -2
View File
@@ -56,8 +56,30 @@ def safe_local_name(name: str) -> str:
def parse_image_dimensions(prop: ET.Element) -> tuple[int, int]: def parse_image_dimensions(prop: ET.Element) -> tuple[int, int]:
for property_name in ("file-metadata-size", "metadata-photos-size"): for property_name in ("metadata-photos-size", "file-metadata-size"):
raw = prop.findtext(f"{{{NC}}}{property_name}", default="").strip() element = prop.find(f"{{{NC}}}{property_name}")
if element is None:
continue
width = 0
height = 0
for child in list(element):
local_name = child.tag.rsplit("}", 1)[-1]
text = (child.text or "").strip()
if not text:
continue
try:
value = int(text)
except ValueError:
continue
if local_name == "width":
width = value
elif local_name == "height":
height = value
if width > 0 and height > 0:
return width, height
raw = (element.text or "").strip()
if not raw: if not raw:
continue continue
try: try:
+13 -3
View File
@@ -20,6 +20,7 @@ function decrypt_webdav_credentials($blob, $hexKey)
$iv = base64_decode((string)($payload['iv'] ?? ''), true); $iv = base64_decode((string)($payload['iv'] ?? ''), true);
$tag = base64_decode((string)($payload['tag'] ?? ''), true); $tag = base64_decode((string)($payload['tag'] ?? ''), true);
$cipher = base64_decode((string)($payload['data'] ?? ''), true); $cipher = base64_decode((string)($payload['data'] ?? ''), true);
if (!is_string($iv) || !is_string($tag) || !is_string($cipher)) fail_webdav_reconcile('Connector-Zugangsdaten sind unvollstaendig.');
$plain = openssl_decrypt($cipher, 'aes-256-gcm', hex2bin($hexKey), OPENSSL_RAW_DATA, $iv, $tag); $plain = openssl_decrypt($cipher, 'aes-256-gcm', hex2bin($hexKey), OPENSSL_RAW_DATA, $iv, $tag);
if ($plain === false || $plain === '') fail_webdav_reconcile('Connector-Zugangsdaten konnten nicht entschluesselt werden.'); if ($plain === false || $plain === '') fail_webdav_reconcile('Connector-Zugangsdaten konnten nicht entschluesselt werden.');
$decoded = json_decode($plain, true); $decoded = json_decode($plain, true);
@@ -75,11 +76,19 @@ function webdav_source_fingerprint($baseUrl, $user, array $roots)
return hash('sha256', strtolower(rtrim((string)$baseUrl, '/'))."\n".strtolower(trim((string)$user))."\n".json_encode($normalized)); return hash('sha256', strtolower(rtrim((string)$baseUrl, '/'))."\n".strtolower(trim((string)$user))."\n".json_encode($normalized));
} }
function webdav_set_runtime_dir_permissions($dir)
{
if (!is_dir($dir)) return false;
if (!@chgrp($dir, 'www-data')) return false;
return @chmod($dir, 0750);
}
$pluginRoot = dirname(__DIR__); $pluginRoot = dirname(__DIR__);
$piwigoRoot = dirname($pluginRoot, 2); $piwigoRoot = dirname($pluginRoot, 2);
$dbConfig = $piwigoRoot.'/local/config/database.inc.php'; $dbConfig = $piwigoRoot.'/local/config/database.inc.php';
$configDir = '/etc/bratonien-tools/nc-connector'; $configDir = '/etc/bratonien-tools/nc-connector';
$stateRoot = '/var/lib/bratonien-tools/nc-connector'; $stateBase = '/var/lib/bratonien-tools';
$stateRoot = $stateBase.'/nc-connector';
$publicSourceRoot = rtrim($piwigoRoot, '/').'/_data/bratonien-tools/nc-webdav-source'; $publicSourceRoot = rtrim($piwigoRoot, '/').'/_data/bratonien-tools/nc-webdav-source';
$publicGalleryRoot = rtrim($piwigoRoot, '/').'/_data/bratonien-tools/nc-webdav-gallery'; $publicGalleryRoot = rtrim($piwigoRoot, '/').'/_data/bratonien-tools/nc-webdav-gallery';
@@ -111,7 +120,8 @@ try
if (!is_dir($dir) && !mkdir($dir, $dir === $configDir ? 0700 : 0750, true)) fail_webdav_reconcile('Runtime-Verzeichnis konnte nicht angelegt werden: '.$dir); if (!is_dir($dir) && !mkdir($dir, $dir === $configDir ? 0700 : 0750, true)) fail_webdav_reconcile('Runtime-Verzeichnis konnte nicht angelegt werden: '.$dir);
} }
@chmod($configDir, 0700); @chmod($configDir, 0700);
@chmod($stateRoot, 0750); if (!webdav_set_runtime_dir_permissions($stateBase)) fail_webdav_reconcile('Runtime-State-Basis konnte nicht fuer den Webserver freigegeben werden.');
if (!webdav_set_runtime_dir_permissions($stateRoot)) fail_webdav_reconcile('Runtime-State-Verzeichnis konnte nicht fuer den Webserver freigegeben werden.');
@chmod($publicSourceRoot, 0755); @chmod($publicSourceRoot, 0755);
@chmod($publicGalleryRoot, 0755); @chmod($publicGalleryRoot, 0755);
@@ -151,7 +161,7 @@ try
$stateDir = rtrim((string)($config['state_dir'] ?? ''), '/'); $stateDir = rtrim((string)($config['state_dir'] ?? ''), '/');
if ($stateDir === '') $stateDir = $stateRoot.'/connection-'.$id; if ($stateDir === '') $stateDir = $stateRoot.'/connection-'.$id;
if (!is_dir($stateDir) && !mkdir($stateDir, 0750, true)) fail_webdav_reconcile('State-Verzeichnis konnte nicht angelegt werden.'); if (!is_dir($stateDir) && !mkdir($stateDir, 0750, true)) fail_webdav_reconcile('State-Verzeichnis konnte nicht angelegt werden.');
@chmod($stateDir, 0750); if (!webdav_set_runtime_dir_permissions($stateDir)) fail_webdav_reconcile('State-Verzeichnis konnte nicht fuer den Webserver freigegeben werden.');
$galleryRoot = rtrim((string)($config['parallel_gallery_root'] ?? ''), '/'); $galleryRoot = rtrim((string)($config['parallel_gallery_root'] ?? ''), '/');
$expectedGalleryRoot = $publicGalleryRoot.'/connection-'.$id; $expectedGalleryRoot = $publicGalleryRoot.'/connection-'.$id;
+5
View File
@@ -28,6 +28,8 @@ source "$CONFIG_FILE"
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
LOCK_FILE="$STATE_DIR/webdav-sync.lock" LOCK_FILE="$STATE_DIR/webdav-sync.lock"
mkdir -p -- "$STATE_DIR" mkdir -p -- "$STATE_DIR"
chgrp www-data -- "$STATE_DIR"
chmod 0750 -- "$STATE_DIR"
exec 9>"$LOCK_FILE" exec 9>"$LOCK_FILE"
flock -n 9 || exit 0 flock -n 9 || exit 0
@@ -92,6 +94,9 @@ python3 "$SCRIPT_DIR/lib/build_webdav_placeholder_source.py" \
--manifest "$MANIFEST" \ --manifest "$MANIFEST" \
--mapping "$WEBDAV_MAPPING_FILE" --mapping "$WEBDAV_MAPPING_FILE"
chgrp www-data -- "$WEBDAV_MAPPING_FILE"
chmod 0640 -- "$WEBDAV_MAPPING_FILE"
python3 "$SCRIPT_DIR/lib/shadow_tree.py" \ python3 "$SCRIPT_DIR/lib/shadow_tree.py" \
--manifest "$MANIFEST" \ --manifest "$MANIFEST" \
--destination "$GALLERY_ROOT" \ --destination "$GALLERY_ROOT" \
+258 -54
View File
@@ -55,38 +55,44 @@ function bratonien_tools_webdav_derivative_quote_path($path)
return implode('/', array_map('rawurlencode', $parts)); return implode('/', array_map('rawurlencode', $parts));
} }
function bratonien_tools_webdav_derivative_download(array $source, $destination, &$detail=null) function bratonien_tools_webdav_derivative_connection(array $source, &$detail=null)
{ {
$detail = ''; $detail = '';
$table = $GLOBALS['prefixeTable'].'bratonien_tools_nc_connections'; $table = $GLOBALS['prefixeTable'].'bratonien_tools_nc_connections';
$result = pwg_query('SELECT config_json, secret_blob FROM `'.$table.'` WHERE id='.(int)$source['connection_id'].' LIMIT 1'); $result = pwg_query('SELECT config_json, secret_blob FROM `'.$table.'` WHERE id='.(int)$source['connection_id'].' LIMIT 1');
if (!pwg_db_num_rows($result)) { $detail = 'WebDAV-Verbindung nicht gefunden.'; return false; } if (!pwg_db_num_rows($result)) { $detail = 'WebDAV-Verbindung nicht gefunden.'; return null; }
$row = pwg_db_fetch_assoc($result); $row = pwg_db_fetch_assoc($result);
$config = json_decode((string)$row['config_json'], true); $config = json_decode((string)$row['config_json'], true);
if (!is_array($config)) { $detail = 'WebDAV-Konfiguration ist ungueltig.'; return false; } if (!is_array($config)) { $detail = 'WebDAV-Konfiguration ist ungueltig.'; return null; }
$key_result = pwg_query("SELECT value FROM ".$GLOBALS['prefixeTable']."config WHERE param='bratonien_nc_connector_secret' LIMIT 1"); $key_result = pwg_query("SELECT value FROM ".$GLOBALS['prefixeTable']."config WHERE param='bratonien_nc_connector_secret' LIMIT 1");
if (!pwg_db_num_rows($key_result)) { $detail = 'Connector-Schluessel fehlt.'; return false; } if (!pwg_db_num_rows($key_result)) { $detail = 'Connector-Schluessel fehlt.'; return null; }
$key_row = pwg_db_fetch_assoc($key_result); $key_row = pwg_db_fetch_assoc($key_result);
$credentials = bratonien_tools_webdav_derivative_decrypt_secret((string)$row['secret_blob'], (string)$key_row['value']); $credentials = bratonien_tools_webdav_derivative_decrypt_secret((string)$row['secret_blob'], (string)$key_row['value']);
if (!is_array($credentials)) { $detail = 'WebDAV-Zugangsdaten konnten nicht gelesen werden.'; return false; } if (!is_array($credentials)) { $detail = 'WebDAV-Zugangsdaten konnten nicht gelesen werden.'; return null; }
$base_url = rtrim((string)($config['nextcloud_url'] ?? ''), '/'); $base_url = rtrim((string)($config['nextcloud_url'] ?? ''), '/');
$user = trim((string)($credentials['nextcloud_user'] ?? '')); $user = trim((string)($credentials['nextcloud_user'] ?? ''));
$password = (string)($credentials['nextcloud_password'] ?? ''); $password = (string)($credentials['nextcloud_password'] ?? '');
$webdav_path = trim((string)($source['webdav_path'] ?? ''), '/'); if ($base_url === '' || $user === '' || $password === '') { $detail = 'WebDAV-Verbindung ist unvollstaendig.'; return null; }
if ($base_url === '' || $user === '' || $password === '' || $webdav_path === '') { $detail = 'WebDAV-Bildquelle ist unvollstaendig.'; return false; }
return array('base_url'=>$base_url, 'user'=>$user, 'password'=>$password);
}
function bratonien_tools_webdav_derivative_download_url($url, array $connection, $destination, &$detail=null)
{
$detail = '';
$fp = @fopen($destination, 'xb'); $fp = @fopen($destination, 'xb');
if (!$fp) { $detail = 'Temporaere Quelldatei konnte nicht angelegt werden.'; return false; } if (!$fp) { $detail = 'Temporaere Quelldatei konnte nicht angelegt werden.'; return false; }
$url = $base_url.'/remote.php/dav/files/'.rawurlencode($user).'/'.bratonien_tools_webdav_derivative_quote_path($webdav_path);
$ch = curl_init($url); $ch = curl_init($url);
curl_setopt_array($ch, array( curl_setopt_array($ch, array(
CURLOPT_FOLLOWLOCATION => false, CURLOPT_FOLLOWLOCATION => false,
CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 180, CURLOPT_TIMEOUT => 180,
CURLOPT_HTTPAUTH => CURLAUTH_BASIC, CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
CURLOPT_USERPWD => $user.':'.$password, CURLOPT_USERPWD => $connection['user'].':'.$connection['password'],
CURLOPT_RETURNTRANSFER => false, CURLOPT_RETURNTRANSFER => false,
CURLOPT_FAILONERROR => false, CURLOPT_FAILONERROR => false,
CURLOPT_FILE => $fp, CURLOPT_FILE => $fp,
@@ -98,6 +104,7 @@ function bratonien_tools_webdav_derivative_download(array $source, $destination,
$http = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); $http = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch); curl_close($ch);
fclose($fp); fclose($fp);
if ($ok === false || $errno !== 0 || $http < 200 || $http >= 300) if ($ok === false || $errno !== 0 || $http < 200 || $http >= 300)
{ {
@unlink($destination); @unlink($destination);
@@ -113,40 +120,209 @@ function bratonien_tools_webdav_derivative_download(array $source, $destination,
return true; return true;
} }
function bratonien_tools_webdav_derivative_inner_url($derivative_path) function bratonien_tools_webdav_derivative_download(array $source, array $connection, $destination, &$detail=null)
{ {
$derivative_root = PHPWG_ROOT_PATH.PWG_DERIVATIVE_DIR; $webdav_path = trim((string)($source['webdav_path'] ?? ''), '/');
if (strpos($derivative_path, $derivative_root) !== 0) return null; if ($webdav_path === '') { $detail = 'WebDAV-Bildquelle ist unvollstaendig.'; return false; }
$location = ltrim(substr($derivative_path, strlen($derivative_root)), '/');
if ($location === '') return null; $url = $connection['base_url'].'/remote.php/dav/files/'.rawurlencode($connection['user']).'/'.bratonien_tools_webdav_derivative_quote_path($webdav_path);
$segments = array_map('rawurlencode', explode('/', $location)); return bratonien_tools_webdav_derivative_download_url($url, $connection, $destination, $detail);
return rtrim(get_absolute_root_url(), '/').'/i.php?/'.implode('/', $segments);
} }
function bratonien_tools_webdav_derivative_call_i($url, &$status, &$detail=null) function bratonien_tools_webdav_derivative_preview_plan($variant, array $derivative_size, array $source, array $image_row)
{ {
$detail = ''; if ($variant !== 'custom:s9999x250' && $variant !== 'standard:square') return null;
$status = 500; if ((int)($source['fileid'] ?? 0) < 1) return null;
$ch = curl_init($url); if ((int)($source['width'] ?? 0) < 1 || (int)($source['height'] ?? 0) < 1) return null;
curl_setopt_array($ch, array( if ((int)($image_row['rotation'] ?? 0) !== 0) return null;
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_CONNECTTIMEOUT => 10, $target_width = (int)($derivative_size[0] ?? 0);
CURLOPT_TIMEOUT => 180, $target_height = (int)($derivative_size[1] ?? 0);
CURLOPT_RETURNTRANSFER => false, if ($target_width < 1 || $target_height < 1 || max($target_width, $target_height) > 800) return null;
CURLOPT_FAILONERROR => false,
CURLOPT_USERAGENT => 'Bratonien-Tools-WebDAV-Materialize-Gate', $source_width = (int)$source['width'];
CURLOPT_WRITEFUNCTION => function($ch, $data) { return strlen($data); }, $source_height = (int)$source['height'];
)); $required_width = $target_width * 2;
$ok = curl_exec($ch); $required_height = $target_height * 2;
$errno = curl_errno($ch); $scale = max($required_width / $source_width, $required_height / $source_height);
$error = curl_error($ch); $preview_width = max(32, (int)ceil($source_width * $scale));
$status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); $preview_height = max(32, (int)ceil($source_height * $scale));
curl_close($ch);
if ($ok === false || $errno !== 0) { $detail = 'Interner i.php-Aufruf fehlgeschlagen (cURL '.$errno.($error !== '' ? ': '.$error : '').').'; return false; } if (max($preview_width, $preview_height) > 1600) return null;
if ($status < 200 || $status >= 400) { $detail = 'i.php antwortete mit HTTP '.$status.'.'; return false; }
return array(
'fileid'=>(int)$source['fileid'],
'width'=>$preview_width,
'height'=>$preview_height,
);
}
function bratonien_tools_webdav_derivative_download_preview(array $plan, array $connection, $destination, &$detail=null)
{
$query = http_build_query(array(
'fileId'=>(int)$plan['fileid'],
'x'=>(int)$plan['width'],
'y'=>(int)$plan['height'],
'a'=>'true',
'forceIcon'=>'false',
'mode'=>'fill',
'mimeFallback'=>'false',
), '', '&', PHP_QUERY_RFC3986);
$url = $connection['base_url'].'/core/preview?'.$query;
return bratonien_tools_webdav_derivative_download_url($url, $connection, $destination, $detail);
}
function bratonien_tools_webdav_derivative_make_dir($dir)
{
global $conf;
if (!is_dir($dir))
{
$mode = isset($conf['chmod_value']) ? (int)$conf['chmod_value'] : 0755;
$umask = umask(0);
$ok = @mkdir($dir, $mode, true);
umask($umask);
if (!$ok && !is_dir($dir)) return false;
}
if (!is_writable($dir)) return false;
$index = rtrim($dir, '/').'/index.htm';
if (!file_exists($index)) @file_put_contents($index, 'Not allowed!');
return true; return true;
} }
function bratonien_tools_webdav_derivative_generate($source_path, $target_path, $params, array $image_row, &$detail=null)
{
global $conf;
$detail = '';
include_once(PHPWG_ROOT_PATH.'admin/include/image.class.php');
if (!is_object($params)) { $detail = 'Piwigo-Derivatparameter fehlen.'; return false; }
if (!is_file($source_path) || !is_readable($source_path)) { $detail = 'Materialisierte Bildquelle ist nicht lesbar.'; return false; }
if (!bratonien_tools_webdav_derivative_make_dir(dirname($target_path))) { $detail = 'Piwigo-Derivatverzeichnis konnte nicht angelegt werden.'; return false; }
$rotation_angle = isset($image_row['rotation'])
? pwg_image::get_rotation_angle_from_code($image_row['rotation'])
: pwg_image::get_rotation_angle($source_path);
$coi = $image_row['coi'] ?? null;
if (($params->type ?? '') === IMG_CUSTOM)
{
$defined = ImageStdParams::get_defined_type_map();
if (count($defined) > 0)
{
$sharpen = 0;
foreach ($defined as $std_params) $sharpen += $std_params->sharpen;
$params->sharpen = round($sharpen / count($defined));
}
}
$image = null;
$wm_image = null;
try
{
$image = new pwg_image($source_path);
$changes = 0;
if (0 != $rotation_angle)
{
$image->rotate($rotation_angle);
$changes++;
}
$o_size = $d_size = array($image->get_width(), $image->get_height());
$crop_rect = null;
$scaled_size = null;
$params->sizing->compute($o_size, $coi, $crop_rect, $scaled_size);
if ($crop_rect)
{
$image->crop($crop_rect->width(), $crop_rect->height(), $crop_rect->l, $crop_rect->t);
$changes++;
}
if ($scaled_size)
{
$image->resize($scaled_size[0], $scaled_size[1]);
$d_size = $scaled_size;
$changes++;
}
if ($params->sharpen)
{
$changes += $image->sharpen($params->sharpen);
}
if ($params->will_watermark($d_size))
{
$wm = ImageStdParams::get_watermark();
$wm_image = new pwg_image(PHPWG_ROOT_PATH.$wm->file);
$wm_size = array($wm_image->get_width(), $wm_image->get_height());
if ($d_size[0] < $wm_size[0] || $d_size[1] < $wm_size[1])
{
$wm_scaling_params = SizingParams::classic($d_size[0], $d_size[1]);
$tmp = null;
$wm_scaled_size = null;
$wm_scaling_params->compute($wm_size, null, $tmp, $wm_scaled_size);
$wm_size = $wm_scaled_size;
$wm_image->resize($wm_scaled_size[0], $wm_scaled_size[1]);
}
$x = round(($wm->xpos / 100) * ($d_size[0] - $wm_size[0]));
$y = round(($wm->ypos / 100) * ($d_size[1] - $wm_size[1]));
if ($image->compose($wm_image, $x, $y, $wm->opacity))
{
$changes++;
if ($wm->xrepeat || $wm->yrepeat)
{
$xpad = $wm_size[0] + max(30, round($wm_size[0] / 4));
$ypad = $wm_size[1] + max(30, round($wm_size[1] / 4));
for ($i = -$wm->xrepeat; $i <= $wm->xrepeat; $i++)
{
for ($j = -$wm->yrepeat; $j <= $wm->yrepeat; $j++)
{
if (!$i && !$j) continue;
$x2 = $x + $i * $xpad;
$y2 = $y + $j * $ypad;
if ($x2 >= 0 && $x2 + $wm_size[0] < $d_size[0] && $y2 >= 0 && $y2 + $wm_size[1] < $d_size[1])
{
if (!$image->compose($wm_image, $x2, $y2, $wm->opacity)) break;
}
}
}
}
}
$wm_image->destroy();
$wm_image = null;
}
if (!$changes)
{
$detail = 'Piwigo-Derivat benoetigt keine Bildaenderung.';
return false;
}
if ($d_size[0] * $d_size[1] < ($conf['derivatives_strip_metadata_threshold'] ?? PHP_INT_MAX))
{
$image->strip();
}
$image->write($target_path);
@chmod($target_path, 0644);
clearstatcache(true, $target_path);
if (!is_file($target_path) || !is_readable($target_path) || @getimagesize($target_path) === false)
{
$detail = 'Piwigo hat kein gueltiges finales Derivat erzeugt.';
return false;
}
return true;
}
catch (Throwable $e)
{
$detail = 'Piwigo-Derivaterzeugung fehlgeschlagen: '.$e->getMessage();
return false;
}
finally
{
if ($wm_image) $wm_image->destroy();
if ($image) $image->destroy();
}
}
function bratonien_tools_webdav_derivative_after_url($encoded) function bratonien_tools_webdav_derivative_after_url($encoded)
{ {
$encoded = trim((string)$encoded); $encoded = trim((string)$encoded);
@@ -233,7 +409,12 @@ if (!$source)
bratonien_tools_webdav_derivative_debug($request_id, 'source_resolve_failed'); bratonien_tools_webdav_derivative_debug($request_id, 'source_resolve_failed');
bratonien_tools_webdav_derivative_abort(404, 'Keine WebDAV-Quelle fuer dieses Bild gefunden.'); bratonien_tools_webdav_derivative_abort(404, 'Keine WebDAV-Quelle fuer dieses Bild gefunden.');
} }
bratonien_tools_webdav_derivative_debug($request_id, 'source_resolved', array('connection_id'=>$source['connection_id'] ?? 0, 'root_fileid'=>$source['root_fileid'] ?? 0, 'webdav_path'=>$source['webdav_path'] ?? '')); bratonien_tools_webdav_derivative_debug($request_id, 'source_resolved', array(
'connection_id'=>$source['connection_id'] ?? 0,
'root_fileid'=>$source['root_fileid'] ?? 0,
'fileid'=>$source['fileid'] ?? 0,
'webdav_path'=>$source['webdav_path'] ?? '',
));
$params = bratonien_tools_webdav_materialize_params_from_variant($variant); $params = bratonien_tools_webdav_materialize_params_from_variant($variant);
if (!$params) bratonien_tools_webdav_derivative_abort(400, 'Unbekannte Derivat-Variante.'); if (!$params) bratonien_tools_webdav_derivative_abort(400, 'Unbekannte Derivat-Variante.');
@@ -246,6 +427,7 @@ if ($real_derivative->same_as_source())
header('Location: '.$source_url, true, 302); header('Location: '.$source_url, true, 302);
exit; exit;
} }
$derivative_size = $real_derivative->get_size();
$target_path = $real_derivative->get_path(); $target_path = $real_derivative->get_path();
$derivative_root = PHPWG_ROOT_PATH.PWG_DERIVATIVE_DIR; $derivative_root = PHPWG_ROOT_PATH.PWG_DERIVATIVE_DIR;
@@ -324,41 +506,63 @@ try
{ {
if (is_link($placeholder_backup)) bratonien_tools_webdav_derivative_abort(503, 'WebDAV-Placeholder-Backup existiert bereits; vorheriger Materialisierungslauf ist nicht sauber abgeschlossen.'); if (is_link($placeholder_backup)) bratonien_tools_webdav_derivative_abort(503, 'WebDAV-Placeholder-Backup existiert bereits; vorheriger Materialisierungslauf ist nicht sauber abgeschlossen.');
$detail = ''; $detail = '';
$download_start = microtime(true); $connection = bratonien_tools_webdav_derivative_connection($source, $detail);
if (!bratonien_tools_webdav_derivative_download($source, $download_path, $detail)) bratonien_tools_webdav_derivative_abort(502, $detail); if (!$connection) bratonien_tools_webdav_derivative_abort(502, $detail);
bratonien_tools_webdav_derivative_debug($request_id, 'download_done', array('ms'=>round((microtime(true)-$download_start)*1000, 1), 'bytes'=>@filesize($download_path) ?: 0));
if (!@copy($download_path, $staging_path)) bratonien_tools_webdav_derivative_abort(500, 'Original konnte nicht in den WebDAV-Shadow-Baum kopiert werden.'); $source_mode = 'original';
$preview_plan = bratonien_tools_webdav_derivative_preview_plan($variant, $derivative_size, $source, $image_row);
$download_start = microtime(true);
if ($preview_plan !== null)
{
bratonien_tools_webdav_derivative_debug($request_id, 'preview_start', $preview_plan);
if (bratonien_tools_webdav_derivative_download_preview($preview_plan, $connection, $download_path, $detail))
{
$source_mode = 'preview';
}
else
{
bratonien_tools_webdav_derivative_debug($request_id, 'preview_failed', array('detail'=>$detail));
$detail = '';
}
}
if ($source_mode !== 'preview')
{
if (!bratonien_tools_webdav_derivative_download($source, $connection, $download_path, $detail)) bratonien_tools_webdav_derivative_abort(502, $detail);
}
bratonien_tools_webdav_derivative_debug($request_id, 'download_done', array(
'mode'=>$source_mode,
'ms'=>round((microtime(true)-$download_start)*1000, 1),
'bytes'=>@filesize($download_path) ?: 0,
));
if (!@copy($download_path, $staging_path)) bratonien_tools_webdav_derivative_abort(500, 'Bildquelle konnte nicht in den WebDAV-Shadow-Baum kopiert werden.');
@chmod($staging_path, 0644); @chmod($staging_path, 0644);
if (@getimagesize($staging_path) === false) bratonien_tools_webdav_derivative_abort(500, 'Materialisierte Shadow-Quelle ist kein gueltiges Bild.'); if (@getimagesize($staging_path) === false) bratonien_tools_webdav_derivative_abort(500, 'Materialisierte Shadow-Quelle ist kein gueltiges Bild.');
bratonien_tools_webdav_derivative_debug($request_id, 'swap_begin', array('placeholder'=>$placeholder_target)); bratonien_tools_webdav_derivative_debug($request_id, 'swap_begin', array('placeholder'=>$placeholder_target, 'mode'=>$source_mode));
if (!@rename($shadow_path, $placeholder_backup)) bratonien_tools_webdav_derivative_abort(500, 'WebDAV-Placeholder konnte nicht temporaer gesichert werden.'); if (!@rename($shadow_path, $placeholder_backup)) bratonien_tools_webdav_derivative_abort(500, 'WebDAV-Placeholder konnte nicht temporaer gesichert werden.');
if (!@rename($staging_path, $shadow_path)) if (!@rename($staging_path, $shadow_path))
{ {
@rename($placeholder_backup, $shadow_path); @rename($placeholder_backup, $shadow_path);
bratonien_tools_webdav_derivative_abort(500, 'Materialisiertes Original konnte nicht am Piwigo-Quellpfad aktiviert werden.'); bratonien_tools_webdav_derivative_abort(500, 'Materialisierte Bildquelle konnte nicht am Piwigo-Quellpfad aktiviert werden.');
} }
$swapped = true; $swapped = true;
clearstatcache(true, $shadow_path); clearstatcache(true, $shadow_path);
bratonien_tools_webdav_derivative_debug($request_id, 'swapped', array('is_link'=>is_link($shadow_path), 'realpath'=>realpath($shadow_path) ?: 'FALSE')); bratonien_tools_webdav_derivative_debug($request_id, 'swapped', array('is_link'=>is_link($shadow_path), 'realpath'=>realpath($shadow_path) ?: 'FALSE', 'mode'=>$source_mode));
if (!is_file($shadow_path) || is_link($shadow_path) || @getimagesize($shadow_path) === false) bratonien_tools_webdav_derivative_abort(500, 'Piwigo-Quellpfad enthaelt nach Materialisierung kein gueltiges Original.'); if (!is_file($shadow_path) || is_link($shadow_path) || @getimagesize($shadow_path) === false) bratonien_tools_webdav_derivative_abort(500, 'Piwigo-Quellpfad enthaelt nach Materialisierung keine gueltige Bildquelle.');
$inner_url = bratonien_tools_webdav_derivative_inner_url($target_path); $generate_start = microtime(true);
if ($inner_url === null) bratonien_tools_webdav_derivative_abort(500, 'Piwigo-i.php-URL konnte nicht bestimmt werden.'); bratonien_tools_webdav_derivative_debug($request_id, 'generate_start', array('target'=>$target_path, 'mode'=>$source_mode));
$inner_status = 500; $generate_ok = bratonien_tools_webdav_derivative_generate($shadow_path, $target_path, $params, $image_row, $detail);
$i_start = microtime(true); bratonien_tools_webdav_derivative_debug($request_id, 'generate_done', array('ok'=>$generate_ok, 'mode'=>$source_mode, 'ms'=>round((microtime(true)-$generate_start)*1000, 1), 'detail'=>$detail));
bratonien_tools_webdav_derivative_debug($request_id, 'i_start', array('url'=>$inner_url)); if (!$generate_ok) bratonien_tools_webdav_derivative_abort(500, $detail);
$i_ok = bratonien_tools_webdav_derivative_call_i($inner_url, $inner_status, $detail);
bratonien_tools_webdav_derivative_debug($request_id, 'i_done', array('status'=>$inner_status, 'ok'=>$i_ok, 'ms'=>round((microtime(true)-$i_start)*1000, 1), 'detail'=>$detail));
if (!$i_ok) bratonien_tools_webdav_derivative_abort(502, $detail);
clearstatcache(true, $target_path); clearstatcache(true, $target_path);
$final_size = @getimagesize($target_path); $final_size = @getimagesize($target_path);
if (!is_file($target_path) || !is_readable($target_path) || !is_array($final_size)) bratonien_tools_webdav_derivative_abort(500, 'Piwigo hat kein gueltiges finales Derivat erzeugt.'); if (!is_file($target_path) || !is_readable($target_path) || !is_array($final_size)) bratonien_tools_webdav_derivative_abort(500, 'Piwigo hat kein gueltiges finales Derivat erzeugt.');
$cleanup(); $cleanup();
bratonien_tools_webdav_derivative_debug($request_id, 'serve', array('target'=>$target_path, 'width'=>$final_size[0] ?? 0, 'height'=>$final_size[1] ?? 0)); bratonien_tools_webdav_derivative_debug($request_id, 'serve', array('target'=>$target_path, 'width'=>$final_size[0] ?? 0, 'height'=>$final_size[1] ?? 0, 'mode'=>$source_mode));
if ($redirect_after !== null) { header('Location: '.$redirect_after, true, 302); exit; } if ($redirect_after !== null) { header('Location: '.$redirect_after, true, 302); exit; }
bratonien_tools_webdav_derivative_serve($target_path); bratonien_tools_webdav_derivative_serve($target_path);
} }