Compare commits

...

20 Commits

Author SHA1 Message Date
Terranom674
abd0cc5d21 Bump patch version for atomic cache clear 2026-09-03 14:46:11 +02:00
Terranom674
fced2eac49 Route cache clear button through atomic cutover 2026-09-03 14:45:43 +02:00
Terranom674
14aac0d7a4 Make cache clear use atomic directory cutover 2026-09-03 14:45:24 +02:00
Terranom674
3f2c1f8ff7 Load strict WebDAV derivative cache validator 2026-09-03 14:42:13 +02:00
Terranom674
7310d70b93 Add strict WebDAV derivative cache validation 2026-09-03 14:41:44 +02:00
Terranom674
ae73840d40 Bump patch version to 0.9.7.1.10 2026-09-03 14:39:56 +02:00
Terranom674
a0195b499c Keep legacy cache builder away from WebDAV placeholders 2026-09-03 14:39:26 +02:00
Terranom674
0372a74ae9 Bump patch version to 0.9.7.1.9 2026-09-03 14:33:05 +02:00
Terranom674
dbcc55351c Fix Piwigo root resolution in warmup dispatcher 2026-09-03 14:32:24 +02:00
Terranom674
f02d2f0239 Fix Piwigo root resolution in derivative caller 2026-09-03 14:31:54 +02:00
Terranom674
2617175158 Fix Piwigo root resolution in WebDAV warmup audit 2026-09-03 14:31:44 +02:00
Terranom674
362d24b89a Require warmup baseline before enabling automation 2026-09-03 14:19:48 +02:00
Terranom674
459f233b57 Refresh WebDAV mapping inside cache warmup workers 2026-09-03 14:19:03 +02:00
Terranom674
1368c8e5b7 Queue sync warmup until active worker yields 2026-09-03 14:18:04 +02:00
Terranom674
662d22c533 Queue priority warmup behind an active worker 2026-09-03 14:17:40 +02:00
Terranom674
f9188ac5db Remove obsolete full-run warmup guard 2026-09-03 14:17:14 +02:00
Terranom674
5aab3e52ed Route on-demand derivatives through sync guard 2026-09-03 14:17:03 +02:00
Terranom674
6bd14d2b9b Guard on-demand derivatives against connector tree swaps 2026-09-03 14:16:36 +02:00
Terranom674
a63d5fe3c3 Allow connector sync between warmup batches 2026-09-03 14:15:59 +02:00
Terranom674
84456de512 Preempt stage two warmup safely between batches 2026-09-03 14:15:23 +02:00
14 changed files with 457 additions and 78 deletions

View File

@@ -0,0 +1,154 @@
<?php
if (!defined('PHPWG_ROOT_PATH'))
{
die('Hacking attempt!');
}
function bratonien_tools_atomic_cache_remove_tree($root, array &$failed)
{
if (!file_exists($root) && !is_link($root)) return;
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($iterator as $item)
{
$path = $item->getPathname();
if ($item->isLink() || $item->isFile())
{
if (!@unlink($path)) $failed[] = $path;
continue;
}
if ($item->isDir() && !@rmdir($path))
{
$failed[] = $path;
}
}
if (!@rmdir($root) && is_dir($root)) $failed[] = $root;
}
function bratonien_tools_clear_image_cache_atomic()
{
global $conf;
if (!defined('PWG_DERIVATIVE_DIR'))
{
throw new RuntimeException('PWG_DERIVATIVE_DIR ist nicht definiert.');
}
if (bratonien_tools_main_cache_process_active() || bratonien_tools_main_cache_is_running())
{
bratonien_tools_request_main_cache_cancel();
if (!bratonien_tools_wait_main_cache_stopped(10.0))
{
throw new RuntimeException('Der laufende Cache-Aufbau konnte noch nicht beendet werden. Bitte den Abbruch kurz abschließen lassen und erneut leeren.');
}
}
@unlink(bratonien_tools_main_cache_cancel_file());
$piwigo_root = realpath(PHPWG_ROOT_PATH);
if ($piwigo_root === false)
{
throw new RuntimeException('Piwigo-Root konnte für die Cache-Sicherheitsprüfung nicht aufgelöst werden.');
}
$cache_root = rtrim(PHPWG_ROOT_PATH, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.trim(PWG_DERIVATIVE_DIR, '/\\');
$real_cache_root = realpath($cache_root);
if ($real_cache_root === false || !is_dir($real_cache_root))
{
throw new RuntimeException('Bildcache-Verzeichnis wurde nicht gefunden: '.$cache_root);
}
$root_prefix = rtrim($piwigo_root, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR;
if (strpos(rtrim($real_cache_root, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR, $root_prefix) !== 0)
{
throw new RuntimeException('Bildcache liegt außerhalb der Piwigo-Installation. Abbruch.');
}
if (rtrim($real_cache_root, DIRECTORY_SEPARATOR) === rtrim($piwigo_root, DIRECTORY_SEPARATOR))
{
throw new RuntimeException('Bildcache-Pfad entspricht dem Piwigo-Root. Sicherheitsabbruch.');
}
$before = bratonien_tools_scan_image_cache($real_cache_root);
$parent = dirname($real_cache_root);
if (!is_dir($parent) || !is_writable($parent))
{
throw new RuntimeException('Übergeordnetes Bildcache-Verzeichnis ist nicht beschreibbar: '.$parent);
}
$detached = $parent.'/.'.basename($real_cache_root).'.bratonien-clear-'.date('YmdHis').'-'.bin2hex(random_bytes(4));
if (file_exists($detached) || is_link($detached))
{
throw new RuntimeException('Temporäres Cache-Auslagerungsverzeichnis existiert bereits. Abbruch.');
}
// Der gesamte bisherige Cache wird in einem einzigen Rename aus dem aktiven
// Piwigo-Pfad entfernt. Neue Requests schreiben danach nur noch in den neu
// angelegten Cachebaum und können nicht mit dem Löschen des Altbestands
// konkurrieren.
if (!@rename($real_cache_root, $detached))
{
throw new RuntimeException('Bildcache konnte nicht atomar aus dem aktiven Pfad ausgelagert werden.');
}
$mode = isset($conf['chmod_value']) ? (int)$conf['chmod_value'] : 0755;
$umask = umask(0);
$created = @mkdir($real_cache_root, $mode, true);
umask($umask);
if (!$created && !is_dir($real_cache_root))
{
// Der aktive Pfad muss in diesem Fehlerfall wiederhergestellt werden.
if (!file_exists($real_cache_root) && @rename($detached, $real_cache_root))
{
throw new RuntimeException('Neuer Bildcache konnte nicht angelegt werden; der bisherige Cache wurde vollständig wiederhergestellt.');
}
throw new RuntimeException('Neuer Bildcache konnte nicht angelegt und der bisherige Cache nicht automatisch wiederhergestellt werden. Manueller Eingriff erforderlich.');
}
@chmod($real_cache_root, $mode);
@file_put_contents($real_cache_root.'/index.htm', 'Not allowed!');
$failed = array();
bratonien_tools_atomic_cache_remove_tree($detached, $failed);
$active = bratonien_tools_scan_image_cache($real_cache_root);
bratonien_tools_write_main_cache_status(array(
'state'=>'idle',
'message'=>'Bildcache wurde atomar geleert. Kein manueller Cache-Aufbau aktiv.',
));
if ($failed)
{
throw new RuntimeException(sprintf(
'Der aktive Bildcache wurde erfolgreich geleert und neu angelegt, aber %d Datei(en)/Verzeichnis(se) des ausgelagerten Altbestands konnten nicht entfernt werden. Erste problematische Stelle: %s',
count($failed),
$failed[0]
));
}
$message = sprintf(
'Bildcache atomar geleert: %d alte Datei(en) (%s) entfernt, davon %d Custom-Derivate.',
$before['files'],
bratonien_tools_format_bytes($before['bytes']),
$before['custom']
);
if ($active['files'] > 0)
{
$message .= sprintf(
' Während bzw. unmittelbar nach dem Umschalten wurden bereits %d neue Derivatdatei(en) durch laufende Anfragen erzeugt; diese gehören zum neuen Cache und sind kein Rest des gelöschten Bestands.',
$active['files']
);
}
else
{
$message .= ' Der neu aktive Cache ist zum Abschluss der Prüfung leer.';
}
return array('message'=>$message);
}

View File

@@ -5,6 +5,7 @@ if (!defined('PHPWG_ROOT_PATH'))
}
require_once(BRATONIEN_TOOLS_PATH . 'tools/image_cache.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/cache_clear_atomic.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'tools/watermark.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'tools/watermark_profiles.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'tools/watermark_settings.inc.php');
@@ -28,7 +29,7 @@ require_once(BRATONIEN_TOOLS_PATH . 'include/webdav_warmup_settings.inc.php');
function bratonien_tools_get_tools()
{
return array(
'image_cache_clear' => array('handler' => 'bratonien_tools_clear_image_cache'),
'image_cache_clear' => array('handler' => 'bratonien_tools_clear_image_cache_atomic'),
'image_cache_build' => array('handler' => 'bratonien_tools_start_main_cache_build'),
'image_cache_cancel' => array('handler' => 'bratonien_tools_cancel_main_cache_build'),
'image_cache_worker_settings' => array('handler' => 'bratonien_tools_save_cache_worker_settings'),

View File

@@ -0,0 +1,66 @@
<?php
if (!defined('PHPWG_ROOT_PATH'))
{
die('Hacking attempt!');
}
function bratonien_tools_webdav_derivative_cache_valid($target_path, $derivative, $params=null, &$reason=null)
{
$reason = '';
$target_path = (string)$target_path;
if ($target_path === '' || !is_file($target_path) || !is_readable($target_path))
{
$reason = 'missing';
return false;
}
$cache_root = PHPWG_ROOT_PATH.PWG_DERIVATIVE_DIR;
if (strpos($target_path, $cache_root) !== 0)
{
$reason = 'outside-cache';
return false;
}
clearstatcache(true, $target_path);
$bytes = @filesize($target_path);
if ($bytes === false || $bytes < 1)
{
$reason = 'empty';
return false;
}
$actual = @getimagesize($target_path);
if (!is_array($actual) || (int)($actual[0] ?? 0) < 1 || (int)($actual[1] ?? 0) < 1)
{
$reason = 'not-image';
return false;
}
if (is_object($derivative) && method_exists($derivative, 'get_size'))
{
$expected = $derivative->get_size();
$expected_width = (int)($expected[0] ?? 0);
$expected_height = (int)($expected[1] ?? 0);
if ($expected_width > 0 && $expected_height > 0)
{
if ((int)$actual[0] !== $expected_width || (int)$actual[1] !== $expected_height)
{
$reason = 'dimension-mismatch:'.(int)$actual[0].'x'.(int)$actual[1].'!='.$expected_width.'x'.$expected_height;
return false;
}
}
}
if (is_object($params) && isset($params->last_mod_time))
{
$mtime = @filemtime($target_path);
if ($mtime === false || (int)$mtime < (int)$params->last_mod_time)
{
$reason = 'stale-params';
return false;
}
}
$reason = 'valid';
return true;
}

View File

@@ -10,7 +10,9 @@ function bratonien_tools_webdav_materialize_source_info($image_id)
$image_id = (int)$image_id;
if ($image_id < 1) return null;
if (array_key_exists($image_id, $cache)) return $cache[$image_id];
$warmup_cli = PHP_SAPI === 'cli'
&& strpos((string)($_SERVER['HTTP_USER_AGENT'] ?? ''), 'Bratonien-WebDAV-Cache-Warmup/') === 0;
if (!$warmup_cli && array_key_exists($image_id, $cache)) return $cache[$image_id];
$result = pwg_query('SELECT path, coi FROM '.IMAGES_TABLE.' WHERE id='.$image_id.' LIMIT 1');
if (!pwg_db_num_rows($result)) return $cache[$image_id] = null;
@@ -126,6 +128,7 @@ function bratonien_tools_webdav_materialize_source_info($image_id)
'fileid'=>$fileid,
'width'=>$width,
'height'=>$height,
'state_dir'=>$state_dir,
'coi'=>$row['coi'] ?? null,
);
}
@@ -244,7 +247,7 @@ function bratonien_tools_webdav_materialize_after_signature($image_id, $variant,
function bratonien_tools_webdav_materialize_derivative_url($image_id, $variant, array $info, $after_url='')
{
$url = get_root_url().'plugins/'.BRATONIEN_TOOLS_ID.'/webdav-derivative.php?id='.(int)$image_id.'&variant='.rawurlencode($variant);
$url = get_root_url().'plugins/'.BRATONIEN_TOOLS_ID.'/webdav-derivative-guard.php?id='.(int)$image_id.'&variant='.rawurlencode($variant);
if ($after_url !== '')
{
$url .= '&after='.rawurlencode(bratonien_tools_webdav_materialize_b64url_encode($after_url));

View File

@@ -32,6 +32,25 @@ function bratonien_tools_get_webdav_warmup_settings()
return $settings;
}
function bratonien_tools_webdav_warmup_missing_baselines()
{
if (!function_exists('bratonien_tools_nc_connector_connections') || !function_exists('bratonien_tools_nc_connector_is_webdav'))
{
return array('Connector-Verbindungen konnten nicht geprüft werden.');
}
$missing = array();
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;
$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;
}
return $missing;
}
function bratonien_tools_save_webdav_warmup_settings()
{
$current = bratonien_tools_get_webdav_warmup_settings();
@@ -41,6 +60,17 @@ 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)
);
}
}
$payload = array(
'enabled'=>$enabled,
'batch_size'=>$batch_size,

View File

@@ -35,6 +35,7 @@ if (!defined('BRATONIEN_TOOLS_PATH'))
require_once(BRATONIEN_TOOLS_PATH.'tools/image_cache.inc.php');
require_once(BRATONIEN_TOOLS_PATH.'include/watermark_engine.inc.php');
require_once(BRATONIEN_TOOLS_PATH.'include/webdav_materialize_runtime.inc.php');
require_once(PHPWG_ROOT_PATH.'include/derivative.inc.php');
require_once(PHPWG_ROOT_PATH.'admin/include/image.class.php');
@@ -229,6 +230,7 @@ function bratonien_tools_cache_builder_worker($worker_index, $worker_count, $run
$source_path = $src->get_path();
$image_id = (int)$image_row['id'];
$metadata = null;
$webdav_source = bratonien_tools_webdav_materialize_source_info($image_id);
foreach ($variants as $variant_name => $requested_params)
{
@@ -248,6 +250,28 @@ function bratonien_tools_cache_builder_worker($worker_index, $worker_count, $run
{
$derivative = new DerivativeImage($requested_params, $src);
$target_path = $derivative->get_path();
// WebDAV-Connector-Bilder besitzen absichtlich nur einen Placeholder als
// lokale Quelle. Der alte Full-Cache-Builder darf diesen niemals direkt
// rendern. Fehlende Connector-Derivate gehören ausschließlich in den
// materialisierenden WebDAV-Warmup-/On-Demand-Pfad.
if ($webdav_source)
{
if (
strpos($target_path, PHPWG_ROOT_PATH.PWG_DERIVATIVE_DIR) === 0
&& is_file($target_path)
&& is_readable($target_path)
)
{
$cached++;
}
else
{
$skipped++;
}
continue;
}
if (strpos($target_path, PHPWG_ROOT_PATH.PWG_DERIVATIVE_DIR) !== 0)
{
$skipped++;

View File

@@ -1,7 +1,7 @@
<?php
/*
Plugin Name: Bratonien Tools
Version: 0.9.7.1.8
Version: 0.9.7.1.11
Description: Erweiterbare Administrationswerkzeuge fuer die Bratonien-Piwigo-Installation.
Plugin URI: https://github.com/Terranom674/Piwigo_Bratonien_Tools
Author: Bratonien
@@ -17,6 +17,7 @@ define('BRATONIEN_TOOLS_PATH', PHPWG_PLUGINS_PATH . BRATONIEN_TOOLS_ID . '/');
require_once(BRATONIEN_TOOLS_PATH . 'include/watermark_runtime.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/webdav_image_runtime.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/public_selection.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/picture_navigation.inc.php');

View File

@@ -19,7 +19,7 @@ if ($request === '' || strpos($request, '/i.php?/') !== 0)
exit(2);
}
$piwigo_root = realpath(dirname(__DIR__, 3));
$piwigo_root = realpath(dirname(__DIR__, 4));
if ($piwigo_root === false || !is_file($piwigo_root.'/i.php'))
{
fwrite(STDERR, "Piwigo i.php wurde nicht gefunden.\n");

View File

@@ -1,58 +0,0 @@
#!/usr/bin/env bash
set -Eeuo pipefail
STATE_DIR="${1:-}"
shift || true
[[ -n "$STATE_DIR" ]] || { echo "Connector-State-Verzeichnis fehlt." >&2; exit 2; }
[[ $# -gt 0 ]] || { echo "Warmup-Kommando fehlt." >&2; exit 2; }
mkdir -p -- "$STATE_DIR"
LOCK_FILE="$STATE_DIR/webdav-sync.lock"
PRIORITY_FILE="$STATE_DIR/webdav-cache-warmup-priority-sync"
# Der Connector-Sync nutzt denselben Lock exklusiv. Der Warmup hält ihn für
# seinen gesamten Lauf nur lesend/geteilt. Dadurch kann der Placeholder- und
# Shadow-Tree während Batch-Download, Piwigo-Aufruf und Restore nicht atomar
# unter dem Warmup ausgetauscht werden.
exec 9>"$LOCK_FILE"
flock -s 9
COMMAND=("$@")
set +e
"${COMMAND[@]}"
result=$?
set -e
# Ein Connector-Sync kann während eines bereits laufenden Warmups neue Alben
# melden. Der Dispatcher legt dafür nur eine Prioritätsmarke an; er startet
# keinen zweiten konkurrierenden Worker. Falls der aktive Lauf die Marke noch
# nicht selbst übernehmen konnte, wird sie hier nach seinem sauberen Ende
# garantiert nachgeholt. Dadurch geht ein Sofort-Warmup nie verloren.
if [[ -f "$PRIORITY_FILE" ]]; then
rm -f -- "$PRIORITY_FILE"
SYNC_COMMAND=()
replaced=0
for arg in "${COMMAND[@]}"; do
if [[ "$arg" == --mode=* ]]; then
SYNC_COMMAND+=("--mode=sync")
replaced=1
else
SYNC_COMMAND+=("$arg")
fi
done
if [[ "$replaced" -eq 0 ]]; then
SYNC_COMMAND+=("--mode=sync")
fi
set +e
"${SYNC_COMMAND[@]}"
sync_result=$?
set -e
if [[ "$sync_result" -ne 0 ]]; then
result="$sync_result"
fi
fi
exit "$result"

View File

@@ -0,0 +1,41 @@
#!/usr/bin/env bash
set -Eeuo pipefail
STATE_DIR="${1:-}"
shift || true
[[ -n "$STATE_DIR" ]] || { echo "Connector-State-Verzeichnis fehlt." >&2; exit 2; }
[[ $# -gt 0 ]] || { echo "Warmup-Kommando fehlt." >&2; exit 2; }
PRIORITY_FILE="$STATE_DIR/webdav-cache-warmup-priority-sync"
ATTEMPTS=0
MAX_ATTEMPTS=3600
# Der Dispatcher setzt die Prioritätsmarke vor dem Start. Falls bereits ein
# Warmup läuft, beendet der zusätzliche Worker sich wegen seines Prozess-Locks
# sofort und lässt die Marke unangetastet. Wir probieren deshalb erst dann
# erneut, wenn die Marke noch vorhanden ist. Sobald ein Sync-Worker den Lock
# tatsächlich übernimmt, entfernt er die Marke selbst und arbeitet den neuen
# Bestand ab. Dadurch geht die Sofort-Priorität nicht verloren und es gibt nie
# zwei gleichzeitig arbeitende Warmup-Worker derselben Verbindung.
while [[ -f "$PRIORITY_FILE" ]]; do
ATTEMPTS=$((ATTEMPTS + 1))
if [[ "$ATTEMPTS" -gt "$MAX_ATTEMPTS" ]]; then
echo "Priorisierter WebDAV-Warmup wartet seit zu langer Zeit auf den aktiven Worker." >&2
exit 3
fi
set +e
"$@"
result=$?
set -e
if [[ "$result" -ne 0 ]]; then
exit "$result"
fi
[[ -f "$PRIORITY_FILE" ]] || exit 0
sleep 1
done
exit 0

View File

@@ -61,6 +61,16 @@ function bratonien_tools_cache_warmup_status_file($connection_id)
return PHPWG_ROOT_PATH.PWG_LOCAL_DIR.'bratonien-webdav-warmup.status-'.(int)$connection_id.'.json';
}
function bratonien_tools_cache_warmup_priority_file($state_dir)
{
return rtrim((string)$state_dir, '/').'/webdav-cache-warmup-priority-sync';
}
function bratonien_tools_cache_warmup_priority_pending($priority_file)
{
return is_string($priority_file) && $priority_file !== '' && is_file($priority_file);
}
function bratonien_tools_cache_warmup_write_json($file, array $payload)
{
$directory = dirname($file);
@@ -575,10 +585,16 @@ function bratonien_tools_cache_warmup_stage_pending(array $selected, array $stat
return $pending;
}
function bratonien_tools_cache_warmup_run_stage($connection_id, $stage, array $selected, array &$state, array $credentials, $batch_size)
function bratonien_tools_cache_warmup_run_stage($connection_id, $stage, array $selected, array &$state, array $credentials, $batch_size, $priority_file='')
{
$pending = bratonien_tools_cache_warmup_stage_pending($selected, $state, $stage);
if (!$pending) return array('ok'=>true, 'success'=>array(), 'failed'=>0);
if (!$pending) return array('ok'=>true, 'success'=>array(), 'failed'=>0, 'preempted'=>false);
if ($stage === 2 && bratonien_tools_cache_warmup_priority_pending($priority_file))
{
bratonien_tools_cache_warmup_log('stage2_preempted', array('connection_id'=>$connection_id, 'point'=>'before_first_batch'));
return array('ok'=>true, 'success'=>array(), 'failed'=>0, 'preempted'=>true);
}
$temp_root = PHPWG_ROOT_PATH.'upload/bratonien-webdav-warmup';
if (!is_dir($temp_root) && !@mkdir($temp_root, 0775, true) && !is_dir($temp_root)) throw new RuntimeException('Warmup-Temp-Verzeichnis konnte nicht angelegt werden.');
@@ -652,8 +668,14 @@ function bratonien_tools_cache_warmup_run_stage($connection_id, $stage, array $s
foreach ($downloads as $file) @unlink($file);
@rmdir($dir);
if ($stage === 2 && bratonien_tools_cache_warmup_priority_pending($priority_file))
{
bratonien_tools_cache_warmup_log('stage2_preempted', array('connection_id'=>$connection_id, 'point'=>'after_batch', 'batch'=>$batch_number));
return array('ok'=>$failed === 0, 'success'=>$success, 'failed'=>$failed, 'preempted'=>true);
}
}
return array('ok'=>$failed === 0, 'success'=>$success, 'failed'=>$failed);
return array('ok'=>$failed === 0, 'success'=>$success, 'failed'=>$failed, 'preempted'=>false);
}
function bratonien_tools_cache_warmup_run($connection_id, $mode)
@@ -670,6 +692,7 @@ function bratonien_tools_cache_warmup_run($connection_id, $mode)
$state_dir = rtrim((string)($credentials['config']['state_dir'] ?? ''), '/');
if ($state_dir === '') throw new RuntimeException('Connector-State-Verzeichnis fehlt.');
if (!is_dir($state_dir) && !@mkdir($state_dir, 0750, true) && !is_dir($state_dir)) throw new RuntimeException('Connector-State-Verzeichnis konnte nicht angelegt werden.');
$priority_file = bratonien_tools_cache_warmup_priority_file($state_dir);
$process_lock = @fopen($state_dir.'/webdav-cache-warmup.lock', 'c');
if (!$process_lock || !@flock($process_lock, LOCK_EX | LOCK_NB))
@@ -681,6 +704,12 @@ function bratonien_tools_cache_warmup_run($connection_id, $mode)
try
{
// Nur der Sync-Worker, der den Prozess-Lock tatsächlich erhalten hat,
// konsumiert seine eigene Prioritätsmarke. Ein zweiter Sync-Aufruf während
// eines laufenden Workers lässt die Marke stehen, damit der aktive Lauf
// Stufe 2 sicher zwischen zwei vollständigen Batches unterbrechen kann.
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)
@@ -723,14 +752,24 @@ function bratonien_tools_cache_warmup_run($connection_id, $mode)
return 0;
}
$stage1 = bratonien_tools_cache_warmup_run_stage($connection_id, 1, $selected, $state, $credentials, $settings['batch_size']);
$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;
}
$stage2 = bratonien_tools_cache_warmup_run_stage($connection_id, 2, $stage2_candidates, $state, $credentials, $settings['batch_size']);
$stage2 = bratonien_tools_cache_warmup_run_stage($connection_id, 2, $stage2_candidates, $state, $credentials, $settings['batch_size'], $priority_file);
if (!empty($stage2['preempted']))
{
bratonien_tools_cache_warmup_status($connection_id, 'preempted', 'Warmup Stufe 2 wurde nach einem vollständigen Batch für neue Sync-Priorität freigegeben.', array(
'mode'=>$mode,
'stage1_failed'=>(int)$stage1['failed'],
'stage2_failed'=>(int)$stage2['failed'],
));
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();

View File

@@ -5,7 +5,7 @@ if (PHP_SAPI !== 'cli')
exit;
}
$piwigo_root = realpath(dirname(__DIR__, 3));
$piwigo_root = realpath(dirname(__DIR__, 4));
if ($piwigo_root === false)
{
fwrite(STDERR, "Piwigo-Root wurde nicht gefunden.\n");

View File

@@ -5,7 +5,7 @@ if (PHP_SAPI !== 'cli')
exit;
}
$piwigo_root = realpath(dirname(__DIR__, 2));
$piwigo_root = realpath(dirname(__DIR__, 3));
if ($piwigo_root === false)
{
fwrite(STDERR, "Piwigo-Root wurde nicht gefunden.\n");
@@ -58,15 +58,15 @@ if (!function_exists('exec'))
}
$worker = realpath(BRATONIEN_TOOLS_PATH.'runtime/lib/webdav-cache-warmup.php');
$guard = realpath(BRATONIEN_TOOLS_PATH.'runtime/lib/run-webdav-cache-warmup.sh');
$priority_waiter = realpath(BRATONIEN_TOOLS_PATH.'runtime/lib/run-webdav-priority-wait.sh');
if (!$worker || !is_file($worker))
{
fwrite(STDERR, "WebDAV-Cache-Warmup-Worker wurde nicht gefunden.\n");
exit(1);
}
if (!$guard || !is_file($guard))
if (!$priority_waiter || !is_file($priority_waiter))
{
fwrite(STDERR, "WebDAV-Cache-Warmup-Schutzskript wurde nicht gefunden.\n");
fwrite(STDERR, "WebDAV-Prioritäts-Warter wurde nicht gefunden.\n");
exit(1);
}
@@ -135,14 +135,22 @@ foreach (bratonien_tools_nc_connector_connections() as $connection)
@chmod($priority_file, 0664);
}
// Der Guard hält webdav-sync.lock für den gesamten Worker-Lauf geteilt.
// Dadurch kann der produktive Source-/Shadow-Tree während Download, Swap,
// Piwigo-Aufruf und Restore nicht von einem Connector-Sync ersetzt werden.
$base = escapeshellarg('/bin/bash').' '.escapeshellarg($guard).' '.escapeshellarg($state_dir)
.' '.escapeshellarg(PHP_BINARY).' '.escapeshellarg($worker)
$worker_command = escapeshellarg(PHP_BINARY).' '.escapeshellarg($worker)
.' --connection-id='.$connection_id
.' --mode='.escapeshellarg($mode);
if ($mode === 'sync')
{
// Ein bereits laufender Worker darf seinen aktuellen Bild-/Batchvorgang
// vollständig restaurieren. Der Waiter startet den priorisierten Sync-Lauf
// danach automatisch, statt die Prioritätsmarke verloren gehen zu lassen.
$base = escapeshellarg('/bin/bash').' '.escapeshellarg($priority_waiter).' '.escapeshellarg($state_dir).' '.$worker_command;
}
else
{
$base = $worker_command;
}
if ($wait)
{
$output = array();

View File

@@ -0,0 +1,70 @@
<?php
define('PHPWG_ROOT_PATH', '../../');
include_once(PHPWG_ROOT_PATH.'include/common.inc.php');
require_once(PHPWG_ROOT_PATH.'include/derivative.inc.php');
if (!defined('BRATONIEN_TOOLS_PATH'))
{
define('BRATONIEN_TOOLS_ID', basename(__DIR__));
define('BRATONIEN_TOOLS_PATH', PHPWG_ROOT_PATH.'plugins/'.BRATONIEN_TOOLS_ID.'/');
}
require_once(BRATONIEN_TOOLS_PATH.'include/webdav_materialize_runtime.inc.php');
$image_id = (int)($_GET['id'] ?? 0);
if ($image_id < 1)
{
http_response_code(400);
header('Content-Type: text/plain; charset=utf-8');
echo 'Ungueltige Derivatanforderung.';
exit;
}
$source = bratonien_tools_webdav_materialize_source_info($image_id);
if (!$source)
{
http_response_code(404);
header('Content-Type: text/plain; charset=utf-8');
echo 'WebDAV-Bildquelle nicht gefunden.';
exit;
}
$state_dir = rtrim((string)($source['state_dir'] ?? ''), '/');
if ($state_dir === '')
{
http_response_code(503);
header('Content-Type: text/plain; charset=utf-8');
echo 'Connector-State-Verzeichnis fehlt.';
exit;
}
$lock = @fopen($state_dir.'/webdav-sync.lock', 'c');
if (!$lock)
{
http_response_code(503);
header('Content-Type: text/plain; charset=utf-8');
echo 'Connector-Sync-Lock konnte nicht geoeffnet werden.';
exit;
}
// On-Demand bleibt parallel zu anderen Bildabrufen moeglich, verhindert aber
// fuer die Dauer dieses einzelnen Derivataufrufs einen exklusiven Connector-
// Tree-Swap. Der bestehende Bild-Lock im eigentlichen Endpoint koordiniert
// weiterhin On-Demand und Warmup fuer dieselbe Bild-ID.
if (!@flock($lock, LOCK_SH))
{
fclose($lock);
http_response_code(503);
header('Content-Type: text/plain; charset=utf-8');
echo 'Connector-Sync-Schutz konnte nicht gesetzt werden.';
exit;
}
try
{
include BRATONIEN_TOOLS_PATH.'webdav-derivative.php';
}
finally
{
@flock($lock, LOCK_UN);
fclose($lock);
}