mirror of
https://github.com/Terranom674/Piwigo_Bratonien_Tools.git
synced 2026-09-19 11:44:31 +00:00
320 lines
12 KiB
PHP
320 lines
12 KiB
PHP
<?php
|
|
if (!defined('PHPWG_ROOT_PATH'))
|
|
{
|
|
die('Hacking attempt!');
|
|
}
|
|
|
|
require_once(BRATONIEN_TOOLS_PATH.'include/webdav_source_index.inc.php');
|
|
|
|
function bratonien_tools_webdav_warmup_settings_file()
|
|
{
|
|
return PHPWG_ROOT_PATH.PWG_LOCAL_DIR.'bratonien-webdav-warmup.settings.json';
|
|
}
|
|
|
|
function bratonien_tools_get_webdav_warmup_settings()
|
|
{
|
|
$settings = array(
|
|
'enabled'=>false,
|
|
'batch_size'=>10,
|
|
'periodic_hours'=>12,
|
|
);
|
|
|
|
$file = bratonien_tools_webdav_warmup_settings_file();
|
|
if (is_file($file) && is_readable($file))
|
|
{
|
|
$raw = @file_get_contents($file);
|
|
$saved = $raw !== false ? json_decode($raw, true) : null;
|
|
if (is_array($saved))
|
|
{
|
|
if (array_key_exists('enabled', $saved)) $settings['enabled'] = (bool)$saved['enabled'];
|
|
if (isset($saved['batch_size'])) $settings['batch_size'] = max(1, min(50, (int)$saved['batch_size']));
|
|
if (isset($saved['periodic_hours'])) $settings['periodic_hours'] = max(1, min(168, (int)$saved['periodic_hours']));
|
|
}
|
|
}
|
|
return $settings;
|
|
}
|
|
|
|
function bratonien_tools_webdav_warmup_connections()
|
|
{
|
|
$result = array();
|
|
if (!function_exists('bratonien_tools_nc_connector_connections') || !function_exists('bratonien_tools_nc_connector_is_webdav')) return $result;
|
|
|
|
foreach (bratonien_tools_nc_connector_connections() as $connection)
|
|
{
|
|
if (empty($connection['enabled']) || !bratonien_tools_nc_connector_is_webdav($connection)) continue;
|
|
$connection_id = (int)($connection['id'] ?? 0);
|
|
if ($connection_id < 1) continue;
|
|
$config = isset($connection['config']) && is_array($connection['config']) ? $connection['config'] : array();
|
|
$state_dir = rtrim((string)($config['state_dir'] ?? ''), '/');
|
|
if ($state_dir === '') continue;
|
|
$result[$connection_id] = array('id'=>$connection_id, 'state_dir'=>$state_dir, 'connection'=>$connection);
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
function bratonien_tools_webdav_warmup_status_file_for_connection($connection_id)
|
|
{
|
|
return PHPWG_ROOT_PATH.PWG_LOCAL_DIR.'bratonien-webdav-warmup.status-'.(int)$connection_id.'.json';
|
|
}
|
|
|
|
function bratonien_tools_webdav_warmup_write_status($connection_id, $state, $message, array $extra=array())
|
|
{
|
|
$file = bratonien_tools_webdav_warmup_status_file_for_connection($connection_id);
|
|
$payload = array_merge(array(
|
|
'state'=>(string)$state,
|
|
'message'=>(string)$message,
|
|
'connection_id'=>(int)$connection_id,
|
|
'updated_at'=>time(),
|
|
), $extra);
|
|
$json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
|
if ($json === false) throw new RuntimeException('WebDAV-Worker-Status konnte nicht serialisiert werden.');
|
|
$directory = dirname($file);
|
|
if (!is_dir($directory) && !@mkdir($directory, 0775, true) && !is_dir($directory)) throw new RuntimeException('WebDAV-Worker-Statusverzeichnis konnte nicht angelegt werden.');
|
|
$tmp = $file.'.tmp-'.bin2hex(random_bytes(4));
|
|
if (@file_put_contents($tmp, $json."\n", LOCK_EX) === false) throw new RuntimeException('WebDAV-Worker-Status konnte nicht geschrieben werden.');
|
|
@chmod($tmp, 0664);
|
|
if (!@rename($tmp, $file))
|
|
{
|
|
@unlink($tmp);
|
|
throw new RuntimeException('WebDAV-Worker-Status konnte nicht atomar gespeichert werden.');
|
|
}
|
|
}
|
|
|
|
function bratonien_tools_invalidate_webdav_cache_completion($reason='Piwigo-Bildcache wurde geleert.')
|
|
{
|
|
$invalidated = 0;
|
|
foreach (bratonien_tools_webdav_warmup_connections() as $connection_id=>$runtime)
|
|
{
|
|
$index = bratonien_tools_webdav_source_index_load($runtime['state_dir'], $connection_id);
|
|
if (is_array($index))
|
|
{
|
|
foreach ($index['sources'] as &$entry)
|
|
{
|
|
if (!is_array($entry)) continue;
|
|
unset($entry['stage1_signature'], $entry['stage2_signature']);
|
|
}
|
|
unset($entry);
|
|
bratonien_tools_webdav_source_index_save($runtime['state_dir'], $connection_id, $index);
|
|
$invalidated++;
|
|
}
|
|
|
|
bratonien_tools_webdav_warmup_write_status(
|
|
$connection_id,
|
|
'idle',
|
|
$reason.' Der Quellenindex bleibt erhalten; seine Cache-Fertigmarkierungen wurden verworfen.',
|
|
array('cache_invalidated'=>true)
|
|
);
|
|
}
|
|
return $invalidated;
|
|
}
|
|
|
|
function bratonien_tools_save_webdav_warmup_settings()
|
|
{
|
|
$current = bratonien_tools_get_webdav_warmup_settings();
|
|
$enabled = !empty($_POST['webdav_warmup_enabled']);
|
|
$batch_size = isset($_POST['webdav_warmup_batch_size']) ? (int)$_POST['webdav_warmup_batch_size'] : (int)$current['batch_size'];
|
|
$periodic_hours = isset($_POST['webdav_warmup_periodic_hours']) ? (int)$_POST['webdav_warmup_periodic_hours'] : (int)$current['periodic_hours'];
|
|
$batch_size = max(1, min(50, $batch_size));
|
|
$periodic_hours = max(1, min(168, $periodic_hours));
|
|
|
|
$payload = array(
|
|
'enabled'=>$enabled,
|
|
'batch_size'=>$batch_size,
|
|
'periodic_hours'=>$periodic_hours,
|
|
);
|
|
$json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
|
if ($json === false) throw new RuntimeException('Warmup-Einstellungen konnten nicht serialisiert werden.');
|
|
|
|
$file = bratonien_tools_webdav_warmup_settings_file();
|
|
$directory = dirname($file);
|
|
if (!is_dir($directory) && !@mkdir($directory, 0775, true) && !is_dir($directory))
|
|
{
|
|
throw new RuntimeException('Warmup-Einstellungsverzeichnis konnte nicht angelegt werden.');
|
|
}
|
|
$tmp = $file.'.tmp-'.bin2hex(random_bytes(4));
|
|
if (@file_put_contents($tmp, $json."\n", LOCK_EX) === false)
|
|
{
|
|
throw new RuntimeException('Warmup-Einstellungen konnten nicht gespeichert werden.');
|
|
}
|
|
@chmod($tmp, 0664);
|
|
if (!@rename($tmp, $file))
|
|
{
|
|
@unlink($tmp);
|
|
throw new RuntimeException('Warmup-Einstellungen konnten nicht atomar gespeichert werden.');
|
|
}
|
|
|
|
return array('message'=>sprintf(
|
|
'WebDAV-Worker gespeichert: %s, %d Bilder pro Batch, Shadow-Tree-Abgleich alle %d Stunden. Der Worker-Index wird bei Bedarf automatisch aus dem Shadow-Tree aufgebaut.',
|
|
$enabled ? 'automatisch aktiv' : 'Automatik deaktiviert',
|
|
$batch_size,
|
|
$periodic_hours
|
|
));
|
|
}
|
|
|
|
function bratonien_tools_webdav_warmup_php_cli()
|
|
{
|
|
if (function_exists('bratonien_tools_find_php_cli'))
|
|
{
|
|
$php = bratonien_tools_find_php_cli();
|
|
if ($php) return $php;
|
|
}
|
|
foreach (array('/usr/bin/php', '/usr/bin/php8.4', '/usr/bin/php8.3', '/usr/local/bin/php') as $candidate)
|
|
{
|
|
if (is_file($candidate) && is_executable($candidate)) return $candidate;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function bratonien_tools_start_webdav_warmup_mode($mode, $message)
|
|
{
|
|
if (!function_exists('exec')) throw new RuntimeException('PHP exec() ist deaktiviert; Worker kann nicht gestartet werden.');
|
|
$php = bratonien_tools_webdav_warmup_php_cli();
|
|
if (!$php) throw new RuntimeException('PHP-CLI wurde für den WebDAV-Worker nicht gefunden.');
|
|
$dispatcher = realpath(BRATONIEN_TOOLS_PATH.'runtime/webdav-warmup-dispatch.php');
|
|
if (!$dispatcher || !is_file($dispatcher)) throw new RuntimeException('WebDAV-Worker-Dispatcher wurde nicht gefunden.');
|
|
if (!in_array($mode, array('manual','rebuild'), true)) throw new RuntimeException('Ungültiger manueller Worker-Modus.');
|
|
|
|
$connections = bratonien_tools_webdav_warmup_connections();
|
|
if (!$connections) throw new RuntimeException('Keine aktive WebDAV-Verbindung mit vollständiger Runtime gefunden.');
|
|
|
|
$run_id = date('YmdHis').'-'.bin2hex(random_bytes(4));
|
|
foreach ($connections as $connection_id=>$runtime)
|
|
{
|
|
bratonien_tools_webdav_warmup_write_status(
|
|
$connection_id,
|
|
'queued',
|
|
$mode === 'rebuild'
|
|
? 'Cache-Rebuild wurde angefordert. Der Worker wartet auf den Start und gleicht danach Shadow-Tree und eigenen Index ab.'
|
|
: 'Cache-Abgleich wurde angefordert. Bereits erfolgreich abgeschlossene Stufen bleiben erhalten; geladen werden nur Quellen mit fehlender Stufe 1 oder Stufe 2.',
|
|
array('mode'=>$mode, 'run_id'=>$run_id)
|
|
);
|
|
}
|
|
|
|
$log = PHPWG_ROOT_PATH.PWG_LOCAL_DIR.'bratonien-webdav-warmup-dispatch.log';
|
|
$command = 'nohup '.escapeshellarg($php).' '.escapeshellarg($dispatcher).' --mode='.escapeshellarg($mode).' >> '.escapeshellarg($log).' 2>&1 < /dev/null & echo $!';
|
|
$output = array();
|
|
$exit = 1;
|
|
@exec($command, $output, $exit);
|
|
$pid = isset($output[0]) ? (int)$output[0] : 0;
|
|
if ($exit !== 0 || $pid <= 0)
|
|
{
|
|
foreach ($connections as $connection_id=>$runtime)
|
|
{
|
|
bratonien_tools_webdav_warmup_write_status($connection_id, 'error', 'WebDAV-Worker konnte nicht gestartet werden.', array('mode'=>$mode, 'run_id'=>$run_id));
|
|
}
|
|
throw new RuntimeException('WebDAV-Worker konnte nicht gestartet werden.');
|
|
}
|
|
|
|
return array('message'=>$message, 'pid'=>$pid, 'run_id'=>$run_id);
|
|
}
|
|
|
|
function bratonien_tools_start_webdav_warmup_manual()
|
|
{
|
|
return bratonien_tools_start_webdav_warmup_mode(
|
|
'manual',
|
|
'WebDAV-Worker: Shadow-Tree und Worker-Index werden abgeglichen; vollständig erledigte Quellen werden nicht aus Nextcloud geladen.'
|
|
);
|
|
}
|
|
|
|
function bratonien_tools_start_webdav_cache_rebuild()
|
|
{
|
|
return bratonien_tools_start_webdav_warmup_mode(
|
|
'rebuild',
|
|
'WebDAV-Bildcache: der vollständige aktuelle Shadow-Bestand wird erneut an Piwigo übergeben.'
|
|
);
|
|
}
|
|
|
|
function bratonien_tools_has_local_cache_sources()
|
|
{
|
|
if (!function_exists('bratonien_tools_webdav_materialize_source_info')) return true;
|
|
|
|
$result = pwg_query('SELECT id FROM '.IMAGES_TABLE.' ORDER BY id');
|
|
while ($row = pwg_db_fetch_assoc($result))
|
|
{
|
|
$image_id = (int)($row['id'] ?? 0);
|
|
if ($image_id < 1) continue;
|
|
if (!bratonien_tools_webdav_materialize_source_info($image_id)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function bratonien_tools_start_combined_image_cache_build()
|
|
{
|
|
// Der normale "Piwigo-Bildcache aufbauen"-Lauf ist immer inkrementell.
|
|
// Der Worker liest zuerst ausschließlich Shadow-Tree/Mapping und seinen
|
|
// eigenen Index. Nur Quellen, denen laut Index Stufe 1 oder Stufe 2 fehlt,
|
|
// dürfen anschließend aus Nextcloud geladen werden. Ein kompletter
|
|
// Neuaufbau entsteht nur nach einer echten Cache-Invalidierung (z.B. Cache
|
|
// leeren), denn genau dort werden die Stage-Signaturen bewusst entfernt.
|
|
$webdav = bratonien_tools_start_webdav_warmup_manual();
|
|
$main = array('started'=>false, 'message'=>'');
|
|
|
|
if (bratonien_tools_has_local_cache_sources())
|
|
{
|
|
$main = bratonien_tools_start_main_cache_build();
|
|
}
|
|
else
|
|
{
|
|
bratonien_tools_write_main_cache_status(array(
|
|
'state'=>'complete',
|
|
'message'=>'Lokaler Piwigo-Teil nicht erforderlich. Der WebDAV-Worker arbeitet ausschließlich mit Shadow-Tree und eigenem Index.',
|
|
'total'=>0,
|
|
'completed'=>0,
|
|
'generated'=>0,
|
|
'cached'=>0,
|
|
'skipped'=>0,
|
|
'errors'=>0,
|
|
'current'=>'',
|
|
'worker_count'=>0,
|
|
));
|
|
$main['message'] = 'Lokaler Piwigo-Teil nicht gestartet, weil keine lokalen Bildquellen vorhanden sind.';
|
|
}
|
|
|
|
$parts = array();
|
|
if (!empty($main['message'])) $parts[] = $main['message'];
|
|
if (!empty($webdav['message'])) $parts[] = $webdav['message'];
|
|
|
|
return array(
|
|
'started'=>!empty($main['started']) || !empty($webdav['pid']),
|
|
'message'=>implode(' ', $parts),
|
|
);
|
|
}
|
|
|
|
function bratonien_tools_run_webdav_warmup_audit()
|
|
{
|
|
if (!function_exists('exec')) throw new RuntimeException('PHP exec() ist deaktiviert; Pfadaudit kann nicht gestartet werden.');
|
|
$php = bratonien_tools_webdav_warmup_php_cli();
|
|
if (!$php) throw new RuntimeException('PHP-CLI wurde für den Pfadaudit nicht gefunden.');
|
|
$audit = realpath(BRATONIEN_TOOLS_PATH.'runtime/lib/webdav-warmup-audit.php');
|
|
if (!$audit || !is_file($audit)) throw new RuntimeException('WebDAV-Pfadaudit wurde nicht gefunden.');
|
|
|
|
$output = array();
|
|
$exit = 1;
|
|
@exec(escapeshellarg($php).' '.escapeshellarg($audit).' 2>&1', $output, $exit);
|
|
$text = trim(implode(' | ', array_slice($output, -8)));
|
|
if ($exit !== 0) throw new RuntimeException('WebDAV-Pfadaudit meldet einen unsicheren Zustand'.($text !== '' ? ': '.$text : '.'));
|
|
return array('message'=>'WebDAV-Pfadaudit erfolgreich'.($text !== '' ? ': '.$text : '.'));
|
|
}
|
|
|
|
function bratonien_tools_get_webdav_warmup_status()
|
|
{
|
|
$status = array(
|
|
'state'=>'idle',
|
|
'message'=>'Noch kein Shadow-Tree-/Worker-Lauf protokolliert.',
|
|
'updated_at'=>0,
|
|
);
|
|
$files = glob(PHPWG_ROOT_PATH.PWG_LOCAL_DIR.'bratonien-webdav-warmup.status-*.json');
|
|
if (!$files) return $status;
|
|
|
|
$latest = null;
|
|
foreach ($files as $file)
|
|
{
|
|
if (!is_readable($file)) continue;
|
|
$raw = @file_get_contents($file);
|
|
$data = $raw !== false ? json_decode($raw, true) : null;
|
|
if (!is_array($data)) continue;
|
|
if ($latest === null || (int)($data['updated_at'] ?? 0) > (int)($latest['updated_at'] ?? 0)) $latest = $data;
|
|
}
|
|
return is_array($latest) ? array_merge($status, $latest) : $status;
|
|
}
|