Compare commits

...

11 Commits

Author SHA1 Message Date
Terranom674
e2f687237e Bump patch version for automatic source-index initialization 2026-09-03 17:58:28 +02:00
Terranom674
479cdbc473 Allow warmup enable before source index exists 2026-09-03 17:58:06 +02:00
Terranom674
a20f9588f0 Bump patch version for combined cache status display 2026-09-03 17:56:45 +02:00
Terranom674
e831301650 Fix PHP branch syntax in combined cache status 2026-09-03 17:56:17 +02:00
Terranom674
030d0c1be7 Show combined cache rebuild phases in status 2026-09-03 17:55:50 +02:00
Terranom674
8c8d131fbb Explain independent WebDAV source index in admin 2026-09-03 17:46:43 +02:00
Terranom674
9f80c4aa97 Treat source index as current only after both stages 2026-09-03 17:46:25 +02:00
Terranom674
813c3d36a8 Bump patch version for independent WebDAV source index 2026-09-03 17:45:48 +02:00
Terranom674
3ff8c4f547 Use source index as WebDAV warmup baseline 2026-09-03 17:45:25 +02:00
Terranom674
6783974c7d Separate WebDAV source index from Piwigo cache state 2026-09-03 17:45:01 +02:00
Terranom674
ac6353b050 Add independent WebDAV source index 2026-09-03 17:43:46 +02:00
6 changed files with 436 additions and 209 deletions

View File

@@ -0,0 +1,140 @@
<?php
if (!defined('PHPWG_ROOT_PATH'))
{
die('Hacking attempt!');
}
function bratonien_tools_webdav_source_index_file($connection_id)
{
return PHPWG_ROOT_PATH.PWG_LOCAL_DIR.'bratonien-webdav-source-index.connection-'.(int)$connection_id.'.json';
}
function bratonien_tools_webdav_source_index_empty($connection_id)
{
return array(
'schema_version'=>1,
'connection_id'=>(int)$connection_id,
'updated_at'=>0,
'last_periodic_at'=>0,
'sources'=>array(),
);
}
function bratonien_tools_webdav_source_index_load($connection_id)
{
$file = bratonien_tools_webdav_source_index_file($connection_id);
if (!is_file($file) || !is_readable($file)) return null;
$raw = @file_get_contents($file);
$index = $raw !== false ? json_decode($raw, true) : null;
if (!is_array($index)) return null;
$base = bratonien_tools_webdav_source_index_empty($connection_id);
$index = array_merge($base, $index);
if (!isset($index['sources']) || !is_array($index['sources'])) $index['sources'] = array();
$index['connection_id'] = (int)$connection_id;
return $index;
}
function bratonien_tools_webdav_source_index_save($connection_id, array $index)
{
$file = bratonien_tools_webdav_source_index_file($connection_id);
$directory = dirname($file);
if (!is_dir($directory) && !@mkdir($directory, 0775, true) && !is_dir($directory))
{
throw new RuntimeException('WebDAV-Quellenindex-Verzeichnis konnte nicht angelegt werden.');
}
$index['schema_version'] = 1;
$index['connection_id'] = (int)$connection_id;
$index['updated_at'] = time();
if (!isset($index['sources']) || !is_array($index['sources'])) $index['sources'] = array();
ksort($index['sources'], SORT_STRING);
$json = json_encode($index, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
if ($json === false) throw new RuntimeException('WebDAV-Quellenindex konnte nicht serialisiert werden.');
$tmp = $file.'.tmp-'.bin2hex(random_bytes(4));
if (@file_put_contents($tmp, $json."\n", LOCK_EX) === false)
{
throw new RuntimeException('WebDAV-Quellenindex konnte nicht geschrieben werden.');
}
@chmod($tmp, 0664);
if (!@rename($tmp, $file))
{
@unlink($tmp);
throw new RuntimeException('WebDAV-Quellenindex konnte nicht atomar gespeichert werden.');
}
}
function bratonien_tools_webdav_source_index_key(array $source)
{
$connection_id = (int)($source['connection_id'] ?? 0);
$fileid = (int)($source['fileid'] ?? 0);
if ($fileid > 0)
{
return 'c'.$connection_id.':f'.$fileid;
}
$root_fileid = (int)($source['root_fileid'] ?? 0);
$path = trim((string)($source['webdav_path'] ?? ''), '/');
return 'c'.$connection_id.':r'.$root_fileid.':p'.sha1($path);
}
function bratonien_tools_webdav_source_index_signature(array $source)
{
return sha1(implode('|', array(
(int)($source['connection_id'] ?? 0),
(int)($source['root_fileid'] ?? 0),
(int)($source['fileid'] ?? 0),
trim((string)($source['webdav_path'] ?? ''), '/'),
(string)($source['etag'] ?? ''),
(int)($source['size'] ?? 0),
strtolower((string)($source['content_type'] ?? $source['mime'] ?? '')),
(int)($source['width'] ?? 0),
(int)($source['height'] ?? 0),
)));
}
function bratonien_tools_webdav_source_index_metadata(array $source, $image_id)
{
$path = trim((string)($source['webdav_path'] ?? ''), '/');
return array(
'image_id'=>(int)$image_id,
'fileid'=>(int)($source['fileid'] ?? 0),
'root_fileid'=>(int)($source['root_fileid'] ?? 0),
'webdav_path'=>$path,
'name'=>basename($path),
'etag'=>(string)($source['etag'] ?? ''),
'size'=>(int)($source['size'] ?? 0),
'content_type'=>(string)($source['content_type'] ?? $source['mime'] ?? ''),
'width'=>(int)($source['width'] ?? 0),
'height'=>(int)($source['height'] ?? 0),
'source_signature'=>bratonien_tools_webdav_source_index_signature($source),
'last_seen_at'=>time(),
);
}
function bratonien_tools_webdav_source_index_is_current(array $entry, $signature)
{
$signature = (string)$signature;
return isset($entry['source_signature'], $entry['stage1_signature'], $entry['stage2_signature'])
&& hash_equals((string)$entry['source_signature'], $signature)
&& hash_equals((string)$entry['stage1_signature'], $signature)
&& hash_equals((string)$entry['stage2_signature'], $signature);
}
function bratonien_tools_webdav_source_index_prune(array &$index, array $current_keys)
{
$keep = array_fill_keys($current_keys, true);
$removed = 0;
foreach (array_keys((array)$index['sources']) as $key)
{
if (!isset($keep[$key]))
{
unset($index['sources'][$key]);
$removed++;
}
}
return $removed;
}

View File

@@ -45,8 +45,8 @@ function bratonien_tools_webdav_warmup_missing_baselines()
if (empty($connection['enabled']) || !bratonien_tools_nc_connector_is_webdav($connection)) continue;
$connection_id = (int)($connection['id'] ?? 0);
if ($connection_id < 1) continue;
$state_file = PHPWG_ROOT_PATH.PWG_LOCAL_DIR.'bratonien-webdav-warmup.connection-'.$connection_id.'.json';
if (!is_file($state_file) || !is_readable($state_file)) $missing[] = '#'.$connection_id;
$index_file = PHPWG_ROOT_PATH.PWG_LOCAL_DIR.'bratonien-webdav-source-index.connection-'.$connection_id.'.json';
if (!is_file($index_file) || !is_readable($index_file)) $missing[] = '#'.$connection_id;
}
return $missing;
}
@@ -60,16 +60,7 @@ function bratonien_tools_save_webdav_warmup_settings()
$batch_size = max(1, min(50, $batch_size));
$periodic_hours = max(1, min(168, $periodic_hours));
if ($enabled && empty($current['enabled']))
{
$missing = bratonien_tools_webdav_warmup_missing_baselines();
if ($missing)
{
throw new RuntimeException(
'WebDAV-Cache-Warmup bleibt deaktiviert. Bitte zuerst „Jetzt prüfen“ ausführen, damit der bestehende produktive Bestand nur als Ausgangszustand erfasst wird. Fehlende Baseline: '.implode(', ', $missing)
);
}
}
$missing = $enabled ? bratonien_tools_webdav_warmup_missing_baselines() : array();
$payload = array(
'enabled'=>$enabled,
@@ -97,12 +88,18 @@ function bratonien_tools_save_webdav_warmup_settings()
throw new RuntimeException('Warmup-Einstellungen konnten nicht atomar gespeichert werden.');
}
return array('message'=>sprintf(
'WebDAV-Cache-Warmup gespeichert: %s, %d Bilder pro Batch, Eingangsprüfung alle %d Stunden.',
$message = sprintf(
'WebDAV-Cache-Warmup gespeichert: %s, %d Bilder pro Batch, Quellenindex-Abgleich alle %d Stunden.',
$enabled ? 'automatisch aktiv' : 'Automatik deaktiviert',
$batch_size,
$periodic_hours
));
);
if ($enabled && $missing)
{
$message .= ' Für '.implode(', ', $missing).' existiert noch kein Quellenindex; dieser wird beim ersten Worker-Lauf automatisch als Ausgangsbestand angelegt.';
}
return array('message'=>$message);
}
function bratonien_tools_webdav_warmup_php_cli()
@@ -143,7 +140,7 @@ function bratonien_tools_start_webdav_warmup_manual()
{
return bratonien_tools_start_webdav_warmup_mode(
'manual',
'WebDAV-Warmup: Prüfung auf neue oder geänderte Bilder wurde gestartet.'
'WebDAV-Quellenindex: Prüfung auf neue oder geänderte Bilder wurde gestartet.'
);
}
@@ -151,7 +148,7 @@ function bratonien_tools_start_webdav_cache_rebuild()
{
return bratonien_tools_start_webdav_warmup_mode(
'rebuild',
'WebDAV-Bildcache: vollständiger Wiederaufbau wurde gestartet.'
'WebDAV-Bildcache: vollständiger Wiederaufbau durch Piwigo wurde gestartet.'
);
}
@@ -193,7 +190,7 @@ function bratonien_tools_get_webdav_warmup_status()
{
$status = array(
'state'=>'idle',
'message'=>'Noch kein Warmup-Lauf protokolliert.',
'message'=>'Noch kein Quellenindex-/Warmup-Lauf protokolliert.',
'updated_at'=>0,
);
$files = glob(PHPWG_ROOT_PATH.PWG_LOCAL_DIR.'bratonien-webdav-warmup.status-*.json');

View File

@@ -25,32 +25,176 @@ if (!function_exists('is_admin') || !is_admin())
}
require_once(BRATONIEN_TOOLS_PATH.'tools/image_cache.inc.php');
$file = bratonien_tools_main_cache_status_file();
if (!is_file($file) || !is_readable($file))
function bratonien_tools_status_read_json($file)
{
echo json_encode(array(
'state'=>'idle','message'=>'Noch kein manueller Cache-Aufbau gestartet.',
if (!is_file($file) || !is_readable($file)) return null;
$raw = @file_get_contents($file);
$data = $raw !== false ? json_decode($raw, true) : null;
return is_array($data) ? $data : null;
}
function bratonien_tools_status_webdav_label(array $status)
{
$connection = (int)($status['connection_id'] ?? 0);
$state = (string)($status['state'] ?? 'idle');
$prefix = $connection > 0 ? 'WebDAV #'.$connection.': ' : 'WebDAV: ';
if ($state === 'scan')
{
$images = (int)($status['images'] ?? 0);
$selected = (int)($status['selected'] ?? 0);
return $prefix.'Quellenindex wird mit dem aktuellen Connector-Bestand verglichen · '.$images.' Quellen gefunden · '.$selected.' neu/geändert bzw. für Rebuild ausgewählt.';
}
if ($state === 'running')
{
$stage = (int)($status['stage'] ?? 0);
$batch = (int)($status['batch'] ?? 0);
$requested = (int)($status['batch_requested'] ?? 0);
$downloaded = (int)($status['batch_downloaded'] ?? 0);
$parts = array($prefix.'Piwigo verarbeitet temporär bereitgestellte WebDAV-Originale');
if ($stage > 0) $parts[] = 'Stufe '.$stage;
if ($batch > 0) $parts[] = 'Batch '.$batch;
if ($requested > 0) $parts[] = $downloaded.' / '.$requested.' Quellen dieses Batches geladen';
return implode(' · ', $parts).'.';
}
if ($state === 'preempted')
{
return $prefix.'Stufe 2 wurde nach einem vollständigen Batch unterbrochen, damit neue Connector-Inhalte zuerst verarbeitet werden können.';
}
if ($state === 'baseline')
{
return $prefix.'Quellenindex wurde als Ausgangsbestand angelegt; noch keine Derivate wurden deshalb erzeugt.';
}
if ($state === 'complete')
{
return $prefix.((string)($status['message'] ?? '') !== '' ? (string)$status['message'] : 'Verarbeitung abgeschlossen.');
}
if ($state === 'error' || $state === 'fatal')
{
return $prefix.'FEHLER: '.((string)($status['message'] ?? '') !== '' ? (string)$status['message'] : 'WebDAV-Verarbeitung fehlgeschlagen.');
}
return $prefix.((string)($status['message'] ?? '') !== '' ? (string)$status['message'] : 'wartet.');
}
$local_file = bratonien_tools_main_cache_status_file();
$local = bratonien_tools_status_read_json($local_file);
if ($local === null)
{
$local = array(
'state'=>'idle','message'=>'Noch kein lokaler Piwigo-Cache-Aufbau gestartet.',
'total'=>0,'completed'=>0,'generated'=>0,'cached'=>0,'skipped'=>0,'errors'=>0,'current'=>'','updated_at'=>0,
), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
);
}
$raw = @file_get_contents($file);
$data = $raw !== false ? json_decode($raw, true) : null;
if (!is_array($data))
$webdav = array();
foreach ((array)glob(PHPWG_ROOT_PATH.PWG_LOCAL_DIR.'bratonien-webdav-warmup.status-*.json') as $file)
{
http_response_code(500);
echo json_encode(array('state'=>'error','message'=>'Cache-Status ist nicht lesbar.'));
exit;
$status = bratonien_tools_status_read_json($file);
if (!$status) continue;
if (!isset($status['connection_id']) && preg_match('/status-(\d+)\.json$/', $file, $m)) $status['connection_id'] = (int)$m[1];
$webdav[] = $status;
}
usort($webdav, function($a, $b) {
return (int)($a['connection_id'] ?? 0) <=> (int)($b['connection_id'] ?? 0);
});
$state = (string)($data['state'] ?? 'idle');
$updated = (int)($data['updated_at'] ?? 0);
if (($state === 'running' || $state === 'queued') && $updated > 0 && (time() - $updated) > 45)
$webdav_active = false;
$webdav_error = false;
$webdav_pending = false;
$webdav_latest = 0;
$webdav_lines = array();
foreach ($webdav as $status)
{
$data['state'] = 'error';
$data['message'] = 'Cache-Aufbau liefert seit mehr als 45 Sekunden keinen Fortschritt. Der Prozess ist vermutlich beendet oder festgefahren.';
$data['errors'] = max(1, (int)($data['errors'] ?? 0));
$state = (string)($status['state'] ?? 'idle');
$webdav_latest = max($webdav_latest, (int)($status['updated_at'] ?? 0));
if (in_array($state, array('scan','running'), true)) $webdav_active = true;
if ($state === 'preempted') $webdav_pending = true;
if (in_array($state, array('error','fatal'), true)) $webdav_error = true;
$webdav_lines[] = bratonien_tools_status_webdav_label($status);
}
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$local_state = (string)($local['state'] ?? 'idle');
$local_updated = (int)($local['updated_at'] ?? 0);
$local_stale = in_array($local_state, array('running','queued'), true) && $local_updated > 0 && (time() - $local_updated) > 45;
// Ein ruhiger lokaler Worker ist kein Stall des Gesamtaufbaus, solange der
// getrennte WebDAV-Worker sichtbar weiterarbeitet. Der 45-Sekunden-Fehler darf
// deshalb nur ausgelöst werden, wenn auch kein WebDAV-Teil aktiv ist.
if ($local_stale && !$webdav_active)
{
$local['state'] = 'error';
$local['message'] = 'Der lokale Piwigo-Teil liefert seit mehr als 45 Sekunden keinen Fortschritt.';
$local['errors'] = max(1, (int)($local['errors'] ?? 0));
$local_state = 'error';
}
$local_label = '';
if ($local_state === 'running' || $local_state === 'queued')
{
$local_label = 'Lokaler Piwigo-Teil läuft: '.((string)($local['message'] ?? '') !== '' ? (string)$local['message'] : 'Cache-Varianten werden verarbeitet.');
}
elseif ($local_state === 'complete')
{
$local_label = 'Lokaler Piwigo-Teil fertig.';
}
elseif ($local_state === 'cancelled')
{
$local_label = 'Lokaler Piwigo-Teil wurde abgebrochen.';
}
elseif ($local_state === 'error')
{
$local_label = 'Lokaler Piwigo-Teil mit Fehler: '.((string)($local['message'] ?? '') !== '' ? (string)$local['message'] : 'unbekannter Fehler');
}
else
{
$local_label = 'Lokaler Piwigo-Teil: keine relevanten lokalen Bildquellen aktiv.';
}
$overall = $local;
$overall['local'] = $local;
$overall['webdav'] = $webdav;
$overall['updated_at'] = max($local_updated, $webdav_latest);
if ($webdav_active)
{
$overall['state'] = 'running';
// Während der WebDAV-Verarbeitung wären die lokalen Variantenzahlen als
// Gesamtfortschritt irreführend. Die Oberfläche zeigt deshalb bewusst die
// aktuelle Arbeitsphase statt eines falschen Prozentwerts.
$overall['total'] = 0;
$overall['completed'] = 0;
$overall['generated'] = 0;
$overall['cached'] = 0;
$overall['skipped'] = 0;
$overall['message'] = 'Gesamtaufbau läuft. '.$local_label;
$overall['current'] = implode(' ', $webdav_lines);
}
elseif ($webdav_error || $local_state === 'error')
{
$overall['state'] = 'error';
$overall['message'] = 'Cache-Aufbau mit Fehlern. '.$local_label;
$overall['current'] = implode(' ', $webdav_lines);
$overall['errors'] = max(1, (int)($overall['errors'] ?? 0));
}
elseif ($webdav_pending)
{
$overall['state'] = 'queued';
$overall['total'] = 0;
$overall['completed'] = 0;
$overall['message'] = 'Cache-Aufbau wartet auf priorisierte Connector-Inhalte. '.$local_label;
$overall['current'] = implode(' ', $webdav_lines);
}
elseif ($webdav && $local_state === 'complete')
{
$overall['state'] = 'complete';
$overall['message'] = 'Cache-Aufbau abgeschlossen. '.$local_label;
$overall['current'] = implode(' ', $webdav_lines);
}
else
{
$overall['message'] = $local_label;
if ($webdav_lines) $overall['current'] = implode(' ', $webdav_lines);
}
echo json_encode($overall, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

View File

@@ -1,7 +1,7 @@
<?php
/*
Plugin Name: Bratonien Tools
Version: 0.9.7.1.12
Version: 0.9.7.1.15
Description: Erweiterbare Administrationswerkzeuge fuer die Bratonien-Piwigo-Installation.
Plugin URI: https://github.com/Terranom674/Piwigo_Bratonien_Tools
Author: Bratonien

View File

@@ -23,7 +23,7 @@ $_SERVER['REQUEST_URI'] = '/';
$_SERVER['SCRIPT_NAME'] = '/plugins/bratonien_tools/runtime/lib/webdav-cache-warmup.php';
$_SERVER['PHP_SELF'] = $_SERVER['SCRIPT_NAME'];
$_SERVER['QUERY_STRING'] = '';
$_SERVER['HTTP_USER_AGENT'] = 'Bratonien-WebDAV-Cache-Warmup/0.9.7.1.12';
$_SERVER['HTTP_USER_AGENT'] = 'Bratonien-WebDAV-Cache-Warmup/0.9.7.1.13';
$_SERVER['HTTPS'] = 'off';
require_once(PHPWG_ROOT_PATH.'include/common.inc.php');
@@ -37,6 +37,7 @@ if (!defined('BRATONIEN_TOOLS_PATH'))
require_once(BRATONIEN_TOOLS_PATH.'include/nc_connector.inc.php');
require_once(BRATONIEN_TOOLS_PATH.'include/webdav_cache_validation.inc.php');
require_once(BRATONIEN_TOOLS_PATH.'include/webdav_materialize_runtime.inc.php');
require_once(BRATONIEN_TOOLS_PATH.'include/webdav_source_index.inc.php');
require_once(BRATONIEN_TOOLS_PATH.'include/webdav_warmup_settings.inc.php');
function bratonien_tools_cache_warmup_log($event, array $fields=array())
@@ -52,11 +53,6 @@ function bratonien_tools_cache_warmup_log($event, array $fields=array())
fwrite(STDOUT, implode(' ', $parts)."\n");
}
function bratonien_tools_cache_warmup_state_file($connection_id)
{
return PHPWG_ROOT_PATH.PWG_LOCAL_DIR.'bratonien-webdav-warmup.connection-'.(int)$connection_id.'.json';
}
function bratonien_tools_cache_warmup_status_file($connection_id)
{
return PHPWG_ROOT_PATH.PWG_LOCAL_DIR.'bratonien-webdav-warmup.status-'.(int)$connection_id.'.json';
@@ -77,17 +73,17 @@ function bratonien_tools_cache_warmup_write_json($file, array $payload)
$directory = dirname($file);
if (!is_dir($directory) && !@mkdir($directory, 0775, true) && !is_dir($directory))
{
throw new RuntimeException('Warmup-State-Verzeichnis konnte nicht angelegt werden.');
throw new RuntimeException('Warmup-Statusverzeichnis konnte nicht angelegt werden.');
}
$json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
if ($json === false) throw new RuntimeException('Warmup-State konnte nicht serialisiert werden.');
if ($json === false) throw new RuntimeException('Warmup-Status konnte nicht serialisiert werden.');
$tmp = $file.'.tmp-'.bin2hex(random_bytes(4));
if (@file_put_contents($tmp, $json."\n", LOCK_EX) === false) throw new RuntimeException('Warmup-State konnte nicht geschrieben werden.');
if (@file_put_contents($tmp, $json."\n", LOCK_EX) === false) throw new RuntimeException('Warmup-Status konnte nicht geschrieben werden.');
@chmod($tmp, 0664);
if (!@rename($tmp, $file))
{
@unlink($tmp);
throw new RuntimeException('Warmup-State konnte nicht atomar gespeichert werden.');
throw new RuntimeException('Warmup-Status konnte nicht atomar gespeichert werden.');
}
}
@@ -104,23 +100,6 @@ function bratonien_tools_cache_warmup_status($connection_id, $state, $message, a
);
}
function bratonien_tools_cache_warmup_load_state($connection_id)
{
$file = bratonien_tools_cache_warmup_state_file($connection_id);
if (!is_file($file) || !is_readable($file)) return null;
$raw = @file_get_contents($file);
$state = $raw !== false ? json_decode($raw, true) : null;
return is_array($state) ? $state : null;
}
function bratonien_tools_cache_warmup_save_state($connection_id, array $state)
{
$state['updated_at'] = time();
if (!isset($state['albums']) || !is_array($state['albums'])) $state['albums'] = array();
if (!isset($state['images']) || !is_array($state['images'])) $state['images'] = array();
bratonien_tools_cache_warmup_write_json(bratonien_tools_cache_warmup_state_file($connection_id), $state);
}
function bratonien_tools_cache_warmup_credentials($connection_id)
{
$connection = bratonien_tools_nc_connector_connection((int)$connection_id, true);
@@ -152,24 +131,9 @@ function bratonien_tools_cache_warmup_absolute_path($path)
return PHPWG_ROOT_PATH.ltrim(preg_replace('#^\./#', '', $path), '/');
}
function bratonien_tools_cache_warmup_signature(array $source)
{
return sha1(implode('|', array(
(int)($source['connection_id'] ?? 0),
(int)($source['root_fileid'] ?? 0),
(int)($source['fileid'] ?? 0),
trim((string)($source['webdav_path'] ?? ''), '/'),
(string)($source['etag'] ?? ''),
(int)($source['size'] ?? 0),
(int)($source['width'] ?? 0),
(int)($source['height'] ?? 0),
)));
}
function bratonien_tools_cache_warmup_scan($connection_id)
{
$images = array();
$albums = array();
$resolved = array();
$result = pwg_query('SELECT id, path, width, height, rotation, coi FROM '.IMAGES_TABLE.' ORDER BY id');
@@ -185,6 +149,7 @@ function bratonien_tools_cache_warmup_scan($connection_id)
{
throw new RuntimeException('Bild #'.$image_id.': bestehender Piwigo-Pfad kann nicht aufgelöst werden.');
}
$normalized = str_replace('\\', '/', $real);
$expected = '/nc-webdav-source/connection-'.(int)$source['connection_id'].'/root-'.(int)$source['root_fileid'].'/';
if (strpos($normalized, $expected) === false)
@@ -195,7 +160,6 @@ function bratonien_tools_cache_warmup_scan($connection_id)
{
throw new RuntimeException('Bild #'.$image_id.': WebDAV-Mapping enthält keinen Pfad.');
}
if (isset($resolved[$normalized]) && $resolved[$normalized] !== $image_id)
{
throw new RuntimeException('Sicherheitsabbruch: Bild #'.$resolved[$normalized].' und Bild #'.$image_id.' zeigen auf dieselbe physische Connector-Quelle.');
@@ -203,93 +167,53 @@ function bratonien_tools_cache_warmup_scan($connection_id)
$resolved[$normalized] = $image_id;
$row['id'] = $image_id;
$index_key = bratonien_tools_webdav_source_index_key($source);
$images[$image_id] = array(
'row'=>$row,
'source'=>$source,
'logical'=>$logical,
'resolved'=>$real,
'signature'=>bratonien_tools_cache_warmup_signature($source),
'albums'=>array(),
'index_key'=>$index_key,
'signature'=>bratonien_tools_webdav_source_index_signature($source),
);
}
if ($images)
{
foreach (array_chunk(array_keys($images), 500) as $chunk)
{
$result = pwg_query('SELECT image_id, category_id FROM '.IMAGE_CATEGORY_TABLE.' WHERE image_id IN ('.implode(',', array_map('intval', $chunk)).')');
while ($rel = pwg_db_fetch_assoc($result))
{
$image_id = (int)$rel['image_id'];
$category_id = (int)$rel['category_id'];
if (!isset($images[$image_id]) || $category_id < 1) continue;
$images[$image_id]['albums'][$category_id] = true;
$albums[$category_id] = true;
}
}
}
foreach ($images as &$image) $image['albums'] = array_map('intval', array_keys($image['albums']));
unset($image);
return array('images'=>$images, 'albums'=>array_map('intval', array_keys($albums)));
return array('images'=>$images);
}
function bratonien_tools_cache_warmup_baseline($connection_id, array $scan)
function bratonien_tools_cache_warmup_build_baseline($connection_id, array $scan)
{
$state = array('albums'=>array(), 'images'=>array(), 'last_periodic_at'=>time());
foreach ($scan['albums'] as $album_id) $state['albums'][(string)$album_id] = array('seen_at'=>time());
$index = bratonien_tools_webdav_source_index_empty($connection_id);
foreach ($scan['images'] as $image_id=>$image)
{
$state['images'][(string)$image_id] = array(
'stage1_signature'=>$image['signature'],
'stage2_signature'=>$image['signature'],
'baseline'=>true,
);
$entry = bratonien_tools_webdav_source_index_metadata($image['source'], $image_id);
$entry['stage1_signature'] = $image['signature'];
$entry['stage2_signature'] = $image['signature'];
$entry['baseline'] = true;
$index['sources'][$image['index_key']] = $entry;
}
bratonien_tools_cache_warmup_save_state($connection_id, $state);
return $state;
$index['last_periodic_at'] = time();
bratonien_tools_webdav_source_index_save($connection_id, $index);
return $index;
}
function bratonien_tools_cache_warmup_select(array $scan, array $state, $mode)
function bratonien_tools_cache_warmup_select(array $scan, array $index, $mode)
{
$known_albums = array_fill_keys(array_map('intval', array_keys((array)($state['albums'] ?? array()))), true);
$new_albums = array();
foreach ($scan['albums'] as $album_id)
{
if (!isset($known_albums[(int)$album_id])) $new_albums[(int)$album_id] = true;
}
if ($mode === 'rebuild')
{
return array('images'=>$scan['images'], 'new_albums'=>array_map('intval', array_keys($new_albums)));
}
if ($mode === 'rebuild') return $scan['images'];
$selected = array();
foreach ($scan['images'] as $image_id=>$image)
{
$saved = isset($state['images'][(string)$image_id]) && is_array($state['images'][(string)$image_id])
? $state['images'][(string)$image_id]
: array();
$stage1_done = isset($saved['stage1_signature']) && hash_equals((string)$saved['stage1_signature'], $image['signature']);
$stage2_done = isset($saved['stage2_signature']) && hash_equals((string)$saved['stage2_signature'], $image['signature']);
$entry = isset($index['sources'][$image['index_key']]) && is_array($index['sources'][$image['index_key']])
? $index['sources'][$image['index_key']]
: null;
$in_new_album = false;
foreach ($image['albums'] as $album_id)
if ($entry === null || !bratonien_tools_webdav_source_index_is_current($entry, $image['signature']))
{
if (isset($new_albums[(int)$album_id])) { $in_new_album = true; break; }
}
if ($mode === 'sync')
{
if ($in_new_album && (!$stage1_done || !$stage2_done)) $selected[$image_id] = $image;
}
else
{
if (!$stage1_done || !$stage2_done) $selected[$image_id] = $image;
$selected[$image_id] = $image;
}
}
return array('images'=>$selected, 'new_albums'=>array_map('intval', array_keys($new_albums)));
return $selected;
}
function bratonien_tools_cache_warmup_variant_rows(array $image, $stage)
@@ -377,7 +301,7 @@ function bratonien_tools_cache_warmup_download(array $source, array $credentials
CURLOPT_RETURNTRANSFER=>false,
CURLOPT_FAILONERROR=>false,
CURLOPT_FILE=>$fp,
CURLOPT_USERAGENT=>'Bratonien-WebDAV-Cache-Warmup/0.9.7.1.12',
CURLOPT_USERAGENT=>'Bratonien-WebDAV-Cache-Warmup/0.9.7.1.13',
));
$ok = curl_exec($ch);
$errno = curl_errno($ch);
@@ -455,19 +379,17 @@ function bratonien_tools_cache_warmup_restore($source_path, $backup, $staging, $
return true;
}
function bratonien_tools_cache_warmup_process(array $image, $temp_file, array $credentials, $stage, $force=false)
function bratonien_tools_cache_warmup_process(array $image, $temp_file, array $credentials, $stage)
{
$image_id = (int)$image['row']['id'];
$variants = bratonien_tools_cache_warmup_variant_rows($image, $stage);
$pending = array();
$requests = array();
foreach ($variants as $variant)
{
$cache_reason = '';
if (!$force && bratonien_tools_webdav_derivative_cache_valid($variant['target'], $variant['derivative'], $variant['params'], $cache_reason)) continue;
$request = bratonien_tools_cache_warmup_i_request($variant['target']);
if ($request !== null) $pending[] = $variant + array('request'=>$request);
if ($request !== null) $requests[] = $variant + array('request'=>$request);
}
if (!$pending) return array('ok'=>true, 'generated'=>0, 'message'=>'Bereits gültig im Piwigo-Cache vorhanden.');
if (!$requests) return array('ok'=>true, 'generated'=>0, 'message'=>'Für diese Stufe sind keine Piwigo-Derivate definiert.');
$image_lock = null;
if (!bratonien_tools_cache_warmup_image_lock($image_id, $image_lock))
@@ -483,7 +405,7 @@ function bratonien_tools_cache_warmup_process(array $image, $temp_file, array $c
}
$fresh = bratonien_tools_webdav_materialize_source_info($image_id);
if (!$fresh || (int)$fresh['connection_id'] !== (int)$image['source']['connection_id'] || bratonien_tools_cache_warmup_signature($fresh) !== $image['signature'])
if (!$fresh || (int)$fresh['connection_id'] !== (int)$image['source']['connection_id'] || bratonien_tools_webdav_source_index_signature($fresh) !== $image['signature'])
{
bratonien_tools_cache_warmup_unlock($sync_lock);
bratonien_tools_cache_warmup_unlock($image_lock);
@@ -544,7 +466,7 @@ function bratonien_tools_cache_warmup_process(array $image, $temp_file, array $c
clearstatcache(true, $source_path);
if (@getimagesize($source_path) === false) throw new RuntimeException('Piwigo-Quellpfad enthält nach dem Swap kein lesbares Bild.');
foreach ($pending as $variant)
foreach ($requests as $variant)
{
$call_detail = '';
if (!bratonien_tools_cache_warmup_call_piwigo($variant['request'], $call_detail))
@@ -578,24 +500,53 @@ function bratonien_tools_cache_warmup_process(array $image, $temp_file, array $c
bratonien_tools_cache_warmup_unlock($sync_lock);
bratonien_tools_cache_warmup_unlock($image_lock);
if (!$restored) return array('ok'=>false, 'fatal'=>true, 'message'=>'RESTORE FEHLGESCHLAGEN: '.$restore_detail);
return array('ok'=>true, 'generated'=>$generated, 'message'=>'Piwigo hat die angeforderten Derivate erzeugt.');
return array('ok'=>true, 'generated'=>$generated, 'message'=>'Piwigo hat die angeforderten Derivate verarbeitet.');
}
function bratonien_tools_cache_warmup_stage_pending(array $selected, array $state, $stage)
function bratonien_tools_cache_warmup_stage_pending(array $selected, array $index, $stage, $force_all=false)
{
if ($force_all) return $selected;
$pending = array();
$key = $stage === 1 ? 'stage1_signature' : 'stage2_signature';
$stage_key = $stage === 1 ? 'stage1_signature' : 'stage2_signature';
foreach ($selected as $image_id=>$image)
{
$saved = isset($state['images'][(string)$image_id]) && is_array($state['images'][(string)$image_id]) ? $state['images'][(string)$image_id] : array();
if (!isset($saved[$key]) || !hash_equals((string)$saved[$key], $image['signature'])) $pending[$image_id] = $image;
$entry = isset($index['sources'][$image['index_key']]) && is_array($index['sources'][$image['index_key']])
? $index['sources'][$image['index_key']]
: array();
if (!isset($entry[$stage_key]) || !hash_equals((string)$entry[$stage_key], $image['signature']))
{
$pending[$image_id] = $image;
}
}
return $pending;
}
function bratonien_tools_cache_warmup_run_stage($connection_id, $stage, array $selected, array &$state, array $credentials, $batch_size, $priority_file='')
function bratonien_tools_cache_warmup_mark_stage_success($connection_id, array &$index, array $image, $image_id, $stage)
{
$pending = bratonien_tools_cache_warmup_stage_pending($selected, $state, $stage);
$key = $image['index_key'];
$previous = isset($index['sources'][$key]) && is_array($index['sources'][$key]) ? $index['sources'][$key] : array();
$previous_signature = (string)($previous['source_signature'] ?? '');
$entry = array_merge($previous, bratonien_tools_webdav_source_index_metadata($image['source'], $image_id));
if ($stage === 1)
{
$entry['stage1_signature'] = $image['signature'];
if ($previous_signature === '' || !hash_equals($previous_signature, $image['signature'])) unset($entry['stage2_signature']);
}
else
{
$entry['stage2_signature'] = $image['signature'];
}
unset($entry['baseline']);
$entry['last_processed_at'] = time();
$index['sources'][$key] = $entry;
bratonien_tools_webdav_source_index_save($connection_id, $index);
}
function bratonien_tools_cache_warmup_run_stage($connection_id, $stage, array $selected, array &$index, array $credentials, $batch_size, $priority_file='', $force_all=false)
{
$pending = bratonien_tools_cache_warmup_stage_pending($selected, $index, $stage, $force_all);
if (!$pending) return array('ok'=>true, 'success'=>array(), 'failed'=>0, 'preempted'=>false);
if ($stage === 2 && bratonien_tools_cache_warmup_priority_pending($priority_file))
@@ -637,20 +588,16 @@ function bratonien_tools_cache_warmup_run_stage($connection_id, $stage, array $s
'batch'=>$batch_number,
'batch_requested'=>count($ids),
'batch_downloaded'=>count($downloads),
'source_index'=>count($index['sources']),
));
foreach ($downloads as $image_id=>$file)
{
$saved = isset($state['images'][(string)$image_id]) && is_array($state['images'][(string)$image_id]) ? $state['images'][(string)$image_id] : array();
$force = !empty($saved['baseline']) || (
isset($saved['stage1_signature']) && !hash_equals((string)$saved['stage1_signature'], $pending[$image_id]['signature'])
);
if ($stage === 2 && isset($saved['stage2_signature']) && !hash_equals((string)$saved['stage2_signature'], $pending[$image_id]['signature'])) $force = true;
$result = bratonien_tools_cache_warmup_process($pending[$image_id], $file, $credentials, $stage, $force);
$result = bratonien_tools_cache_warmup_process($pending[$image_id], $file, $credentials, $stage);
bratonien_tools_cache_warmup_log('image', array(
'stage'=>$stage,
'image_id'=>$image_id,
'source_key'=>$pending[$image_id]['index_key'],
'ok'=>!empty($result['ok']),
'generated'=>(int)($result['generated'] ?? 0),
'message'=>(string)($result['message'] ?? ''),
@@ -667,10 +614,7 @@ function bratonien_tools_cache_warmup_run_stage($connection_id, $stage, array $s
continue;
}
if (!isset($state['images'][(string)$image_id]) || !is_array($state['images'][(string)$image_id])) $state['images'][(string)$image_id] = array();
$state['images'][(string)$image_id][$stage === 1 ? 'stage1_signature' : 'stage2_signature'] = $pending[$image_id]['signature'];
unset($state['images'][(string)$image_id]['baseline']);
bratonien_tools_cache_warmup_save_state($connection_id, $state);
bratonien_tools_cache_warmup_mark_stage_success($connection_id, $index, $pending[$image_id], $image_id, $stage);
$success[$image_id] = true;
}
@@ -715,28 +659,28 @@ function bratonien_tools_cache_warmup_run($connection_id, $mode)
if ($mode === 'sync' && is_file($priority_file)) @unlink($priority_file);
$scan = bratonien_tools_cache_warmup_scan($connection_id);
$state = bratonien_tools_cache_warmup_load_state($connection_id);
if ($state === null)
$index = bratonien_tools_webdav_source_index_load($connection_id);
if ($index === null)
{
if ($mode === 'rebuild')
{
$state = array('albums'=>array(), 'images'=>array(), 'last_periodic_at'=>0);
$index = bratonien_tools_webdav_source_index_empty($connection_id);
}
else
{
bratonien_tools_cache_warmup_baseline($connection_id, $scan);
bratonien_tools_cache_warmup_status($connection_id, 'baseline', 'Bestehender produktiver Bestand wurde nur als Ausgangszustand erfasst.', array(
$index = bratonien_tools_cache_warmup_build_baseline($connection_id, $scan);
bratonien_tools_cache_warmup_status($connection_id, 'baseline', 'Bestehender Connector-Bestand wurde als Quellenindex erfasst.', array(
'images'=>count($scan['images']),
'albums'=>count($scan['albums']),
'indexed'=>count($index['sources']),
));
bratonien_tools_cache_warmup_log('baseline', array('connection_id'=>$connection_id, 'images'=>count($scan['images']), 'albums'=>count($scan['albums'])));
bratonien_tools_cache_warmup_log('baseline', array('connection_id'=>$connection_id, 'images'=>count($scan['images'])));
return 0;
}
}
if ($mode === 'periodic')
{
$last = (int)($state['last_periodic_at'] ?? 0);
$last = (int)($index['last_periodic_at'] ?? 0);
$interval = max(1, (int)$settings['periodic_hours']) * 3600;
if ($last > 0 && time() - $last < $interval)
{
@@ -745,41 +689,41 @@ function bratonien_tools_cache_warmup_run($connection_id, $mode)
}
}
$selection = bratonien_tools_cache_warmup_select($scan, $state, $mode);
$selected = $selection['images'];
bratonien_tools_cache_warmup_status($connection_id, 'scan', 'Warmup-Eingangsprüfung abgeschlossen.', array(
$current_keys = array();
foreach ($scan['images'] as $image) $current_keys[] = $image['index_key'];
$removed = bratonien_tools_webdav_source_index_prune($index, $current_keys);
$selected = bratonien_tools_cache_warmup_select($scan, $index, $mode);
bratonien_tools_cache_warmup_status($connection_id, 'scan', 'Quellenindex-Abgleich abgeschlossen.', array(
'mode'=>$mode,
'images'=>count($scan['images']),
'indexed'=>count($index['sources']),
'selected'=>count($selected),
'new_albums'=>count($selection['new_albums']),
'removed'=>$removed,
));
if (!$selected)
{
foreach ($scan['albums'] as $album_id) $state['albums'][(string)$album_id] = array('seen_at'=>time());
if ($mode === 'periodic' || $mode === 'manual') $state['last_periodic_at'] = time();
bratonien_tools_cache_warmup_save_state($connection_id, $state);
bratonien_tools_cache_warmup_status($connection_id, 'complete', 'Keine neuen oder geänderten Bilder für den Warmup gefunden.', array('mode'=>$mode));
if ($mode === 'periodic' || $mode === 'manual') $index['last_periodic_at'] = time();
bratonien_tools_webdav_source_index_save($connection_id, $index);
bratonien_tools_cache_warmup_status($connection_id, 'complete', 'Quellenindex unverändert; keine Arbeit für Piwigo.', array(
'mode'=>$mode,
'images'=>count($scan['images']),
'removed'=>$removed,
));
return 0;
}
if ($mode === 'rebuild')
{
foreach ($selected as $image_id=>$image)
{
if (!isset($state['images'][(string)$image_id]) || !is_array($state['images'][(string)$image_id])) $state['images'][(string)$image_id] = array();
unset($state['images'][(string)$image_id]['stage1_signature'], $state['images'][(string)$image_id]['stage2_signature']);
}
}
$force_all = $mode === 'rebuild';
$stage1 = bratonien_tools_cache_warmup_run_stage($connection_id, 1, $selected, $index, $credentials, $settings['batch_size'], $priority_file, $force_all);
$stage1 = bratonien_tools_cache_warmup_run_stage($connection_id, 1, $selected, $state, $credentials, $settings['batch_size'], $priority_file);
$stage2_candidates = array();
foreach ($selected as $image_id=>$image)
{
$saved = isset($state['images'][(string)$image_id]) && is_array($state['images'][(string)$image_id]) ? $state['images'][(string)$image_id] : array();
if (isset($saved['stage1_signature']) && hash_equals((string)$saved['stage1_signature'], $image['signature'])) $stage2_candidates[$image_id] = $image;
$entry = isset($index['sources'][$image['index_key']]) && is_array($index['sources'][$image['index_key']]) ? $index['sources'][$image['index_key']] : array();
if (isset($entry['stage1_signature']) && hash_equals((string)$entry['stage1_signature'], $image['signature'])) $stage2_candidates[$image_id] = $image;
}
$stage2 = bratonien_tools_cache_warmup_run_stage($connection_id, 2, $stage2_candidates, $state, $credentials, $settings['batch_size'], $priority_file);
$stage2 = bratonien_tools_cache_warmup_run_stage($connection_id, 2, $stage2_candidates, $index, $credentials, $settings['batch_size'], $priority_file, $force_all);
if (!empty($stage2['preempted']))
{
@@ -791,15 +735,17 @@ function bratonien_tools_cache_warmup_run($connection_id, $mode)
return ((int)$stage1['failed'] + (int)$stage2['failed']) > 0 ? 2 : 0;
}
foreach ($scan['albums'] as $album_id) $state['albums'][(string)$album_id] = array('seen_at'=>time());
if ($mode === 'periodic' || $mode === 'manual') $state['last_periodic_at'] = time();
bratonien_tools_cache_warmup_save_state($connection_id, $state);
if ($mode === 'periodic' || $mode === 'manual') $index['last_periodic_at'] = time();
bratonien_tools_webdav_source_index_save($connection_id, $index);
$failed = (int)$stage1['failed'] + (int)$stage2['failed'];
bratonien_tools_cache_warmup_status($connection_id, $failed ? 'error' : 'complete', $failed ? 'Warmup mit einzelnen Fehlern beendet.' : ($mode === 'rebuild' ? 'WebDAV-Bildcache vollständig wiederaufgebaut.' : 'Warmup vollständig beendet.'), array(
bratonien_tools_cache_warmup_status($connection_id, $failed ? 'error' : 'complete', $failed ? 'Warmup mit einzelnen Fehlern beendet.' : ($mode === 'rebuild' ? 'WebDAV-Bildcache durch Piwigo vollständig neu angefordert.' : 'Quellenänderungen durch Piwigo verarbeitet.'), array(
'mode'=>$mode,
'selected'=>count($selected),
'removed'=>$removed,
'stage1_failed'=>(int)$stage1['failed'],
'stage2_failed'=>(int)$stage2['failed'],
'indexed'=>count($index['sources']),
));
return $failed ? 2 : 0;
}

View File

@@ -1,18 +1,18 @@
<div id="bratonien-webdav-warmup-card" class="bratonien-card" style="margin-top:16px;">
<h4>WebDAV-Cache-Warmup</h4>
<p>Bereitet fehlende Piwigo-Bildgrößen für neue Nextcloud-Inhalte vor. Bratonien Tools lädt die Quellen nur temporär in Batches; die eigentlichen Derivate erzeugt ausschließlich Piwigo über seinen normalen <code>i.php</code>-Pfad.</p>
<p class="bratonien-main-cache__warning"><strong>Patchphase 0.9.7.1.8:</strong> Die Automatik ist standardmäßig deaktiviert. Bestehende produktive Bilder werden beim ersten Lauf nur als Ausgangsbestand erfasst und nicht automatisch neu aufgebaut.</p>
<h4>WebDAV-Quellenindex &amp; Cache-Warmup</h4>
<p>Bratonien Tools vergleicht den aktuellen Nextcloud-/Connector-Bestand mit einem eigenen kompakten Quellenindex. Nur neue, geänderte oder noch nicht vollständig verarbeitete Quellen werden an Piwigo übergeben. Der Piwigo-Bildcache selbst entscheidet nicht, ob eine Quelle neu ist.</p>
<p class="bratonien-main-cache__warning"><strong>Patchphase 0.9.7.1.13:</strong> Der Quellenindex speichert unter anderem File-ID, Pfad/Name, ETag, Größe, Format und Abmessungen. Die eigentlichen Derivate erzeugt ausschließlich Piwigo über seinen normalen <code>i.php</code>-Pfad.</p>
<form method="post" class="bratonien-worker-settings">
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
<label><input type="checkbox" name="webdav_warmup_enabled" value="1" {if $CACHE_WORKERS.webdav_warmup.enabled}checked{/if}> Automatischen WebDAV-Warmup aktivieren</label>
<label>Bilder pro Batch: <input type="number" name="webdav_warmup_batch_size" value="{$CACHE_WORKERS.webdav_warmup.batch_size}" min="1" max="50" step="1"></label>
<label>Eingangsprüfung: <input type="number" name="webdav_warmup_periodic_hours" value="{$CACHE_WORKERS.webdav_warmup.periodic_hours}" min="1" max="168" step="1"> Stunden</label>
<label>Index-Abgleich: <input type="number" name="webdav_warmup_periodic_hours" value="{$CACHE_WORKERS.webdav_warmup.periodic_hours}" min="1" max="168" step="1"> Stunden</label>
<button class="buttonLike" type="submit" name="bratonien_tool" value="image_cache_webdav_warmup_settings">Warmup-Einstellungen speichern</button>
</form>
<div class="bratonien-form-grid" style="margin-top:12px;">
<span class="bratonien-label">Ablauf</span><span>Neues Album: direkt nach erfolgreichem Connector-Sync · einzelne neue/geänderte Bilder: periodisch oder manuell</span>
<span class="bratonien-label">Änderungserkennung</span><span>Connector-Bestand ↔ eigener Quellenindex; Piwigo-Placeholder und Cache-Dateien sind dafür nicht maßgeblich</span>
<span class="bratonien-label">Stufe 1</span><span>Von Piwigo definierte Derivate bis einschließlich 1920 px längster Kante</span>
<span class="bratonien-label">Stufe 2</span><span>Danach alle übrigen von Piwigo definierten Derivate</span>
<span class="bratonien-label">Letzter Status</span><span><strong>{$CACHE_WORKERS.webdav_warmup.status.state|default:'idle'|escape:html}</strong>{if $CACHE_WORKERS.webdav_warmup.status.message} · {$CACHE_WORKERS.webdav_warmup.status.message|escape:html}{/if}</span>
@@ -21,14 +21,14 @@
<div class="bratonien-actions">
<form method="post">
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
<button class="buttonLike" type="submit" name="bratonien_tool" value="image_cache_webdav_warmup_manual">Jetzt auf neue Bilder prüfen</button>
<button class="buttonLike" type="submit" name="bratonien_tool" value="image_cache_webdav_warmup_manual">Quellenindex jetzt abgleichen</button>
</form>
<form method="post">
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN|escape:html}">
<button class="buttonLike" type="submit" name="bratonien_tool" value="image_cache_webdav_warmup_audit">Produktive Pfade prüfen</button>
</form>
</div>
<p class="bratonien-base-note">Der Pfadaudit ist schreibgeschützt. Warmup und On-Demand verwenden denselben Bild-Lock; während eines Warmups wird zusätzlich der Connector-Sync-Lock geteilt gehalten, damit Source- und Shadow-Tree nicht ausgetauscht werden können.</p>
<p class="bratonien-base-note">Der Pfadaudit ist schreibgeschützt. Warmup und On-Demand verwenden denselben Bild-Lock; während der produktiven Materialisierung wird zusätzlich der Connector-Sync-Lock geteilt gehalten, damit Source- und Shadow-Tree nicht ausgetauscht werden können.</p>
</div>
{literal}