Compare commits

..

66 Commits

Author SHA1 Message Date
Terranom674
531bffa973 Bereinige versehentlich angelegte Leerdatei 2026-08-19 22:17:02 +02:00
Terranom674
5ed53dca8d noop 2026-08-19 22:15:46 +02:00
Terranom674
d7628d1dac Version auf 0.9.7.8 setzen 2026-08-19 22:02:59 +02:00
Terranom674
7e9c3ae05f 0.9.7.8: Modus-Galeriederivat vorab erzeugen 2026-08-19 22:02:43 +02:00
Terranom674
b64e46a549 Merge pull request #29 from Terranom674/fix/0974-gallery-derivative-only
0.9.7.7: Connector-Altbestand einmalig zurücksetzen
2026-08-19 21:50:57 +02:00
Terranom674
779ba80dfe Jetzt-abgleichen global und verbindungsbezogen erlauben 2026-08-19 21:50:18 +02:00
Terranom674
ebbc183f87 Globalen Jetzt-abgleichen-Lauf erhalten 2026-08-19 21:49:45 +02:00
Terranom674
f3b5002f4d run-all auf angeforderte Verbindung filtern 2026-08-19 21:48:05 +02:00
Terranom674
6c393bbcfc Native Runner auf einzelne Verbindung begrenzen 2026-08-19 21:47:40 +02:00
Terranom674
829381676d Manuellen Abgleich auf ausgewählte Verbindung begrenzen 2026-08-19 21:47:25 +02:00
Terranom674
6382cee491 Jetzt-abgleichen verbindungsbezogen starten 2026-08-19 21:47:01 +02:00
Terranom674
69c3932e9b CI für 0.9.7.7 auslösen 2026-08-19 21:45:16 +02:00
Terranom674
747a76c066 Altbestand beim Update auf 0.9.7.7 einmalig bereinigen 2026-08-19 21:45:06 +02:00
Terranom674
5024757136 Einmaligen Connector-Altbestand-Reset ergänzen 2026-08-19 21:44:52 +02:00
Terranom674
5125d2e247 0.9.7.7 vorbereiten 2026-08-19 21:44:39 +02:00
Terranom674
6607c4aefe Merge pull request #28 from Terranom674/fix/0974-gallery-derivative-only
0.9.7.6: Galerie-Hotpath entlasten
2026-08-19 21:38:09 +02:00
Terranom674
32f2ceaaec cleanup: temporaeren CI-Trigger entfernen 2026-08-19 21:37:19 +02:00
Terranom674
5a5669d976 ci: trigger 0.9.7.6 2026-08-19 21:36:46 +02:00
Terranom674
e4ab277cfc 0.9.7.6: Wasserzeichen-Engine pro Request nur einmal initialisieren 2026-08-19 21:36:37 +02:00
Terranom674
9e07cd8042 0.9.7.6: Request-Scheduler aus Galeriepfad entfernen 2026-08-19 21:35:50 +02:00
Terranom674
588f1fc100 0.9.7.6: WebDAV-Hotpath pro Request cachen 2026-08-19 21:35:06 +02:00
Terranom674
a23fc81190 Merge pull request #27 from Terranom674/fix/0974-gallery-derivative-only
0.9.7.5: Alt-Derivate reparieren und Galerie beschleunigen
2026-08-19 21:30:24 +02:00
Terranom674
ff5af83e65 Rebuild only mismatched legacy gallery derivatives 2026-08-19 21:29:31 +02:00
Terranom674
c61d8e32cc Use fast WebDAV gallery derivative filter 2026-08-19 21:28:47 +02:00
Terranom674
38c4955f70 Add fast WebDAV gallery derivative path 2026-08-19 21:28:29 +02:00
Terranom674
5c217288a8 Bump version to 0.9.7.5 2026-08-19 21:27:17 +02:00
Terranom674
17c79ce8ab Restore branch to 0.9.7.4 baseline 2026-08-19 21:27:03 +02:00
Terranom674
7746d26c9f Remove accidental temporary file 2026-08-19 21:26:47 +02:00
Terranom674
d05ab28549 noop 2026-08-19 21:26:19 +02:00
Terranom674
55b5be56b6 Merge pull request #26 from Terranom674/fix/0974-gallery-derivative-only
0.9.7.4: nur Galeriederivat beim WebDAV-Import
2026-08-19 21:23:35 +02:00
Terranom674
ba0b5af417 Release 0.9.7.4 2026-08-19 21:12:39 +02:00
Terranom674
315af9641c Build only gallery derivative from WebDAV preview 2026-08-19 21:12:24 +02:00
Terranom674
77ae007a28 Merge pull request #25 from Terranom674/fix/0973-webdav-root-altimports
0.9.7.3: Altimporte aus WebDAV-Stammverzeichnis reparieren
2026-08-19 21:02:13 +02:00
Terranom674
e5be2b3043 Release 0.9.7.3 2026-08-19 21:01:26 +02:00
Terranom674
3fe9527537 Fix root WebDAV alt-import derivative detection 2026-08-19 21:01:00 +02:00
Terranom674
fb1f12d608 0.9.7.2: sichtbarer Abgleich und sicherer WebDAV-Import
0.9.7.2: sichtbarer Abgleich und sicherer WebDAV-Import
2026-08-19 20:53:28 +02:00
Terranom674
017cbdbd3b 0.9.7.2: private Alben nicht von öffentlichen Regeln erben lassen 2026-08-19 20:52:16 +02:00
Terranom674
9aaa1a47b3 0.9.7.2: WebDAV-Sync nicht-destruktiv machen 2026-08-19 20:51:23 +02:00
Terranom674
fc3d855e5e 0.9.7.2: Version anheben 2026-08-19 20:49:18 +02:00
Terranom674
4847d2ff11 0.9.7.2: aktuellen Schedulerstatus in Statuskarte spiegeln 2026-08-19 20:48:48 +02:00
Terranom674
0b405e9efb 0.9.7.2: Abgleich sofort als angefordert markieren 2026-08-19 20:48:25 +02:00
Terranom674
2c302b356d Merge pull request #23 from Terranom674/fix/0971-webdav-public-ip
0.9.7.1: WebDAV über öffentliche Ziel-IP
2026-08-19 20:43:19 +02:00
Terranom674
5c62dbd9be 0.9.7.1: clean pinned WebDAV resolver implementation 2026-08-19 20:41:46 +02:00
Terranom674
5f81cc9ce9 0.9.7.1: bump plugin version 2026-08-19 20:40:57 +02:00
Terranom674
a5be16f88e 0.9.7.1: stream WebDAV originals through resolved target IP 2026-08-19 20:40:40 +02:00
Terranom674
6b082ba8b5 0.9.7.1: fetch WebDAV previews through resolved target IP 2026-08-19 20:40:19 +02:00
Terranom674
5396f50bc0 0.9.7.1: resolve remote Nextcloud target before WebDAV scan 2026-08-19 20:39:54 +02:00
Terranom674
a54df74e67 0.9.7.1: pin WebDAV runtime to resolved target IP 2026-08-19 20:39:27 +02:00
Terranom674
986eb530d7 0.9.7.1: scan remote Nextcloud through resolved IP transport 2026-08-19 20:38:52 +02:00
Terranom674
b9adcbddac 0.9.7.1: use transport-aware WebDAV wizard handlers 2026-08-19 20:38:25 +02:00
Terranom674
bb434b9b02 0.9.7.1: route wizard and WebDAV calls through resolved target IP 2026-08-19 20:37:59 +02:00
Terranom674
aa5c2f7f0c 0.9.7.1: add runtime Nextcloud target resolver 2026-08-19 20:37:23 +02:00
Terranom674
aa803a4149 0.9.7.1: add generic Nextcloud transport resolver 2026-08-19 20:35:40 +02:00
Terranom674
ad39b9f0d2 Merge pull request #22 from Terranom674/fix/09630-native-piwigo-scheduler
0.9.6.30: NC Connector nativ in Piwigo ausführen
2026-08-19 20:18:39 +02:00
Terranom674
aa8beb3794 0.9.6.30: globales Lauf-Lock gegen Parallelstarts 2026-08-19 20:17:47 +02:00
Terranom674
3ad6e9eb8e 0.9.6.30: globales Lauf-Lock an run-all übergeben 2026-08-19 20:17:24 +02:00
Terranom674
21118f6fb3 0.9.6.30: Bildaufbereitung ohne systemd im nativen Modus 2026-08-19 20:16:48 +02:00
Terranom674
c23302197a 0.9.6.30: WebDAV-Runtime unter Piwigo _data ablegen 2026-08-19 20:16:19 +02:00
Terranom674
d54aa482f7 0.9.6.30: Runtime für nativen Piwigo-Scheduler vorbereiten 2026-08-19 20:15:41 +02:00
Terranom674
25133bc895 0.9.6.30: Jetzt-abgleichen auf nativen Scheduler umstellen 2026-08-19 20:15:22 +02:00
Terranom674
62cc1af8bf 0.9.6.30: Status auf nativen Piwigo-Scheduler umstellen 2026-08-19 20:14:56 +02:00
Terranom674
cf300bc5c3 0.9.6.30: Scheduler beim Plugin-Lifecycle initialisieren 2026-08-19 20:14:33 +02:00
Terranom674
0dde87cda0 0.9.6.30: nativen Scheduler beim Plugin laden 2026-08-19 20:14:21 +02:00
Terranom674
17a76c4de2 0.9.6.30: nativen Connector-Runner hinzufügen 2026-08-19 20:14:05 +02:00
Terranom674
2366f6366c 0.9.6.30: nativen Piwigo-Scheduler hinzufügen 2026-08-19 20:13:47 +02:00
Terranom674
43780cb9a3 Merge pull request #21 from Terranom674/fix/09629-stability-rollback
0.9.6.29: Shadowtree stabilisieren
2026-08-19 19:59:15 +02:00
23 changed files with 1358 additions and 407 deletions

1
.ci-trigger-0977 Normal file
View File

@@ -0,0 +1 @@
0.9.7.7

View File

@@ -0,0 +1,210 @@
<?php
if (!defined('PHPWG_ROOT_PATH'))
{
die('Hacking attempt!');
}
function bratonien_tools_nc_scheduler_paths()
{
$base = rtrim(PHPWG_ROOT_PATH, '/').'/_data/bratonien-tools';
return array(
'base'=>$base,
'runtime'=>$base.'/nc-connector-runtime',
'state_root'=>$base.'/nc-connector-state',
'scheduler'=>$base.'/nc-connector-scheduler',
'state'=>$base.'/nc-connector-scheduler/state.json',
'trigger_lock'=>$base.'/nc-connector-scheduler/trigger.lock',
'worker_lock'=>$base.'/nc-connector-scheduler/worker.lock',
'log'=>$base.'/nc-connector-scheduler/last-worker.log',
'status_root'=>$base.'/nc-connector-status',
);
}
function bratonien_tools_nc_scheduler_interval()
{
global $conf;
$interval = isset($conf['bratonien_nc_scheduler_interval']) ? (int)$conf['bratonien_nc_scheduler_interval'] : 60;
return max(60, $interval);
}
function bratonien_tools_nc_scheduler_ensure_dirs()
{
foreach (bratonien_tools_nc_scheduler_paths() as $key=>$path)
{
if (in_array($key, array('state','trigger_lock','worker_lock','log'), true)) continue;
if (!is_dir($path) && !@mkdir($path, 0750, true) && !is_dir($path))
{
return false;
}
}
return true;
}
function bratonien_tools_nc_scheduler_read_state()
{
$paths = bratonien_tools_nc_scheduler_paths();
if (!is_readable($paths['state'])) return array();
$decoded = json_decode((string)@file_get_contents($paths['state']), true);
return is_array($decoded) ? $decoded : array();
}
function bratonien_tools_nc_scheduler_write_state(array $state)
{
$paths = bratonien_tools_nc_scheduler_paths();
if (!bratonien_tools_nc_scheduler_ensure_dirs()) return false;
$json = json_encode($state, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
if (!is_string($json)) return false;
$tmp = $paths['state'].'.tmp';
if (@file_put_contents($tmp, $json."\n", LOCK_EX) === false) return false;
@chmod($tmp, 0640);
return @rename($tmp, $paths['state']);
}
function bratonien_tools_nc_scheduler_write_connection_status($connection_id, $state, $message)
{
$connection_id = (int)$connection_id;
if ($connection_id < 1) return false;
$paths = bratonien_tools_nc_scheduler_paths();
if (!bratonien_tools_nc_scheduler_ensure_dirs()) return false;
$target = $paths['status_root'].'/connection-'.$connection_id.'.json';
$existing = array();
if (is_readable($target))
{
$decoded = json_decode((string)@file_get_contents($target), true);
if (is_array($decoded)) $existing = $decoded;
}
$existing['state'] = (string)$state;
$existing['message'] = (string)$message;
$existing['timestamp'] = time();
$existing['connection_id'] = $connection_id;
$json = json_encode($existing, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
if (!is_string($json)) return false;
$tmp = $target.'.tmp';
if (@file_put_contents($tmp, $json."\n", LOCK_EX) === false) return false;
@chmod($tmp, 0644);
return @rename($tmp, $target);
}
function bratonien_tools_nc_scheduler_install()
{
if (!bratonien_tools_nc_scheduler_ensure_dirs()) return false;
$state = bratonien_tools_nc_scheduler_read_state();
if (empty($state['next_due']))
{
$state['next_due'] = time();
$state['enabled'] = true;
$state['mode'] = 'piwigo-native';
bratonien_tools_nc_scheduler_write_state($state);
}
return true;
}
function bratonien_tools_nc_scheduler_php_binary()
{
foreach (array('/usr/bin/php', '/usr/local/bin/php', PHP_BINARY) as $candidate)
{
if (is_string($candidate) && $candidate !== '' && is_executable($candidate)) return $candidate;
}
return '';
}
function bratonien_tools_nc_scheduler_spawn($force = false, $connection_id = 0)
{
$connection_id = max(0, (int)$connection_id);
$paths = bratonien_tools_nc_scheduler_paths();
if (!bratonien_tools_nc_scheduler_ensure_dirs())
{
throw new RuntimeException('Der native NC-Scheduler kann sein Laufzeitverzeichnis nicht anlegen.');
}
$lock = @fopen($paths['trigger_lock'], 'c+');
if (!is_resource($lock) || !@flock($lock, LOCK_EX | LOCK_NB))
{
if (is_resource($lock)) fclose($lock);
return array('started'=>false, 'message'=>'Ein NC-Abgleich wird bereits vorbereitet.');
}
$state = bratonien_tools_nc_scheduler_read_state();
$now = time();
$next_due = (int)($state['next_due'] ?? 0);
if (!$force && $next_due > $now)
{
@flock($lock, LOCK_UN);
fclose($lock);
return array('started'=>false, 'message'=>'Der nächste NC-Abgleich ist noch nicht fällig.');
}
$php = bratonien_tools_nc_scheduler_php_binary();
if ($php === '')
{
@flock($lock, LOCK_UN);
fclose($lock);
throw new RuntimeException('Kein ausführbares PHP-CLI für den nativen NC-Scheduler gefunden.');
}
$state['enabled'] = true;
$state['mode'] = 'piwigo-native';
$state['state'] = 'queued';
$state['message'] = $connection_id > 0 ? 'NC-Abgleich für Verbindung #'.$connection_id.' wurde angefordert.' : 'NC-Abgleich für alle Verbindungen wurde angefordert.';
$state['queued_at'] = $now;
$state['timestamp'] = $now;
$state['connection_id'] = $connection_id;
$state['next_due'] = $now + bratonien_tools_nc_scheduler_interval();
bratonien_tools_nc_scheduler_write_state($state);
if ($connection_id > 0)
{
bratonien_tools_nc_scheduler_write_connection_status($connection_id, 'queued', 'Abgleich wurde angefordert.');
}
$runner = BRATONIEN_TOOLS_PATH.'runtime/native-runner.php';
$runner_args = $connection_id > 0 ? ' --connection-id='.escapeshellarg((string)$connection_id) : '';
$command = escapeshellarg($php).' '.escapeshellarg($runner).$runner_args.' >> '.escapeshellarg($paths['log']).' 2>&1 &';
$spec = array(
0=>array('file','/dev/null','r'),
1=>array('file','/dev/null','a'),
2=>array('file','/dev/null','a'),
);
$process = function_exists('proc_open') ? @proc_open(array('/bin/sh','-c',$command), $spec, $pipes) : false;
$exit = is_resource($process) ? proc_close($process) : 1;
@flock($lock, LOCK_UN);
fclose($lock);
if ($exit !== 0)
{
$state = bratonien_tools_nc_scheduler_read_state();
$state['state'] = 'error';
$state['message'] = 'Der native NC-Abgleich konnte nicht gestartet werden.';
$state['timestamp'] = time();
bratonien_tools_nc_scheduler_write_state($state);
if ($connection_id > 0)
{
bratonien_tools_nc_scheduler_write_connection_status($connection_id, 'error', 'Abgleich konnte nicht gestartet werden.');
}
throw new RuntimeException('Der native NC-Abgleich konnte nicht gestartet werden.');
}
return array('started'=>true, 'message'=>$connection_id > 0 ? 'Abgleich für Verbindung #'.$connection_id.' wurde angefordert.' : 'Abgleich für alle Verbindungen wurde angefordert.');
}
function bratonien_tools_nc_scheduler_tick()
{
$state = bratonien_tools_nc_scheduler_read_state();
if (isset($state['enabled']) && !$state['enabled']) return;
$next_due = (int)($state['next_due'] ?? 0);
if ($next_due > time()) return;
try
{
bratonien_tools_nc_scheduler_spawn(false, 0);
}
catch (Throwable $e)
{
$state = bratonien_tools_nc_scheduler_read_state();
$state['state'] = 'error';
$state['message'] = $e->getMessage();
$state['timestamp'] = time();
$state['next_due'] = time() + bratonien_tools_nc_scheduler_interval();
bratonien_tools_nc_scheduler_write_state($state);
}
}

View File

@@ -4,109 +4,13 @@ if (!defined('PHPWG_ROOT_PATH'))
die('Hacking attempt!'); die('Hacking attempt!');
} }
function bratonien_tools_nc_connector_systemctl_value(array $args) require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_scheduler.inc.php');
{
if (!function_exists('proc_open'))
{
return '';
}
$command = array_merge(array('/usr/bin/systemctl'), $args);
$spec = array(
0 => array('file', '/dev/null', 'r'),
1 => array('pipe', 'w'),
2 => array('pipe', 'w'),
);
$environment = array_merge($_ENV, array('LC_ALL'=>'C', 'LANG'=>'C'));
$process = @proc_open($command, $spec, $pipes, null, $environment);
if (!is_resource($process))
{
return '';
}
$stdout = stream_get_contents($pipes[1]);
stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$exit = proc_close($process);
return $exit === 0 ? trim((string)$stdout) : '';
}
function bratonien_tools_nc_connector_parse_systemd_time($value)
{
$value = trim((string)$value);
if ($value === '' || strtolower($value) === 'n/a')
{
return 0;
}
$parsed = strtotime($value);
return $parsed === false ? 0 : (int)$parsed;
}
function bratonien_tools_nc_connector_monotonic_to_timestamp($value)
{
$value = trim((string)$value);
if ($value === '' || $value === '0' || strtolower($value) === 'n/a')
{
return 0;
}
if (preg_match('/^([0-9]+)(?:us)?$/', $value, $matches))
{
$next_boot_seconds = ((float)$matches[1]) / 1000000;
}
else
{
return 0;
}
$uptime_raw = @file_get_contents('/proc/uptime');
if (!is_string($uptime_raw) || !preg_match('/^([0-9]+(?:\.[0-9]+)?)/', trim($uptime_raw), $uptime_match))
{
return 0;
}
$remaining = $next_boot_seconds - (float)$uptime_match[1];
if ($remaining < -1)
{
return 0;
}
return (int)round(time() + max(0, $remaining));
}
function bratonien_tools_nc_connector_next_from_timer_list($timer)
{
$line = bratonien_tools_nc_connector_systemctl_value(array(
'list-timers', '--all', '--no-pager', '--no-legend', $timer,
));
if ($line === '')
{
return 0;
}
$first_line = trim((string)strtok($line, "\n"));
if (preg_match('/^(\S+\s+\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\s+\S+)/', $first_line, $matches))
{
$parsed = strtotime($matches[1]);
return $parsed === false ? 0 : (int)$parsed;
}
return 0;
}
function bratonien_tools_nc_connector_connection_last_status(array $connection) function bratonien_tools_nc_connector_connection_last_status(array $connection)
{ {
$empty = array( $empty = array(
'timestamp'=>0, 'timestamp'=>0,'label'=>'Nicht verfügbar','state'=>'','message'=>'','auth_mode'=>'',
'label'=>'Nicht verfügbar', 'api_state'=>'','api_message'=>'','fallback_state'=>'','fallback_message'=>'','error_detail'=>'',
'state'=>'',
'message'=>'',
'auth_mode'=>'',
'api_state'=>'',
'api_message'=>'',
'fallback_state'=>'',
'fallback_message'=>'',
'error_detail'=>'',
); );
$connection_id = (int)($connection['id'] ?? 0); $connection_id = (int)($connection['id'] ?? 0);
@@ -114,42 +18,25 @@ function bratonien_tools_nc_connector_connection_last_status(array $connection)
if ($connection_id > 0) if ($connection_id > 0)
{ {
$candidates[] = rtrim(PHPWG_ROOT_PATH, '/').'/_data/bratonien-tools/nc-connector-status/connection-'.$connection_id.'.json'; $candidates[] = rtrim(PHPWG_ROOT_PATH, '/').'/_data/bratonien-tools/nc-connector-status/connection-'.$connection_id.'.json';
$candidates[] = rtrim(PHPWG_ROOT_PATH, '/').'/_data/bratonien-tools/nc-connector-state/connection-'.$connection_id.'/connector-status.json';
} }
$config = isset($connection['config']) && is_array($connection['config']) ? $connection['config'] : array(); $config = isset($connection['config']) && is_array($connection['config']) ? $connection['config'] : array();
$state_dir = rtrim((string)($config['state_dir'] ?? ''), '/'); $state_dir = rtrim((string)($config['state_dir'] ?? ''), '/');
if ($state_dir === '' && $connection_id > 0) if ($state_dir !== '') $candidates[] = $state_dir.'/connector-status.json';
{
$state_dir = '/var/lib/bratonien-tools/nc-connector/connection-'.$connection_id;
}
if ($state_dir !== '')
{
$candidates[] = $state_dir.'/connector-status.json';
}
$decoded = null; $decoded = null;
foreach ($candidates as $candidate) foreach (array_unique($candidates) as $candidate)
{ {
if (!is_readable($candidate)) if (!is_readable($candidate)) continue;
{
continue;
}
$value = json_decode((string)@file_get_contents($candidate), true); $value = json_decode((string)@file_get_contents($candidate), true);
if (is_array($value)) if (is_array($value)) { $decoded = $value; break; }
{
$decoded = $value;
break;
}
}
if (!is_array($decoded))
{
return $empty;
} }
if (!is_array($decoded)) return $empty;
$timestamp = (int)($decoded['timestamp'] ?? 0); $timestamp = (int)($decoded['timestamp'] ?? 0);
$api = isset($decoded['api']) && is_array($decoded['api']) ? $decoded['api'] : array(); $api = isset($decoded['api']) && is_array($decoded['api']) ? $decoded['api'] : array();
$fallback = isset($decoded['fallback']) && is_array($decoded['fallback']) ? $decoded['fallback'] : array(); $fallback = isset($decoded['fallback']) && is_array($decoded['fallback']) ? $decoded['fallback'] : array();
return array( return array(
'timestamp'=>$timestamp, 'timestamp'=>$timestamp,
'label'=>$timestamp > 0 ? date('d.m.Y H:i:s', $timestamp) : 'Nicht verfügbar', 'label'=>$timestamp > 0 ? date('d.m.Y H:i:s', $timestamp) : 'Nicht verfügbar',
@@ -166,99 +53,88 @@ function bratonien_tools_nc_connector_connection_last_status(array $connection)
function bratonien_tools_nc_connector_last_status(array $connections) function bratonien_tools_nc_connector_last_status(array $connections)
{ {
$latest = array('timestamp'=>0, 'state'=>'', 'message'=>'', 'auth_mode'=>'', 'api_state'=>'', 'api_message'=>'', 'fallback_state'=>'', 'fallback_message'=>'', 'error_detail'=>''); $latest = array('timestamp'=>0,'state'=>'','message'=>'','auth_mode'=>'','api_state'=>'','api_message'=>'','fallback_state'=>'','fallback_message'=>'','error_detail'=>'');
foreach ($connections as $connection) foreach ($connections as $connection)
{ {
if (empty($connection['enabled'])) if (empty($connection['enabled'])) continue;
{
continue;
}
$status = bratonien_tools_nc_connector_connection_last_status($connection); $status = bratonien_tools_nc_connector_connection_last_status($connection);
if ((int)$status['timestamp'] >= (int)$latest['timestamp']) if ((int)$status['timestamp'] >= (int)$latest['timestamp']) $latest = $status;
{
$latest = $status;
}
} }
return $latest; return $latest;
} }
function bratonien_tools_nc_connector_system_status(array $connections = array()) function bratonien_tools_nc_connector_system_status(array $connections = array())
{ {
$timer = 'bratonien-nc-connector.timer'; $scheduler = bratonien_tools_nc_scheduler_read_state();
$service = 'bratonien-nc-connector.service'; $enabled = !isset($scheduler['enabled']) || !empty($scheduler['enabled']);
$scheduler_state = (string)($scheduler['state'] ?? '');
$active = bratonien_tools_nc_connector_systemctl_value(array('is-active', $timer)); $running = $scheduler_state === 'running';
$enabled = bratonien_tools_nc_connector_systemctl_value(array('is-enabled', $timer)); $queued = $scheduler_state === 'queued';
$service_active_raw = bratonien_tools_nc_connector_systemctl_value(array('is-active', $service)); $started = (int)($scheduler['started_at'] ?? 0);
$service_active = in_array($service_active_raw, array('active', 'activating'), true); $next = (int)($scheduler['next_due'] ?? 0);
$service_started_raw = bratonien_tools_nc_connector_systemctl_value(array('show', $service, '--property=ActiveEnterTimestamp', '--value'));
$service_started_timestamp = bratonien_tools_nc_connector_parse_systemd_time($service_started_raw);
$next_realtime_raw = bratonien_tools_nc_connector_systemctl_value(array('show', $timer, '--property=NextElapseUSecRealtime', '--value'));
$next_timestamp = bratonien_tools_nc_connector_parse_systemd_time($next_realtime_raw);
if ($next_timestamp <= 0)
{
$next_monotonic_raw = bratonien_tools_nc_connector_systemctl_value(array('show', $timer, '--property=NextElapseUSecMonotonic', '--value'));
$next_timestamp = bratonien_tools_nc_connector_monotonic_to_timestamp($next_monotonic_raw);
}
if ($next_timestamp <= 0)
{
$next_timestamp = bratonien_tools_nc_connector_next_from_timer_list($timer);
}
$last = bratonien_tools_nc_connector_last_status($connections); $last = bratonien_tools_nc_connector_last_status($connections);
if ($last['timestamp'] <= 0)
{
$last_raw = bratonien_tools_nc_connector_systemctl_value(array('show', $service, '--property=ExecMainExitTimestamp', '--value'));
$last['timestamp'] = bratonien_tools_nc_connector_parse_systemd_time($last_raw);
}
if ($next_timestamp > 0) if ($next > 0)
{ {
$next_label = date('d.m.Y H:i:s', $next_timestamp); $next_label = date('d.m.Y H:i:s', $next).' (beim nächsten Piwigo-Aufruf)';
}
elseif ($service_active && $active === 'active' && $enabled === 'enabled')
{
$next_label = 'Nach Abschluss des aktuellen Laufs';
}
elseif ($active === 'active' && $enabled === 'enabled')
{
$next_label = 'Wird von systemd neu geplant';
} }
else else
{ {
$next_label = 'Nicht verfügbar'; $next_label = 'Beim nächsten Piwigo-Aufruf';
} }
$scheduler_timestamp = (int)($scheduler['timestamp'] ?? 0);
if ($scheduler_timestamp > (int)$last['timestamp'])
{
$last['timestamp'] = $scheduler_timestamp;
$last['state'] = $scheduler_state;
$last['message'] = (string)($scheduler['message'] ?? '');
if ($scheduler_state === 'error')
{
$detail = trim((string)($scheduler['stderr'] ?? ''));
if ($detail === '') $detail = trim((string)($scheduler['stdout'] ?? ''));
$last['error_detail'] = $detail;
}
}
elseif ((int)$last['timestamp'] <= 0 && !empty($scheduler['finished_at']))
{
$last['timestamp'] = (int)$scheduler['finished_at'];
$last['state'] = $scheduler_state;
$last['message'] = (string)($scheduler['message'] ?? '');
if ((string)$last['state'] === 'error')
{
$detail = trim((string)($scheduler['stderr'] ?? ''));
if ($detail === '') $detail = trim((string)($scheduler['stdout'] ?? ''));
$last['error_detail'] = $detail;
}
}
$current_label = 'Kein Lauf aktiv';
if ($queued) $current_label = 'Abgleich angefordert';
if ($running) $current_label = $started > 0 ? 'Läuft seit '.date('d.m.Y H:i:s', $started) : 'Läuft gerade';
return array( return array(
'timer_name' => $timer, 'timer_name'=>'Piwigo nativer NC-Scheduler',
'timer_active' => $active === 'active', 'timer_active'=>$enabled,
'timer_enabled' => $enabled === 'enabled', 'timer_enabled'=>$enabled,
'service_active' => $service_active, 'service_active'=>$running || $queued,
'current_run_timestamp' => $service_started_timestamp, 'current_run_timestamp'=>$queued ? (int)($scheduler['queued_at'] ?? 0) : $started,
'current_run_label' => $service_active 'current_run_label'=>$current_label,
? ($service_started_timestamp > 0 ? 'Läuft seit '.date('d.m.Y H:i:s', $service_started_timestamp) : 'Läuft gerade') 'last_run_timestamp'=>(int)$last['timestamp'],
: 'Kein Lauf aktiv', 'last_run_label'=>(int)$last['timestamp'] > 0 ? date('d.m.Y H:i:s', (int)$last['timestamp']) : 'Nicht verfügbar',
'last_run_timestamp' => (int)$last['timestamp'], 'last_run_state'=>(string)$last['state'],
'last_run_label' => $last['timestamp'] > 0 ? date('d.m.Y H:i:s', (int)$last['timestamp']) : 'Nicht verfügbar', 'last_run_message'=>(string)$last['message'],
'last_run_state' => (string)$last['state'], 'last_run_auth_mode'=>(string)$last['auth_mode'],
'last_run_message' => (string)$last['message'], 'last_run_api_state'=>(string)$last['api_state'],
'last_run_auth_mode' => (string)$last['auth_mode'], 'last_run_api_message'=>(string)$last['api_message'],
'last_run_api_state' => (string)$last['api_state'], 'last_run_fallback_state'=>(string)$last['fallback_state'],
'last_run_api_message' => (string)$last['api_message'], 'last_run_fallback_message'=>(string)$last['fallback_message'],
'last_run_fallback_state' => (string)$last['fallback_state'], 'last_run_error_detail'=>(string)$last['error_detail'],
'last_run_fallback_message' => (string)$last['fallback_message'], 'next_run_timestamp'=>$next,
'last_run_error_detail' => (string)$last['error_detail'], 'next_run_label'=>$next_label,
'next_run_timestamp' => $next_timestamp, 'legacy_runtime_exists'=>is_dir('/opt/piwigo-sync'),
'next_run_label' => $next_label, 'legacy_config_exists'=>is_dir('/etc/piwigo-sync'),
'legacy_runtime_exists' => is_dir('/opt/piwigo-sync'), 'legacy_service_exists'=>is_file('/etc/systemd/system/piwigo-sync.service'),
'legacy_config_exists' => is_dir('/etc/piwigo-sync'), 'legacy_timer_exists'=>is_file('/etc/systemd/system/piwigo-sync.timer'),
'legacy_service_exists' => is_file('/etc/systemd/system/piwigo-sync.service'),
'legacy_timer_exists' => is_file('/etc/systemd/system/piwigo-sync.timer'),
); );
} }

View File

@@ -28,7 +28,7 @@ function bratonien_tools_nc_wizard_scan_webdav_first()
{ {
try try
{ {
$response = bratonien_tools_nc_wizard_http($candidate_url.'/status.php'); $response = bratonien_tools_nc_transport_http($candidate_url.'/status.php');
if ($response['status'] < 200 || $response['status'] >= 300) continue; if ($response['status'] < 200 || $response['status'] >= 300) continue;
$candidate_status = json_decode($response['body'], true); $candidate_status = json_decode($response['body'], true);
if (!is_array($candidate_status) || empty($candidate_status['installed'])) continue; if (!is_array($candidate_status) || empty($candidate_status['installed'])) continue;
@@ -44,7 +44,7 @@ function bratonien_tools_nc_wizard_scan_webdav_first()
throw new RuntimeException('Unter dieser Adresse konnte keine Nextcloud erreicht werden. HTTP und HTTPS wurden automatisch geprüft.'); throw new RuntimeException('Unter dieser Adresse konnte keine Nextcloud erreicht werden. HTTP und HTTPS wurden automatisch geprüft.');
} }
$user_response = bratonien_tools_nc_wizard_http( $user_response = bratonien_tools_nc_transport_http(
$base_url.'/ocs/v2.php/cloud/user?format=json', $base_url.'/ocs/v2.php/cloud/user?format=json',
$username, $username,
$password, $password,
@@ -104,7 +104,7 @@ function bratonien_tools_nc_wizard_scan_webdav_first()
'api_error'=>'', 'api_error'=>'',
)); ));
bratonien_tools_nc_wizard_refresh_directory_state($state, ''); bratonien_tools_nc_transport_refresh_directory_state($state, '');
bratonien_tools_nc_wizard_store($state); bratonien_tools_nc_wizard_store($state);
return array('message'=>'Nextcloud und WebDAV wurden bestätigt. Jetzt können die Verzeichnisse des angemeldeten Benutzers ausgewählt werden.'); return array('message'=>'Nextcloud und WebDAV wurden bestätigt. Jetzt können die Verzeichnisse des angemeldeten Benutzers ausgewählt werden.');

View File

@@ -0,0 +1,115 @@
<?php
if (!defined('PHPWG_ROOT_PATH'))
{
die('Hacking attempt!');
}
function bratonien_tools_nc_legacy_remove_tree($path, $allowed_root)
{
$path = rtrim(str_replace('\\', '/', (string)$path), '/');
$allowed_root = rtrim(str_replace('\\', '/', (string)$allowed_root), '/');
if ($path === '' || $allowed_root === '' || $path === $allowed_root || strpos($path, $allowed_root.'/') !== 0)
{
return 0;
}
if (!file_exists($path) && !is_link($path)) return 0;
if (is_link($path) || is_file($path))
{
return @unlink($path) ? 1 : 0;
}
$removed = 0;
$items = @scandir($path);
if (is_array($items))
{
foreach ($items as $item)
{
if ($item === '.' || $item === '..') continue;
$removed += bratonien_tools_nc_legacy_remove_tree($path.'/'.$item, $allowed_root);
}
}
if (@rmdir($path)) $removed++;
return $removed;
}
function bratonien_tools_nc_legacy_remove_state_files($state_root)
{
$removed = 0;
foreach (glob(rtrim($state_root, '/').'/connection-*') ?: array() as $connection_dir)
{
if (!is_dir($connection_dir)) continue;
foreach (array('webdav-map.json', 'webdav-manifest.tsv', 'webdav-shadow-map.json') as $name)
{
$path = $connection_dir.'/'.$name;
if (is_file($path) && @unlink($path)) $removed++;
}
}
return $removed;
}
function bratonien_tools_nc_reset_legacy_imports()
{
include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
$ids = array();
$result = pwg_query(
"SELECT id FROM ".IMAGES_TABLE.
" WHERE path LIKE '%nc-webdav-source/connection-%'"
);
while ($row = pwg_db_fetch_assoc($result))
{
$ids[] = (int)$row['id'];
}
if ($ids)
{
delete_elements($ids, false);
}
$data_root = rtrim(PHPWG_ROOT_PATH, '/').'/_data';
$tool_root = $data_root.'/bratonien-tools';
$removed_files = 0;
foreach (array(
$tool_root.'/nc-webdav-source',
$tool_root.'/nc-webdav-preview',
$tool_root.'/nc-webdav-gallery',
) as $generated_root)
{
$removed_files += bratonien_tools_nc_legacy_remove_tree($generated_root, $data_root);
}
if (defined('PWG_DERIVATIVE_DIR'))
{
$derivative_root = rtrim(PHPWG_ROOT_PATH.PWG_DERIVATIVE_DIR, '/');
foreach (array(
$derivative_root.'/_data/bratonien-tools/nc-webdav-source',
$derivative_root.'/_data/bratonien-tools/nc-webdav-gallery',
) as $generated_derivative_root)
{
$removed_files += bratonien_tools_nc_legacy_remove_tree($generated_derivative_root, $derivative_root);
}
}
$removed_state_files = bratonien_tools_nc_legacy_remove_state_files($tool_root.'/nc-connector-state');
if ($ids)
{
update_category('all');
invalidate_user_cache(true);
}
$result = array(
'timestamp'=>time(),
'removed_image_records'=>count($ids),
'removed_generated_entries'=>$removed_files,
'removed_state_files'=>$removed_state_files,
);
if (function_exists('conf_update_param'))
{
conf_update_param('bratonien_nc_legacy_reset_0977', json_encode($result));
}
return $result;
}

View File

@@ -56,7 +56,6 @@ function bratonien_tools_nc_find_album($parent_id, $dir, $name, $excluded_site_i
SELECT id, dir, name SELECT id, dir, name
FROM '.CATEGORIES_TABLE.' FROM '.CATEGORIES_TABLE.'
WHERE '.$where_parent.' WHERE '.$where_parent.'
AND (site_id IS NULL OR site_id <> '.(int)$excluded_site_id.')
AND ( AND (
dir = \''.$dir_sql.'\' dir = \''.$dir_sql.'\'
OR LOWER(name) = LOWER(\''.$name_sql.'\') OR LOWER(name) = LOWER(\''.$name_sql.'\')
@@ -122,10 +121,9 @@ function bratonien_tools_nc_managed_images($basedir)
function bratonien_tools_nc_remove_storage_categories($site_id) function bratonien_tools_nc_remove_storage_categories($site_id)
{ {
$ids = query2array('SELECT id FROM '.CATEGORIES_TABLE.' WHERE site_id='.(int)$site_id.' AND dir IS NOT NULL', null, 'id'); // Bestehende Piwigo-Alben gehoeren nicht automatisch dem Connector.
if (!$ids) return 0; // Ohne eindeutige Connector-Eigentumsmarkierung darf hier nichts geloescht werden.
delete_categories(array_map('intval', $ids)); return 0;
return count($ids);
} }
function bratonien_tools_ws_nc_sync_productive($params, &$service) function bratonien_tools_ws_nc_sync_productive($params, &$service)
@@ -169,8 +167,6 @@ function bratonien_tools_ws_nc_sync_productive($params, &$service)
try try
{ {
$counts['removed_duplicate_categories'] = bratonien_tools_nc_remove_storage_categories($site_id);
list($dbnow) = pwg_db_fetch_row(pwg_query('SELECT NOW()')); list($dbnow) = pwg_db_fetch_row(pwg_query('SELECT NOW()'));
$fs_dirs = $site_reader->get_full_directories($basedir); $fs_dirs = $site_reader->get_full_directories($basedir);
usort($fs_dirs, function($a, $b) usort($fs_dirs, function($a, $b)
@@ -199,20 +195,10 @@ function bratonien_tools_ws_nc_sync_productive($params, &$service)
$db_elements = bratonien_tools_nc_managed_images($basedir); $db_elements = bratonien_tools_nc_managed_images($basedir);
$db_by_path = array_flip($db_elements); $db_by_path = array_flip($db_elements);
$to_delete = array(); // Nicht-destruktiver Schutz: Bestehende Piwigo-Bilder werden niemals allein
foreach ($db_elements as $id=>$path) // deshalb geloescht, weil sie im aktuellen WebDAV-Scan nicht vorkommen.
{ // Das Entfernen ist erst wieder zulaessig, wenn Connector-Eigentum eindeutig
if (!array_key_exists($path, $fs)) $to_delete[] = (int)$id; // und verbindungsbezogen gespeichert wird.
}
if ($to_delete)
{
delete_elements($to_delete, false);
$counts['del_elements'] = count($to_delete);
foreach ($to_delete as $id)
{
if (isset($db_elements[$id])) unset($db_by_path[$db_elements[$id]], $db_elements[$id]);
}
}
$next_element_id = pwg_db_nextval('id', IMAGES_TABLE); $next_element_id = pwg_db_nextval('id', IMAGES_TABLE);
$image_inserts = array(); $image_inserts = array();
@@ -232,13 +218,9 @@ function bratonien_tools_ws_nc_sync_productive($params, &$service)
if (isset($db_by_path[$path])) if (isset($db_by_path[$path]))
{ {
$id = (int)$db_by_path[$path]; // Altbestand niemals umhaengen. Vorhandene Bild-Album-Zuordnungen bleiben
$all_ids[] = $id; // exakt bestehen; der Connector darf nur neue Datensaetze ergaenzen.
pwg_query('DELETE FROM '.IMAGE_CATEGORY_TABLE.' WHERE image_id='.$id); $all_ids[] = (int)$db_by_path[$path];
if ($category_id !== null)
{
single_insert(IMAGE_CATEGORY_TABLE, array('image_id'=>$id, 'category_id'=>$category_id));
}
continue; continue;
} }
@@ -276,8 +258,10 @@ function bratonien_tools_ws_nc_sync_productive($params, &$service)
$counts['new_elements'] = count($new_ids); $counts['new_elements'] = count($new_ids);
} }
// Bestehende Bilder werden nicht durch den Connector aktualisiert. Nur neu
// angelegte Connector-Bilder erhalten die aus der Quelle ermittelten Attribute.
$updates = array(); $updates = array();
foreach ($all_ids as $id) foreach ($new_ids as $id)
{ {
$path_result = pwg_query('SELECT path FROM '.IMAGES_TABLE.' WHERE id='.(int)$id.' LIMIT 1'); $path_result = pwg_query('SELECT path FROM '.IMAGES_TABLE.' WHERE id='.(int)$id.' LIMIT 1');
if (!pwg_db_num_rows($path_result)) continue; if (!pwg_db_num_rows($path_result)) continue;

View File

@@ -0,0 +1,340 @@
<?php
function bratonien_tools_nc_transport_host($url)
{
$host = trim((string)parse_url((string)$url, PHP_URL_HOST));
if ($host === '') throw new RuntimeException('Die Nextcloud-Adresse enthält keinen gültigen Hostnamen oder keine IP-Adresse.');
return trim($host, '[]');
}
function bratonien_tools_nc_transport_scheme($url)
{
$scheme = strtolower(trim((string)parse_url((string)$url, PHP_URL_SCHEME)));
if (!in_array($scheme, array('http','https'), true)) throw new RuntimeException('Nextcloud muss per HTTP oder HTTPS angesprochen werden.');
return $scheme;
}
function bratonien_tools_nc_transport_is_ip($host)
{
return filter_var(trim((string)$host, '[]'), FILTER_VALIDATE_IP) !== false;
}
function bratonien_tools_nc_transport_public_ip($host)
{
static $cache = array();
$host = strtolower(trim((string)$host, '[]'));
if ($host === '') throw new RuntimeException('Für die Nextcloud-Verbindung fehlt der Hostname.');
if (bratonien_tools_nc_transport_is_ip($host)) return $host;
if (isset($cache[$host])) return $cache[$host];
if (!function_exists('curl_init')) throw new RuntimeException('Der öffentliche DNS-Abgleich benötigt PHP-cURL.');
$providers = array(
array('host'=>'dns.google', 'ips'=>array('8.8.8.8','8.8.4.4'), 'url'=>'https://dns.google/resolve?name='.rawurlencode($host).'&type=A'),
array('host'=>'cloudflare-dns.com', 'ips'=>array('1.1.1.1','1.0.0.1'), 'url'=>'https://cloudflare-dns.com/dns-query?name='.rawurlencode($host).'&type=A'),
);
foreach ($providers as $provider)
{
foreach ($provider['ips'] as $resolver_ip)
{
$ch = curl_init($provider['url']);
$options = array(
CURLOPT_RETURNTRANSFER=>true,
CURLOPT_FOLLOWLOCATION=>false,
CURLOPT_CONNECTTIMEOUT=>5,
CURLOPT_TIMEOUT=>10,
CURLOPT_HTTPHEADER=>array('Accept: application/dns-json'),
CURLOPT_USERAGENT=>'Bratonien-Tools-DNS/0.9.7.1',
);
if (defined('CURLOPT_RESOLVE'))
{
$options[CURLOPT_RESOLVE] = array($provider['host'].':443:'.$resolver_ip);
}
curl_setopt_array($ch, $options);
$body = curl_exec($ch);
$errno = curl_errno($ch);
$status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($body === false || $errno !== 0 || $status < 200 || $status >= 300) continue;
$decoded = json_decode((string)$body, true);
if (!is_array($decoded) || !isset($decoded['Answer']) || !is_array($decoded['Answer'])) continue;
foreach ($decoded['Answer'] as $answer)
{
if ((int)($answer['type'] ?? 0) !== 1) continue;
$ip = trim((string)($answer['data'] ?? ''));
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) continue;
return $cache[$host] = $ip;
}
}
}
throw new RuntimeException('Für '.$host.' konnte keine öffentliche IPv4-Adresse ermittelt werden.');
}
function bratonien_tools_nc_transport_resolve_entry($url)
{
$scheme = bratonien_tools_nc_transport_scheme($url);
$host = bratonien_tools_nc_transport_host($url);
if (bratonien_tools_nc_transport_is_ip($host)) return null;
$port = (int)parse_url((string)$url, PHP_URL_PORT);
if ($port < 1) $port = $scheme === 'https' ? 443 : 80;
$ip = bratonien_tools_nc_transport_public_ip($host);
return $host.':'.$port.':'.$ip;
}
function bratonien_tools_nc_transport_apply_curl(array &$options, $url)
{
$entry = bratonien_tools_nc_transport_resolve_entry($url);
if ($entry !== null)
{
if (!defined('CURLOPT_RESOLVE')) throw new RuntimeException('Diese cURL-Version unterstützt keine direkte Host-zu-IP-Zuordnung.');
$options[CURLOPT_RESOLVE] = array($entry);
}
}
function bratonien_tools_nc_transport_http($url, $username = '', $password = '', array $headers = array())
{
if (!function_exists('curl_init')) throw new RuntimeException('Der Server kann Nextcloud derzeit nicht per HTTP prüfen.');
$ch = curl_init($url);
$options = array(
CURLOPT_RETURNTRANSFER=>true,
CURLOPT_FOLLOWLOCATION=>true,
CURLOPT_MAXREDIRS=>3,
CURLOPT_CONNECTTIMEOUT=>8,
CURLOPT_TIMEOUT=>15,
CURLOPT_HTTPHEADER=>array_merge(array('Accept: application/json'), $headers),
CURLOPT_USERAGENT=>'Bratonien-Tools-NC-Wizard/0.9.7.1',
);
bratonien_tools_nc_transport_apply_curl($options, $url);
if ($username !== '')
{
$options[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
$options[CURLOPT_USERPWD] = $username.':'.$password;
}
curl_setopt_array($ch, $options);
$body = curl_exec($ch);
$errno = curl_errno($ch);
$error = curl_error($ch);
$status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($body === false || $errno !== 0)
{
throw new RuntimeException('Verbindung fehlgeschlagen'.($error !== '' ? ': '.$error : '.'));
}
return array('status'=>$status, 'body'=>(string)$body);
}
function bratonien_tools_nc_transport_webdav_list(array $state, $path = '')
{
if (empty($state['scan_ok']) || trim((string)$state['base_url']) === '' || trim((string)$state['username']) === '' || (string)$state['_password'] === '')
{
throw new RuntimeException('Die Nextcloud-Sitzung des Assistenten ist nicht vollständig.');
}
if (!function_exists('curl_init')) throw new RuntimeException('cURL ist für die Verzeichnisauswahl nicht verfügbar.');
$path = trim((string)$path, '/');
if ($path !== '' && preg_match('#(^|/)\.\.(/|$)#', $path)) throw new RuntimeException('Ungültiger Verzeichnispfad.');
$segments = $path === '' ? array() : array_map('rawurlencode', explode('/', $path));
$user = rawurlencode((string)$state['username']);
$url = rtrim((string)$state['base_url'], '/').'/remote.php/dav/files/'.$user.'/'.implode('/', $segments);
if (substr($url, -1) !== '/') $url .= '/';
$body = '<?xml version="1.0"?><d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns"><d:prop><d:resourcetype/><d:displayname/><oc:fileid/></d:prop></d:propfind>';
$ch = curl_init($url);
$options = array(
CURLOPT_RETURNTRANSFER=>true,
CURLOPT_CUSTOMREQUEST=>'PROPFIND',
CURLOPT_POSTFIELDS=>$body,
CURLOPT_HTTPHEADER=>array('Depth: 1','Content-Type: application/xml; charset=utf-8'),
CURLOPT_HTTPAUTH=>CURLAUTH_BASIC,
CURLOPT_USERPWD=>(string)$state['username'].':'.(string)$state['_password'],
CURLOPT_CONNECTTIMEOUT=>8,
CURLOPT_TIMEOUT=>20,
);
bratonien_tools_nc_transport_apply_curl($options, $url);
curl_setopt_array($ch, $options);
$response = curl_exec($ch);
$errno = curl_errno($ch);
$error = curl_error($ch);
$status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($response === false || $errno !== 0) throw new RuntimeException('Nextcloud-Verzeichnisse konnten nicht geladen werden'.($error !== '' ? ': '.$error : '.'));
if ($status === 401 || $status === 403) throw new RuntimeException('Nextcloud hat den Zugriff auf dieses Verzeichnis abgelehnt.');
if ($status !== 207) throw new RuntimeException('Nextcloud-Verzeichnisabfrage antwortete mit HTTP '.$status.'.');
libxml_use_internal_errors(true);
$xml = simplexml_load_string((string)$response);
if ($xml === false) throw new RuntimeException('Nextcloud hat eine ungültige WebDAV-Antwort geliefert.');
$xml->registerXPathNamespace('d', 'DAV:');
$xml->registerXPathNamespace('oc', 'http://owncloud.org/ns');
$children = array();
$fileids = array();
$current_fileid = 0;
$base_path = (string)parse_url($url, PHP_URL_PATH);
foreach ($xml->xpath('//d:response') as $item)
{
$item->registerXPathNamespace('d', 'DAV:');
$item->registerXPathNamespace('oc', 'http://owncloud.org/ns');
$hrefs = $item->xpath('d:href');
$collections = $item->xpath('d:propstat/d:prop/d:resourcetype/d:collection');
$ids = $item->xpath('d:propstat/d:prop/oc:fileid');
if (!$hrefs || !$collections || !$ids) continue;
$fileid = (int)trim((string)$ids[0]);
if ($fileid < 1) continue;
$href = rawurldecode((string)$hrefs[0]);
$href_path = (string)parse_url($href, PHP_URL_PATH);
if (rtrim($href_path, '/') === rtrim($base_path, '/'))
{
$current_fileid = $fileid;
continue;
}
$name = basename(rtrim($href_path, '/'));
if ($name === '') continue;
$child_path = $path === '' ? $name : $path.'/'.$name;
$children[$child_path] = $name;
$fileids[$child_path] = $fileid;
}
natcasesort($children);
if ($current_fileid < 1) throw new RuntimeException('Nextcloud hat für das aktuelle Verzeichnis keine eindeutige Datei-ID geliefert.');
$parent = '';
if ($path !== '')
{
$parts = explode('/', $path);
array_pop($parts);
$parent = implode('/', $parts);
}
return array(
'current'=>$path,
'parent'=>$parent,
'children'=>$children,
'current_fileid'=>$current_fileid,
'fileids'=>$fileids,
);
}
function bratonien_tools_nc_transport_refresh_directory_state(array &$state, $path = null)
{
if ($path === null) $path = (string)($state['directory_path'] ?? '');
$listing = bratonien_tools_nc_transport_webdav_list($state, $path);
$state['directory_path'] = (string)$listing['current'];
$state['directory_parent'] = (string)$listing['parent'];
$state['directory_children'] = (array)$listing['children'];
$state['directory_current_fileid'] = (int)$listing['current_fileid'];
$state['directory_fileids'] = (array)$listing['fileids'];
if (!isset($state['directory_selected']) || !is_array($state['directory_selected'])) $state['directory_selected'] = array();
if (!isset($state['directory_selected_fileids']) || !is_array($state['directory_selected_fileids'])) $state['directory_selected_fileids'] = array();
}
function bratonien_tools_nc_transport_wizard_directory_browse()
{
$state = bratonien_tools_nc_wizard_state();
if ((int)$state['step'] !== 2 || (string)$state['technical_stage'] !== 'mounts' || empty($state['directory_selection_ready'])) throw new RuntimeException('Die Verzeichnisauswahl ist in diesem Fenster nicht verfügbar.');
$path = trim((string)($_POST['nc_wizard_directory_path'] ?? ''), '/');
bratonien_tools_nc_transport_refresh_directory_state($state, $path);
bratonien_tools_nc_wizard_store($state);
return array('message'=>'Verzeichnis geöffnet.');
}
function bratonien_tools_nc_transport_edit_start()
{
$id = (int)($_POST['connection_id'] ?? 0);
$connection = bratonien_tools_nc_connector_connection($id, true);
if (!$connection) throw new RuntimeException('Connector-Verbindung wurde nicht gefunden.');
$config = isset($connection['config']) && is_array($connection['config']) ? $connection['config'] : array();
$is_webdav = (string)$connection['adapter'] === 'remote'
&& (string)($config['source_mode'] ?? '') === 'webdav-placeholder';
if (!$is_webdav) return bratonien_tools_nc_connector_edit_start();
$credentials = bratonien_tools_nc_connector_scoped_secret($connection);
$base_url = trim((string)($config['nextcloud_url'] ?? ''));
$username = trim((string)($credentials['nextcloud_user'] ?? ''));
if ($username === '') $username = trim((string)($config['nextcloud_access_user'] ?? $config['access_user'] ?? ''));
$password = (string)($credentials['nextcloud_password'] ?? '');
$roots = isset($config['roots']) && is_array($config['roots']) ? array_values($config['roots']) : array();
$selected = array();
$selected_ids = array();
foreach ($roots as $root)
{
$path = trim((string)($root['webdav_path'] ?? ''), '/');
$fileid = (int)($root['fileid'] ?? 0);
if ($fileid < 1) continue;
$selected[] = $path;
$selected_ids[$path] = $fileid;
}
$state = bratonien_tools_nc_wizard_state();
$state = array_merge($state, array(
'editing_connection_id'=>$id,
'editing_adapter'=>(string)$connection['adapter'],
'editing_mode'=>'update',
'connection_name'=>(string)$connection['name'],
'host_input'=>$base_url,
'base_url'=>$base_url,
'username'=>$username,
'_password'=>$password,
'_fallback_user'=>(string)($credentials['piwigo_user'] ?? ''),
'_fallback_password'=>(string)($credentials['piwigo_password'] ?? ''),
'_api_key_id'=>(string)($credentials['api_key_id'] ?? ''),
'_api_key_secret'=>(string)($credentials['api_key_secret'] ?? ''),
'api_status'=>trim((string)($credentials['api_key_id'] ?? '')) !== '' && trim((string)($credentials['api_key_secret'] ?? '')) !== '' ? 'ok' : 'pending',
'roots'=>$roots,
'directory_selected'=>$selected,
'directory_selected_fileids'=>$selected_ids,
'source_mode'=>'webdav-placeholder',
'transport'=>'webdav',
));
if ($base_url !== '' && $username !== '' && $password !== '')
{
$state['step'] = 2;
$state['scan_ok'] = true;
$state['technical_stage'] = 'mounts';
$state['technical_source'] = 'WebDAV';
$state['technical_error'] = '';
$state['technical_complete'] = false;
$state['directory_selection_ready'] = true;
$state['directory_path'] = '';
$state['directory_parent'] = '';
$state['directory_children'] = array();
$state['directory_current_fileid'] = 0;
try
{
bratonien_tools_nc_transport_refresh_directory_state($state, '');
}
catch (Throwable $e)
{
$state['step'] = 1;
$state['scan_ok'] = false;
$state['technical_error'] = $e->getMessage();
}
}
else
{
$state['step'] = 1;
$state['scan_ok'] = false;
}
bratonien_tools_nc_wizard_store($state);
return array('message'=>'Verbindung #'.$id.' wurde zum Bearbeiten geöffnet.');
}

View File

@@ -6,7 +6,7 @@ if (!defined('PHPWG_ROOT_PATH'))
if (isset($GLOBALS['template']) && is_object($GLOBALS['template']) && method_exists($GLOBALS['template'], 'func_combine_script')) if (isset($GLOBALS['template']) && is_object($GLOBALS['template']) && method_exists($GLOBALS['template'], 'func_combine_script'))
{ {
$script_version = function_exists('bratonien_tools_current_version') ? bratonien_tools_current_version() : '0.9.6.27'; $script_version = function_exists('bratonien_tools_current_version') ? bratonien_tools_current_version() : '0.9.7.1';
$GLOBALS['template']->func_combine_script(array( $GLOBALS['template']->func_combine_script(array(
'id'=>'bratonien_nc_connector_edit_v2', 'id'=>'bratonien_nc_connector_edit_v2',
'path'=>BRATONIEN_TOOLS_PATH.'js/nc_connector_edit_v2.js', 'path'=>BRATONIEN_TOOLS_PATH.'js/nc_connector_edit_v2.js',
@@ -41,57 +41,23 @@ require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_delete_safe.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_webdav.inc.php'); require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_webdav.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_wizard_webdav_flow.inc.php'); require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_wizard_webdav_flow.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_edit.inc.php'); require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_edit.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_transport.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_scheduler.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_system.inc.php'); require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_system.inc.php');
function bratonien_tools_nc_connector_run_now() function bratonien_tools_nc_connector_run_now()
{ {
$service = 'bratonien-nc-connector.service'; $connection_id = isset($_POST['connection_id']) ? max(0, (int)$_POST['connection_id']) : 0;
$active = bratonien_tools_nc_connector_systemctl_value(array('is-active', $service)); if ($connection_id > 0)
if ($active === 'active' || $active === 'activating')
{ {
return array('message'=>'Der NC-Abgleich läuft bereits.'); $connection = bratonien_tools_nc_connector_connection($connection_id, false);
} if (!$connection)
if (!function_exists('proc_open'))
{
throw new RuntimeException('Der NC-Abgleich konnte nicht gestartet werden: proc_open ist nicht verfügbar.');
}
$commands = array(
array('/usr/bin/sudo', '-n', '/usr/bin/systemctl', 'start', '--no-block', $service),
array('/usr/bin/systemctl', 'start', '--no-block', $service),
);
$last_error = '';
foreach ($commands as $command)
{
if (!is_executable($command[0]))
{ {
continue; throw new RuntimeException('Die ausgewählte Verbindung existiert nicht.');
} }
$spec = array(
0=>array('file','/dev/null','r'),
1=>array('pipe','w'),
2=>array('pipe','w'),
);
$process = @proc_open($command, $spec, $pipes, null, array_merge($_ENV, array('LC_ALL'=>'C','LANG'=>'C')));
if (!is_resource($process))
{
continue;
}
$stdout = trim((string)stream_get_contents($pipes[1]));
$stderr = trim((string)stream_get_contents($pipes[2]));
fclose($pipes[1]);
fclose($pipes[2]);
$exit = proc_close($process);
if ($exit === 0)
{
return array('message'=>'NC-Abgleich wurde gestartet.');
}
$last_error = $stderr !== '' ? $stderr : $stdout;
} }
$result = bratonien_tools_nc_scheduler_spawn(true, $connection_id);
throw new RuntimeException('Der NC-Abgleich konnte nicht gestartet werden'.($last_error !== '' ? ': '.$last_error : '.')); return array('message'=>(string)($result['message'] ?? ($connection_id > 0 ? 'Abgleich wurde gestartet.' : 'Abgleich für alle Verbindungen wurde gestartet.')));
} }
function bratonien_tools_get_tools() function bratonien_tools_get_tools()
@@ -118,7 +84,7 @@ function bratonien_tools_get_tools()
'album_share_regenerate_link' => array('handler' => 'bratonien_tools_regenerate_album_share_link'), 'album_share_regenerate_link' => array('handler' => 'bratonien_tools_regenerate_album_share_link'),
'album_share_revoke' => array('handler' => 'bratonien_tools_revoke_album_share'), 'album_share_revoke' => array('handler' => 'bratonien_tools_revoke_album_share'),
'nc_connector_create_webdav_parallel' => array('handler' => 'bratonien_tools_nc_connector_create_webdav_placeholder_from_wizard'), 'nc_connector_create_webdav_parallel' => array('handler' => 'bratonien_tools_nc_connector_create_webdav_placeholder_from_wizard'),
'nc_connector_edit_start' => array('handler' => 'bratonien_tools_nc_connector_edit_start'), 'nc_connector_edit_start' => array('handler' => 'bratonien_tools_nc_transport_edit_start'),
'nc_connector_update_local' => array('handler' => 'bratonien_tools_nc_connector_update_local_friendly'), 'nc_connector_update_local' => array('handler' => 'bratonien_tools_nc_connector_update_local_friendly'),
'nc_connector_delete' => array('handler' => 'bratonien_tools_nc_connector_delete_safe'), 'nc_connector_delete' => array('handler' => 'bratonien_tools_nc_connector_delete_safe'),
'nc_connector_update_name' => array('handler' => 'bratonien_tools_nc_connector_update_name'), 'nc_connector_update_name' => array('handler' => 'bratonien_tools_nc_connector_update_name'),
@@ -126,7 +92,7 @@ function bratonien_tools_get_tools()
'nc_connector_run_now' => array('handler' => 'bratonien_tools_nc_connector_run_now'), 'nc_connector_run_now' => array('handler' => 'bratonien_tools_nc_connector_run_now'),
'nc_connector_wizard_scan' => array('handler' => 'bratonien_tools_nc_wizard_scan_webdav_first'), 'nc_connector_wizard_scan' => array('handler' => 'bratonien_tools_nc_wizard_scan_webdav_first'),
'nc_connector_wizard_save_technical' => array('handler' => 'bratonien_tools_nc_wizard_save_technical_flow'), 'nc_connector_wizard_save_technical' => array('handler' => 'bratonien_tools_nc_wizard_save_technical_flow'),
'nc_connector_wizard_directory_browse' => array('handler' => 'bratonien_tools_nc_wizard_directory_browse'), 'nc_connector_wizard_directory_browse' => array('handler' => 'bratonien_tools_nc_transport_wizard_directory_browse'),
'nc_connector_wizard_directory_add' => array('handler' => 'bratonien_tools_nc_wizard_directory_add'), 'nc_connector_wizard_directory_add' => array('handler' => 'bratonien_tools_nc_wizard_directory_add'),
'nc_connector_wizard_directory_remove' => array('handler' => 'bratonien_tools_nc_wizard_directory_remove'), 'nc_connector_wizard_directory_remove' => array('handler' => 'bratonien_tools_nc_wizard_directory_remove'),
'nc_connector_wizard_save_mounts' => array('handler' => 'bratonien_tools_nc_wizard_save_sources_dispatch'), 'nc_connector_wizard_save_mounts' => array('handler' => 'bratonien_tools_nc_wizard_save_sources_dispatch'),

View File

@@ -8,12 +8,22 @@ require_once(BRATONIEN_TOOLS_PATH . 'include/watermark_base.inc.php');
function bratonien_tools_watermark_engine_enabled() function bratonien_tools_watermark_engine_enabled()
{ {
static $initialized = false;
static $enabled = false;
if ($initialized)
{
return $enabled;
}
$config = bratonien_tools_get_watermark_engine_config(); $config = bratonien_tools_get_watermark_engine_config();
$enabled = !empty($config['enabled']); $enabled = !empty($config['enabled']);
$initialized = true;
// Piwigo recalculates use_watermark for custom derivatives from the native // Piwigo recalculates use_watermark for custom derivatives from the native
// watermark file itself. Therefore an active Bratonien engine must keep the // watermark file itself. Therefore an active Bratonien engine must keep the
// native watermark file empty, not only the stored use_watermark flags false. // native watermark file empty, not only the stored use_watermark flags false.
// This initialization is required only once per PHP request.
if ($enabled) if ($enabled)
{ {
bratonien_tools_disable_piwigo_watermarks(); bratonien_tools_disable_piwigo_watermarks();

View File

@@ -0,0 +1,36 @@
<?php
if (!defined('PHPWG_ROOT_PATH'))
{
die('Hacking attempt!');
}
function bratonien_tools_filter_webdav_gallery_derivative_url($url, $params, $src_image, $rel_url)
{
if (!is_object($src_image) || empty($src_image->id) || empty($src_image->rel_path)) return $url;
$source_path = str_replace('\\', '/', (string)$src_image->rel_path);
if (!preg_match('#/nc-webdav-source/connection-[0-9]+/root-[0-9]+/.+$#', $source_path))
{
return $url;
}
try
{
$derivative = new DerivativeImage($params, $src_image);
if (!$derivative->same_as_source())
{
$path = $derivative->get_path();
if ($path !== '' && is_file($path) && is_readable($path))
{
return $url;
}
}
}
catch (Throwable $e)
{
error_log('Bratonien WebDAV gallery derivative lookup #'.(int)$src_image->id.': '.$e->getMessage());
}
$preview_url = bratonien_tools_webdav_image_url((int)$src_image->id, true);
return $preview_url ?: $url;
}

View File

@@ -7,6 +7,9 @@ if (!defined('PHPWG_ROOT_PATH'))
function bratonien_tools_webdav_image_source_info($image_id) function bratonien_tools_webdav_image_source_info($image_id)
{ {
static $cache = array(); static $cache = array();
static $connection_cache = array();
static $mapping_cache = array();
$image_id = (int)$image_id; $image_id = (int)$image_id;
if ($image_id < 1) return null; if ($image_id < 1) return null;
if (array_key_exists($image_id, $cache)) return $cache[$image_id]; if (array_key_exists($image_id, $cache)) return $cache[$image_id];
@@ -20,12 +23,12 @@ function bratonien_tools_webdav_image_source_info($image_id)
$absolute = $path; $absolute = $path;
if (strpos($absolute, '/') !== 0) if (strpos($absolute, '/') !== 0)
{ {
$absolute = PHPWG_ROOT_PATH.ltrim(preg_replace('#^\./#', '', $absolute), '/'); $absolute = PHPWG_ROOT_PATH.ltrim(preg_replace('#^\\./#', '', $absolute), '/');
} }
$resolved = realpath($absolute); $resolved = realpath($absolute);
if ($resolved === false) return $cache[$image_id] = null; if ($resolved === false) return $cache[$image_id] = null;
$normalized = str_replace('\\', '/', $resolved); $normalized = str_replace('\\\\', '/', $resolved);
if (!preg_match('#/nc-webdav-source/connection-([0-9]+)/root-([0-9]+)/(.*)$#', $normalized, $match)) if (!preg_match('#/nc-webdav-source/connection-([0-9]+)/root-([0-9]+)/(.*)$#', $normalized, $match))
{ {
return $cache[$image_id] = null; return $cache[$image_id] = null;
@@ -36,60 +39,82 @@ function bratonien_tools_webdav_image_source_info($image_id)
$relative_path = trim((string)$match[3], '/'); $relative_path = trim((string)$match[3], '/');
if ($relative_path === '') return $cache[$image_id] = null; if ($relative_path === '') return $cache[$image_id] = null;
$table = defined('BRATONIEN_TOOLS_NC_CONNECTIONS_TABLE') if (!array_key_exists($connection_id, $connection_cache))
? BRATONIEN_TOOLS_NC_CONNECTIONS_TABLE {
: $GLOBALS['prefixeTable'].'bratonien_tools_nc_connections'; $table = defined('BRATONIEN_TOOLS_NC_CONNECTIONS_TABLE')
$connection_result = pwg_query('SELECT config_json FROM `'.$table.'` WHERE id='.$connection_id.' LIMIT 1'); ? BRATONIEN_TOOLS_NC_CONNECTIONS_TABLE
if (!pwg_db_num_rows($connection_result)) return $cache[$image_id] = null; : $GLOBALS['prefixeTable'].'bratonien_tools_nc_connections';
$connection_row = pwg_db_fetch_assoc($connection_result); $connection_result = pwg_query('SELECT config_json FROM `'.$table.'` WHERE id='.$connection_id.' LIMIT 1');
$config = json_decode((string)$connection_row['config_json'], true); if (!pwg_db_num_rows($connection_result))
{
$connection_cache[$connection_id] = null;
}
else
{
$connection_row = pwg_db_fetch_assoc($connection_result);
$decoded = json_decode((string)$connection_row['config_json'], true);
$connection_cache[$connection_id] = is_array($decoded) ? $decoded : null;
}
}
$config = $connection_cache[$connection_id];
if (!is_array($config) || (string)($config['source_mode'] ?? '') !== 'webdav-placeholder') if (!is_array($config) || (string)($config['source_mode'] ?? '') !== 'webdav-placeholder')
{ {
return $cache[$image_id] = null; return $cache[$image_id] = null;
} }
$root_path = ''; $root_path = '';
$root_found = false;
$roots = isset($config['roots']) && is_array($config['roots']) ? $config['roots'] : array(); $roots = isset($config['roots']) && is_array($config['roots']) ? $config['roots'] : array();
foreach ($roots as $root) foreach ($roots as $root)
{ {
if ((int)($root['fileid'] ?? 0) === $root_fileid) if ((int)($root['fileid'] ?? 0) === $root_fileid)
{ {
$root_path = trim((string)($root['webdav_path'] ?? ''), '/'); $root_path = trim((string)($root['webdav_path'] ?? ''), '/');
$root_found = true;
break; break;
} }
} }
if ($root_path === '') return $cache[$image_id] = null; if (!$root_found) return $cache[$image_id] = null;
$webdav_path = $root_path.'/'.$relative_path; $root_is_base = $root_path === '';
$webdav_path = $root_is_base ? $relative_path : $root_path.'/'.$relative_path;
$content_type = ''; $content_type = '';
$size = 0; $size = 0;
$etag = ''; $etag = '';
$state_dir = rtrim((string)($config['state_dir'] ?? ''), '/'); if (!array_key_exists($connection_id, $mapping_cache))
if ($state_dir !== '')
{ {
$mapping_file = $state_dir.'/webdav-map.json'; $mapping_cache[$connection_id] = array();
if (is_readable($mapping_file)) $state_dir = rtrim((string)($config['state_dir'] ?? ''), '/');
if ($state_dir !== '')
{ {
$mapping = json_decode((string)file_get_contents($mapping_file), true); $mapping_file = $state_dir.'/webdav-map.json';
if (is_array($mapping) && isset($mapping['files']) && is_array($mapping['files'])) if (is_readable($mapping_file))
{ {
$entry = $mapping['files'][$resolved] ?? $mapping['files'][$normalized] ?? null; $mapping = json_decode((string)file_get_contents($mapping_file), true);
if (is_array($entry) && (string)($entry['kind'] ?? '') === 'file') if (is_array($mapping) && isset($mapping['files']) && is_array($mapping['files']))
{ {
$webdav_path = trim((string)($entry['webdav_path'] ?? $webdav_path), '/'); $mapping_cache[$connection_id] = $mapping['files'];
$content_type = (string)($entry['content_type'] ?? '');
$size = (int)($entry['size'] ?? 0);
$etag = (string)($entry['etag'] ?? '');
} }
} }
} }
} }
$entry = $mapping_cache[$connection_id][$resolved] ?? $mapping_cache[$connection_id][$normalized] ?? null;
if (is_array($entry) && (string)($entry['kind'] ?? '') === 'file')
{
$webdav_path = trim((string)($entry['webdav_path'] ?? $webdav_path), '/');
$content_type = (string)($entry['content_type'] ?? '');
$size = (int)($entry['size'] ?? 0);
$etag = (string)($entry['etag'] ?? '');
}
return $cache[$image_id] = array( return $cache[$image_id] = array(
'image_id'=>$image_id, 'image_id'=>$image_id,
'connection_id'=>$connection_id, 'connection_id'=>$connection_id,
'webdav_path'=>$webdav_path, 'webdav_path'=>$webdav_path,
'root_is_base'=>$root_is_base,
'content_type'=>$content_type, 'content_type'=>$content_type,
'size'=>$size, 'size'=>$size,
'etag'=>$etag, 'etag'=>$etag,
@@ -412,8 +437,16 @@ function bratonien_tools_filter_webdav_src_url($url, $src_image)
function bratonien_tools_filter_webdav_derivative_url($url, $params, $src_image, $rel_url) function bratonien_tools_filter_webdav_derivative_url($url, $params, $src_image, $rel_url)
{ {
if (!is_object($src_image) || empty($src_image->id)) return $url; if (!is_object($src_image) || empty($src_image->id)) return $url;
$info = bratonien_tools_webdav_image_source_info((int)$src_image->id);
if (!$info) return $url; // Hotpath: Bei einem bereits vorbereiteten WebDAV-Derivat keinerlei
// Connection-DB oder Mapping-Datei mehr anfassen. Der reale Quellpfad
// reicht aus, um Connector-Bilder sicher zu erkennen.
$source_path = $src_image->get_path();
$resolved_source = $source_path !== '' ? realpath($source_path) : false;
if ($resolved_source === false || !preg_match('#/nc-webdav-source/connection-[0-9]+/root-[0-9]+/#', str_replace('\\\\', '/', $resolved_source)))
{
return $url;
}
try try
{ {

View File

@@ -1,7 +1,7 @@
<?php <?php
/* /*
Plugin Name: Bratonien Tools Plugin Name: Bratonien Tools
Version: 0.9.6.29 Version: 0.9.7.8
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
@@ -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/watermark_runtime.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/webdav_image_runtime.inc.php'); require_once(BRATONIEN_TOOLS_PATH . 'include/webdav_image_runtime.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/webdav_gallery_runtime.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/public_selection.inc.php'); require_once(BRATONIEN_TOOLS_PATH . 'include/public_selection.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/picture_navigation.inc.php'); require_once(BRATONIEN_TOOLS_PATH . 'include/picture_navigation.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/batch_titles.inc.php'); require_once(BRATONIEN_TOOLS_PATH . 'include/batch_titles.inc.php');
@@ -24,11 +25,12 @@ require_once(BRATONIEN_TOOLS_PATH . 'include/album_shares.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_ws.inc.php'); require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_ws.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_orphan_ws.inc.php'); require_once(BRATONIEN_TOOLS_PATH . 'include/nc_orphan_ws.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_productive_ws.inc.php'); require_once(BRATONIEN_TOOLS_PATH . 'include/nc_productive_ws.inc.php');
require_once(BRATONIEN_TOOLS_PATH . 'include/nc_connector_scheduler.inc.php');
add_event_handler('get_admin_plugin_menu_links', 'bratonien_tools_admin_menu'); add_event_handler('get_admin_plugin_menu_links', 'bratonien_tools_admin_menu');
add_event_handler('get_derivative_url', 'bratonien_tools_filter_derivative_url', EVENT_HANDLER_PRIORITY_NEUTRAL, 4); add_event_handler('get_derivative_url', 'bratonien_tools_filter_derivative_url', EVENT_HANDLER_PRIORITY_NEUTRAL, 4);
add_event_handler('get_src_image_url', 'bratonien_tools_filter_webdav_src_url', EVENT_HANDLER_PRIORITY_NEUTRAL + 50, 2); add_event_handler('get_src_image_url', 'bratonien_tools_filter_webdav_src_url', EVENT_HANDLER_PRIORITY_NEUTRAL + 50, 2);
add_event_handler('get_derivative_url', 'bratonien_tools_filter_webdav_derivative_url', EVENT_HANDLER_PRIORITY_NEUTRAL + 50, 4); add_event_handler('get_derivative_url', 'bratonien_tools_filter_webdav_gallery_derivative_url', EVENT_HANDLER_PRIORITY_NEUTRAL + 50, 4);
add_event_handler('loc_end_element_set_global', 'bratonien_tools_batch_titles_register_action'); add_event_handler('loc_end_element_set_global', 'bratonien_tools_batch_titles_register_action');
add_event_handler('element_set_global_action', 'bratonien_tools_batch_titles_apply', EVENT_HANDLER_PRIORITY_NEUTRAL, 2); add_event_handler('element_set_global_action', 'bratonien_tools_batch_titles_apply', EVENT_HANDLER_PRIORITY_NEUTRAL, 2);
add_event_handler('init', 'bratonien_tools_prepare_connector_private_import', EVENT_HANDLER_PRIORITY_NEUTRAL - 30); add_event_handler('init', 'bratonien_tools_prepare_connector_private_import', EVENT_HANDLER_PRIORITY_NEUTRAL - 30);

View File

@@ -8,6 +8,8 @@ require_once(dirname(__FILE__) . '/include/album_shares.inc.php');
require_once(dirname(__FILE__) . '/include/database.class.php'); require_once(dirname(__FILE__) . '/include/database.class.php');
require_once(dirname(__FILE__) . '/tools/watermark_profiles.inc.php'); require_once(dirname(__FILE__) . '/tools/watermark_profiles.inc.php');
require_once(dirname(__FILE__) . '/include/dependencies.inc.php'); require_once(dirname(__FILE__) . '/include/dependencies.inc.php');
require_once(dirname(__FILE__) . '/include/nc_connector_scheduler.inc.php');
require_once(dirname(__FILE__) . '/include/nc_legacy_reset.inc.php');
class bratonien_tools_maintain extends PluginMaintain class bratonien_tools_maintain extends PluginMaintain
{ {
@@ -18,9 +20,14 @@ class bratonien_tools_maintain extends PluginMaintain
$dependency_messages = array(); $dependency_messages = array();
bratonien_tools_ensure_dependencies($dependency_messages); bratonien_tools_ensure_dependencies($dependency_messages);
if (!bratonien_tools_nc_scheduler_install())
{
$dependency_messages[] = 'Der native NC-Scheduler konnte sein Piwigo-Laufzeitverzeichnis nicht anlegen.';
}
if (function_exists('conf_update_param')) if (function_exists('conf_update_param'))
{ {
conf_update_param('bratonien_nc_scheduler_interval', 60);
conf_update_param('bratonien_dependency_status', json_encode(array( conf_update_param('bratonien_dependency_status', json_encode(array(
'checked_at' => time(), 'checked_at' => time(),
'messages' => $dependency_messages, 'messages' => $dependency_messages,
@@ -45,6 +52,18 @@ class bratonien_tools_maintain extends PluginMaintain
public function update($old_version, $new_version, &$errors = array()) public function update($old_version, $new_version, &$errors = array())
{ {
$this->prepare($errors); $this->prepare($errors);
if (version_compare((string)$old_version, '0.9.7.7', '<') && version_compare((string)$new_version, '0.9.7.7', '>='))
{
try
{
bratonien_tools_nc_reset_legacy_imports();
}
catch (Throwable $e)
{
$errors[] = 'Connector-Altbestand konnte nicht vollständig bereinigt werden: '.$e->getMessage();
}
}
} }
public function uninstall() public function uninstall()

View File

@@ -6,7 +6,7 @@ if (PHP_SAPI !== 'cli')
exit(1); exit(1);
} }
const BRATONIEN_WEBDAV_DERIVATIVE_BUILDER_VERSION = '0.9.6.1'; const BRATONIEN_WEBDAV_DERIVATIVE_BUILDER_VERSION = '0.9.7.8';
$options = getopt('', array('piwigo-root:', 'connection-id:')); $options = getopt('', array('piwigo-root:', 'connection-id:'));
$piwigo_root = rtrim((string)($options['piwigo-root'] ?? ''), '/'); $piwigo_root = rtrim((string)($options['piwigo-root'] ?? ''), '/');
@@ -46,20 +46,84 @@ if (!function_exists('bratonien_tools_webdav_image_source_info') || !function_ex
exit(1); exit(1);
} }
function bratonien_tools_webdav_modus_config()
{
global $conf;
$modus = $conf['modus_theme'] ?? array();
if (is_string($modus) && $modus !== '')
{
$decoded = @unserialize($modus);
if (is_array($decoded)) $modus = $decoded;
}
if (!is_array($modus)) $modus = array();
return array_merge(
array(
'index_photo_deriv'=>'2small',
'index_photo_deriv_hdpi'=>'xsmall',
),
$modus
);
}
function bratonien_tools_webdav_gallery_base_params()
{
$modus = bratonien_tools_webdav_modus_config();
$type = trim((string)($modus['index_photo_deriv'] ?? '2small'));
$params = $type !== '' ? @ImageStdParams::get_by_type($type) : null;
if ($params) return $params;
$params = ImageStdParams::get_by_type(IMG_THUMB);
if (!$params)
{
throw new RuntimeException('Piwigo-Galeriederivat ist nicht konfiguriert.');
}
return $params;
}
function bratonien_tools_webdav_modus_gallery_params(SrcImage $src, $base_params)
{
$row_height = (int)$base_params->max_height();
$candidates = array($base_params);
foreach (ImageStdParams::get_defined_type_map() as $params)
{
if (
$params->max_height() > $row_height
&& $params->sizing->max_crop == $base_params->sizing->max_crop
)
{
$candidates[] = $params;
if (count($candidates) === 3) break;
}
}
$selected = $base_params;
foreach ($candidates as $params)
{
$selected = $params;
$probe = new DerivativeImage($params, $src);
$size = $probe->get_size();
if ((int)$size[1] >= $row_height - 2) break;
}
return $selected;
}
try try
{ {
$variants = bratonien_tools_webdav_derivative_variants(); $gallery_base_params = bratonien_tools_webdav_gallery_base_params();
if (!$variants) $gallery_base_type = (string)$gallery_base_params->type;
{
throw new RuntimeException('Keine Piwigo-Derivate konfiguriert.');
}
$images = 0; $images = 0;
$generated_or_ready = 0; $generated_or_ready = 0;
$identity = 0; $identity = 0;
$metadata_repaired = 0; $metadata_repaired = 0;
$legacy_derivatives_rebuilt = 0;
$errors = 0; $errors = 0;
$error_lines = array(); $error_lines = array();
$variant_counts = array();
$result = pwg_query('SELECT * FROM '.IMAGES_TABLE.' ORDER BY id'); $result = pwg_query('SELECT * FROM '.IMAGES_TABLE.' ORDER BY id');
while ($row = pwg_db_fetch_assoc($result)) while ($row = pwg_db_fetch_assoc($result))
@@ -109,34 +173,59 @@ try
} }
$src = new SrcImage($row); $src = new SrcImage($row);
foreach ($variants as $variant_name => $params) try
{ {
try $gallery_params = bratonien_tools_webdav_modus_gallery_params($src, $gallery_base_params);
{ $variant = (string)$gallery_params->type;
$probe = new DerivativeImage($params, $src); if ($variant === '') $variant = 'custom';
if ($probe->same_as_source()) $variant_counts[$variant] = ($variant_counts[$variant] ?? 0) + 1;
{
$identity++;
continue;
}
$detail = ''; $probe = new DerivativeImage($gallery_params, $src);
if (bratonien_tools_webdav_generate_derivative($params, $src, $detail)) if ($probe->same_as_source())
{
$identity++;
continue;
}
$target = $probe->get_path();
if ($target !== '' && is_file($target) && is_readable($target))
{
$expected_size = $gallery_params->compute_final_size(array($preview_width, $preview_height));
$existing_size = @getimagesize($target);
if (
is_array($expected_size)
&& isset($expected_size[0], $expected_size[1])
&& is_array($existing_size)
&& isset($existing_size[0], $existing_size[1])
&& ((int)$existing_size[0] !== (int)$expected_size[0] || (int)$existing_size[1] !== (int)$expected_size[1])
)
{ {
$generated_or_ready++; if (!@unlink($target))
} {
else $errors++;
{ $error_lines[] = 'Bild #'.$image_id.' Galerie: falsches Alt-Derivat konnte nicht entfernt werden: '.$target;
$errors++; continue;
$error_lines[] = 'Bild #'.$image_id.' '.$variant_name.': '.($detail !== '' ? $detail : 'Derivat konnte nicht erzeugt werden.'); }
$legacy_derivatives_rebuilt++;
} }
} }
catch (Throwable $e)
$detail = '';
if (bratonien_tools_webdav_generate_derivative($gallery_params, $src, $detail))
{
$generated_or_ready++;
}
else
{ {
$errors++; $errors++;
$error_lines[] = 'Bild #'.$image_id.' '.$variant_name.': '.get_class($e).': '.$e->getMessage(); $error_lines[] = 'Bild #'.$image_id.' Galerie: '.($detail !== '' ? $detail : 'Galeriederivat konnte nicht erzeugt werden.');
} }
} }
catch (Throwable $e)
{
$errors++;
$error_lines[] = 'Bild #'.$image_id.' Galerie: '.get_class($e).': '.$e->getMessage();
}
} }
if ($metadata_repaired > 0) if ($metadata_repaired > 0)
@@ -157,12 +246,22 @@ try
} }
} }
ksort($variant_counts);
$variant_parts = array();
foreach ($variant_counts as $type=>$count)
{
$variant_parts[] = $type.':'.$count;
}
echo 'WebDAV-Derivative-Builder: version='.BRATONIEN_WEBDAV_DERIVATIVE_BUILDER_VERSION. echo 'WebDAV-Derivative-Builder: version='.BRATONIEN_WEBDAV_DERIVATIVE_BUILDER_VERSION.
' bilder='.$images. ' bilder='.$images.
' varianten='.count($variants). ' varianten_pro_bild=1'.
' modus_basis='.$gallery_base_type.
' modus_varianten='.implode(',', $variant_parts).
' bereit='.$generated_or_ready. ' bereit='.$generated_or_ready.
' identisch='.$identity. ' identisch='.$identity.
' metadaten_repariert='.$metadata_repaired. ' metadaten_repariert='.$metadata_repaired.
' alt_derivate_neu='.$legacy_derivatives_rebuilt.
' fehler='.$errors."\n"; ' fehler='.$errors."\n";
exit($errors > 0 ? 1 : 0); exit($errors > 0 ? 1 : 0);

View File

@@ -14,6 +14,7 @@ import getpass
import json import json
import os import os
import shutil import shutil
import socket
import ssl import ssl
import sys import sys
import tempfile import tempfile
@@ -21,6 +22,7 @@ import urllib.error
import urllib.parse import urllib.parse
import urllib.request import urllib.request
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from contextlib import contextmanager
from pathlib import Path, PurePosixPath from pathlib import Path, PurePosixPath
DAV = "DAV:" DAV = "DAV:"
@@ -52,9 +54,37 @@ def safe_local_name(name: str) -> str:
return name return name
@contextmanager
def pinned_resolution(host: str, ip: str):
host = host.strip("[]").lower()
ip = ip.strip("[]")
if not host or not ip or host == ip:
yield
return
original = socket.getaddrinfo
def resolve(name, port, family=0, type=0, proto=0, flags=0):
normalized = str(name).strip("[]").lower()
if normalized == host:
return original(ip, port, family, type, proto, flags)
return original(name, port, family, type, proto, flags)
socket.getaddrinfo = resolve
try:
yield
finally:
socket.getaddrinfo = original
class WebDavClient: class WebDavClient:
def __init__(self, base_url: str, user: str, password: str, timeout: int = 30) -> None: def __init__(self, base_url: str, user: str, password: str, timeout: int = 30, connect_ip: str = "") -> None:
self.base_url = base_url.rstrip("/") self.base_url = base_url.rstrip("/")
parsed = urllib.parse.urlparse(self.base_url)
if parsed.scheme not in {"http", "https"} or not parsed.hostname:
fail("Nextcloud base URL must use HTTP or HTTPS and contain a host")
self.host = parsed.hostname.strip("[]")
self.connect_ip = connect_ip.strip("[]") or self.host
self.user = user self.user = user
self.password = password self.password = password
self.timeout = timeout self.timeout = timeout
@@ -84,9 +114,10 @@ class WebDavClient:
request.add_header("Depth", "1") request.add_header("Depth", "1")
request.add_header("Content-Type", "application/xml; charset=utf-8") request.add_header("Content-Type", "application/xml; charset=utf-8")
try: try:
with urllib.request.urlopen(request, timeout=self.timeout, context=self.context) as response: with pinned_resolution(self.host, self.connect_ip):
status = response.status with urllib.request.urlopen(request, timeout=self.timeout, context=self.context) as response:
payload = response.read() status = response.status
payload = response.read()
except urllib.error.HTTPError as error: except urllib.error.HTTPError as error:
if error.code in {401, 403}: if error.code in {401, 403}:
fail("Nextcloud rejected the WebDAV credentials or directory access") fail("Nextcloud rejected the WebDAV credentials or directory access")
@@ -96,14 +127,14 @@ class WebDavClient:
if status != 207: if status != 207:
fail(f"Nextcloud PROPFIND returned HTTP {status}") fail(f"Nextcloud PROPFIND returned HTTP {status}")
base_path = urllib.parse.unquote(urllib.parse.urlparse(url).path).rstrip("/")
current: dict[str, object] | None = None
children: list[dict[str, object]] = []
try: try:
root = ET.fromstring(payload) root = ET.fromstring(payload)
except ET.ParseError as error: except ET.ParseError as error:
raise RuntimeError("Nextcloud returned invalid WebDAV XML") from error raise RuntimeError("Nextcloud returned invalid WebDAV XML") from error
base_path = urllib.parse.unquote(urllib.parse.urlparse(url).path).rstrip("/")
current: dict[str, object] | None = None
children: list[dict[str, object]] = []
for response in root.findall(f"{{{DAV}}}response"): for response in root.findall(f"{{{DAV}}}response"):
href = response.findtext(f"{{{DAV}}}href", default="") href = response.findtext(f"{{{DAV}}}href", default="")
href_path = urllib.parse.unquote(urllib.parse.urlparse(href).path).rstrip("/") href_path = urllib.parse.unquote(urllib.parse.urlparse(href).path).rstrip("/")
@@ -213,6 +244,7 @@ def atomic_text(path: Path, text: str) -> None:
def main() -> int: def main() -> int:
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("--base-url", required=True) parser.add_argument("--base-url", required=True)
parser.add_argument("--connect-ip", default="", help="IP address used for the TCP connection while preserving the URL host for HTTP Host and TLS SNI")
parser.add_argument("--user", required=True) parser.add_argument("--user", required=True)
parser.add_argument("--password-file", type=Path) parser.add_argument("--password-file", type=Path)
parser.add_argument("--root", action="append", required=True, help="WebDAV path relative to the authenticated user's files root") parser.add_argument("--root", action="append", required=True, help="WebDAV path relative to the authenticated user's files root")
@@ -241,7 +273,7 @@ def main() -> int:
shutil.rmtree(staging) shutil.rmtree(staging)
staging.mkdir(parents=True) staging.mkdir(parents=True)
client = WebDavClient(args.base_url, args.user, password, max(1, args.timeout)) client = WebDavClient(args.base_url, args.user, password, max(1, args.timeout), args.connect_ip)
mapping: dict[str, dict[str, object]] = {} mapping: dict[str, dict[str, object]] = {}
manifest: list[str] = [] manifest: list[str] = []
total_files = total_folders = total_skipped = 0 total_files = total_folders = total_skipped = 0
@@ -303,6 +335,7 @@ def main() -> int:
atomic_json(args.mapping, { atomic_json(args.mapping, {
"version": 1, "version": 1,
"base_url": args.base_url.rstrip("/"), "base_url": args.base_url.rstrip("/"),
"connect_ip": client.connect_ip,
"user": args.user, "user": args.user,
"files": final_mapping, "files": final_mapping,
}) })
@@ -311,6 +344,7 @@ def main() -> int:
"files": total_files, "files": total_files,
"folders": total_folders, "folders": total_folders,
"skipped": total_skipped, "skipped": total_skipped,
"connect_ip": client.connect_ip,
"source_dir": str(source_dir), "source_dir": str(source_dir),
"manifest": str(args.manifest), "manifest": str(args.manifest),
"mapping": str(args.mapping), "mapping": str(args.mapping),

View File

@@ -6,6 +6,8 @@ if (PHP_SAPI !== 'cli')
exit(1); exit(1);
} }
require_once(dirname(__DIR__, 2).'/include/nc_transport.inc.php');
const BRATONIEN_WEBDAV_PREVIEW_VERSION = 2; const BRATONIEN_WEBDAV_PREVIEW_VERSION = 2;
const BRATONIEN_WEBDAV_PREVIEW_MAX_EDGE = 4096; const BRATONIEN_WEBDAV_PREVIEW_MAX_EDGE = 4096;
const BRATONIEN_WEBDAV_PREVIEW_JPEG_QUALITY = 88; const BRATONIEN_WEBDAV_PREVIEW_JPEG_QUALITY = 88;
@@ -24,7 +26,7 @@ function quote_webdav_path($path)
function fetch_remote_blob($url, $user, $password) function fetch_remote_blob($url, $user, $password)
{ {
$ch = curl_init($url); $ch = curl_init($url);
curl_setopt_array($ch, array( $options = array(
CURLOPT_RETURNTRANSFER => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false, CURLOPT_FOLLOWLOCATION => false,
CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_CONNECTTIMEOUT => 10,
@@ -32,8 +34,10 @@ function fetch_remote_blob($url, $user, $password)
CURLOPT_HTTPAUTH => CURLAUTH_BASIC, CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
CURLOPT_USERPWD => $user.':'.$password, CURLOPT_USERPWD => $user.':'.$password,
CURLOPT_FAILONERROR => false, CURLOPT_FAILONERROR => false,
CURLOPT_USERAGENT => 'Bratonien-Tools-WebDAV-Precache/0.9.6.1', CURLOPT_USERAGENT => 'Bratonien-Tools-WebDAV-Precache/0.9.7.1',
)); );
bratonien_tools_nc_transport_apply_curl($options, $url);
curl_setopt_array($ch, $options);
$body = curl_exec($ch); $body = curl_exec($ch);
$errno = curl_errno($ch); $errno = curl_errno($ch);
$error = curl_error($ch); $error = curl_error($ch);

View File

@@ -0,0 +1,24 @@
#!/usr/bin/env php
<?php
if (PHP_SAPI !== 'cli')
{
fwrite(STDERR, "CLI only\n");
exit(1);
}
require_once(dirname(__DIR__, 2).'/include/nc_transport.inc.php');
try
{
if ($argc !== 2) throw new RuntimeException('Aufruf: resolve-nextcloud-target.php <nextcloud-url>');
$url = rtrim(trim((string)$argv[1]), '/');
bratonien_tools_nc_transport_scheme($url);
$host = bratonien_tools_nc_transport_host($url);
echo bratonien_tools_nc_transport_public_ip($host)."\n";
exit(0);
}
catch (Throwable $e)
{
fwrite(STDERR, $e->getMessage()."\n");
exit(1);
}

108
runtime/native-runner.php Normal file
View File

@@ -0,0 +1,108 @@
#!/usr/bin/env php
<?php
if (PHP_SAPI !== 'cli')
{
fwrite(STDERR, "CLI only\n");
exit(1);
}
$options = getopt('', array('connection-id::'));
$connectionId = isset($options['connection-id']) ? (int)$options['connection-id'] : 0;
$pluginRoot = dirname(__DIR__);
$piwigoRoot = dirname($pluginRoot, 2);
$base = rtrim($piwigoRoot, '/').'/_data/bratonien-tools';
$schedulerDir = $base.'/nc-connector-scheduler';
$stateFile = $schedulerDir.'/state.json';
$runtimeDir = $base.'/nc-connector-runtime';
$stateRoot = $base.'/nc-connector-state';
function native_scheduler_state($path)
{
if (!is_readable($path)) return array();
$decoded = json_decode((string)@file_get_contents($path), true);
return is_array($decoded) ? $decoded : array();
}
function native_scheduler_write($path, array $state)
{
$json = json_encode($state, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
if (!is_string($json)) return false;
$tmp = $path.'.tmp';
if (@file_put_contents($tmp, $json."\n", LOCK_EX) === false) return false;
@chmod($tmp, 0640);
return @rename($tmp, $path);
}
foreach (array($schedulerDir, $runtimeDir, $stateRoot) as $dir)
{
if (!is_dir($dir) && !@mkdir($dir, 0750, true) && !is_dir($dir))
{
fwrite(STDERR, "Runtime-Verzeichnis konnte nicht angelegt werden: {$dir}\n");
exit(1);
}
}
$state = native_scheduler_state($stateFile);
$state['enabled'] = true;
$state['mode'] = 'piwigo-native';
$state['state'] = 'running';
$state['message'] = $connectionId > 0 ? 'NC-Abgleich für Verbindung #'.$connectionId.' läuft.' : 'NC-Abgleich läuft.';
$state['started_at'] = time();
$state['timestamp'] = time();
$state['connection_id'] = $connectionId;
native_scheduler_write($stateFile, $state);
$env = $_ENV;
$env['PATH'] = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin';
$env['BRATONIEN_NC_NATIVE'] = '1';
$env['BRATONIEN_NC_PIWIGO_ROOT'] = $piwigoRoot;
$env['BRATONIEN_NC_CONFIG_DIR'] = $runtimeDir;
$env['BRATONIEN_NC_STATE_ROOT'] = $stateRoot;
$env['BRATONIEN_NC_CONNECTION_ID'] = (string)$connectionId;
$env['LC_ALL'] = 'C';
$env['LANG'] = 'C';
$bash = is_executable('/usr/bin/bash') ? '/usr/bin/bash' : '/bin/bash';
$command = array($bash, $pluginRoot.'/runtime/run-all.sh');
$spec = array(
0=>array('file','/dev/null','r'),
1=>array('pipe','w'),
2=>array('pipe','w'),
);
$process = @proc_open($command, $spec, $pipes, null, $env);
$stdout = '';
$stderr = '';
$exit = 1;
if (is_resource($process))
{
$stdout = (string)stream_get_contents($pipes[1]);
$stderr = (string)stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$exit = proc_close($process);
}
else
{
$stderr = 'run-all.sh konnte nicht gestartet werden.';
}
$state = native_scheduler_state($stateFile);
$state['enabled'] = true;
$state['mode'] = 'piwigo-native';
$state['state'] = $exit === 0 ? 'success' : 'error';
$state['message'] = $exit === 0
? ($connectionId > 0 ? 'NC-Abgleich für Verbindung #'.$connectionId.' erfolgreich abgeschlossen.' : 'NC-Abgleich erfolgreich abgeschlossen.')
: ($connectionId > 0 ? 'NC-Abgleich für Verbindung #'.$connectionId.' fehlgeschlagen.' : 'NC-Abgleich fehlgeschlagen.');
$state['timestamp'] = time();
$state['finished_at'] = time();
$state['exit_code'] = $exit;
$state['connection_id'] = $connectionId;
$state['stdout'] = trim($stdout);
$state['stderr'] = trim($stderr);
if (empty($state['next_due']) || (int)$state['next_due'] < time())
{
$state['next_due'] = time() + 60;
}
native_scheduler_write($stateFile, $state);
exit($exit === 0 ? 0 : 1);

View File

@@ -78,8 +78,11 @@ function webdav_source_fingerprint($baseUrl, $user, array $roots)
$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'; $nativeMode = getenv('BRATONIEN_NC_NATIVE') === '1';
$stateRoot = '/var/lib/bratonien-tools/nc-connector'; $configDir = trim((string)getenv('BRATONIEN_NC_CONFIG_DIR'));
if ($configDir === '') $configDir = $nativeMode ? rtrim($piwigoRoot, '/').'/_data/bratonien-tools/nc-connector-runtime' : '/etc/bratonien-tools/nc-connector';
$stateRoot = trim((string)getenv('BRATONIEN_NC_STATE_ROOT'));
if ($stateRoot === '') $stateRoot = $nativeMode ? rtrim($piwigoRoot, '/').'/_data/bratonien-tools/nc-connector-state' : '/var/lib/bratonien-tools/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';
$legacyGalleryRoot = rtrim($piwigoRoot, '/').'/galleries'; $legacyGalleryRoot = rtrim($piwigoRoot, '/').'/galleries';
@@ -107,11 +110,12 @@ try
$rows = $db->query("SELECT id,name,adapter,config_json,secret_blob FROM `{$table}` ORDER BY id DESC"); $rows = $db->query("SELECT id,name,adapter,config_json,secret_blob FROM `{$table}` ORDER BY id DESC");
if (!$rows) fail_webdav_reconcile('Connector-Verbindungen konnten nicht gelesen werden: '.$db->error); if (!$rows) fail_webdav_reconcile('Connector-Verbindungen konnten nicht gelesen werden: '.$db->error);
foreach (array($configDir, $publicSourceRoot, $publicGalleryRoot) as $dir) foreach (array($configDir, $stateRoot, $publicSourceRoot, $publicGalleryRoot) as $dir)
{ {
if (!is_dir($dir) && !mkdir($dir, $dir === $configDir ? 0700 : 0755, 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);
@chmod($publicSourceRoot, 0755); @chmod($publicSourceRoot, 0755);
@chmod($publicGalleryRoot, 0755); @chmod($publicGalleryRoot, 0755);
@@ -150,7 +154,7 @@ try
$seenFingerprints[$fingerprint] = $id; $seenFingerprints[$fingerprint] = $id;
$known[$id] = true; $known[$id] = true;
$stateDir = rtrim((string)($config['state_dir'] ?? ''), '/'); $stateDir = $nativeMode ? $stateRoot.'/connection-'.$id : 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); @chmod($stateDir, 0750);
@@ -219,7 +223,7 @@ try
$config['parallel_gallery_root'] = $galleryRoot; $config['parallel_gallery_root'] = $galleryRoot;
$config['source_fingerprint'] = $fingerprint; $config['source_fingerprint'] = $fingerprint;
$config['runtime'] = array( $config['runtime'] = array(
'mode'=>'webdav', 'mode'=>$nativeMode ? 'piwigo-native-webdav' : 'webdav',
'config'=>$configPath, 'config'=>$configPath,
'piwigo_sync_enabled'=>true, 'piwigo_sync_enabled'=>true,
'reconciled_at'=>date('Y-m-d H:i:s'), 'reconciled_at'=>date('Y-m-d H:i:s'),

View File

@@ -1,10 +1,26 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
CONFIG_DIR="/etc/bratonien-tools/nc-connector"
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
PIWIGO_ROOT_DEFAULT="${BRATONIEN_NC_PIWIGO_ROOT:-$(cd -- "$SCRIPT_DIR/../../.." && pwd)}"
CONFIG_DIR="${BRATONIEN_NC_CONFIG_DIR:-/etc/bratonien-tools/nc-connector}"
NATIVE_MODE="${BRATONIEN_NC_NATIVE:-0}"
TARGET_CONNECTION_ID="${BRATONIEN_NC_CONNECTION_ID:-0}"
GLOBAL_LOCK_DIR="${PIWIGO_ROOT_DEFAULT%/}/_data/bratonien-tools/nc-connector-scheduler"
GLOBAL_LOCK_FILE="$GLOBAL_LOCK_DIR/worker.lock"
mkdir -p -- "$GLOBAL_LOCK_DIR"
exec 8>"$GLOBAL_LOCK_FILE"
if ! flock -n 8; then
echo "NC Connector: ein Lauf ist bereits aktiv."
exit 0
fi
shopt -s nullglob shopt -s nullglob
if [[ ! "$TARGET_CONNECTION_ID" =~ ^[0-9]+$ ]]; then
echo "NC Connector: ungültige Ziel-Verbindungs-ID: $TARGET_CONNECTION_ID" >&2
exit 1
fi
read_config_value() { read_config_value() {
local key="$1" local key="$1"
local file="$2" local file="$2"
@@ -39,10 +55,14 @@ write_route_status() {
' "$ROUTE_STATUS_FILE" "$route" "$label" "$detail" "$success" ' "$ROUTE_STATUS_FILE" "$route" "$label" "$detail" "$success"
} }
php "$SCRIPT_DIR/reconcile.php" if [[ "$NATIVE_MODE" != "1" ]]; then
php "$SCRIPT_DIR/reconcile.php"
fi
php "$SCRIPT_DIR/reconcile-webdav.php" php "$SCRIPT_DIR/reconcile-webdav.php"
php "$SCRIPT_DIR/cleanup-webdav-piwigo.php" php "$SCRIPT_DIR/cleanup-webdav-piwigo.php"
php "$SCRIPT_DIR/cleanup-stale.php" if [[ "$NATIVE_MODE" != "1" ]]; then
php "$SCRIPT_DIR/cleanup-stale.php"
fi
configs=("$CONFIG_DIR"/connection-*.conf) configs=("$CONFIG_DIR"/connection-*.conf)
webdav_configs=("$CONFIG_DIR"/webdav-connection-*.conf) webdav_configs=("$CONFIG_DIR"/webdav-connection-*.conf)
@@ -55,16 +75,21 @@ fi
route_piwigo_root="" route_piwigo_root=""
for candidate in "${webdav_configs[@]}" "${configs[@]}"; do for candidate in "${webdav_configs[@]}" "${configs[@]}"; do
[[ -f "$candidate" ]] || continue [[ -f "$candidate" ]] || continue
candidate_id="$(read_config_value CONNECTION_ID "$candidate")"
if [[ "$TARGET_CONNECTION_ID" -gt 0 && "$candidate_id" != "$TARGET_CONNECTION_ID" ]]; then
continue
fi
route_piwigo_root="$(read_config_value PIWIGO_ROOT "$candidate")" route_piwigo_root="$(read_config_value PIWIGO_ROOT "$candidate")"
[[ -n "$route_piwigo_root" ]] && break [[ -n "$route_piwigo_root" ]] && break
done done
[[ -n "$route_piwigo_root" ]] || route_piwigo_root="/var/www/piwigo" [[ -n "$route_piwigo_root" ]] || route_piwigo_root="$PIWIGO_ROOT_DEFAULT"
ROUTE_STATUS_FILE="${route_piwigo_root%/}/_data/bratonien-tools/nc-connector-status/route-status.json" ROUTE_STATUS_FILE="${route_piwigo_root%/}/_data/bratonien-tools/nc-connector-status/route-status.json"
failure_count=0 failure_count=0
webdav_count=0 webdav_count=0
local_count=0 local_count=0
summary_parts=() summary_parts=()
matched_count=0
for config in "${webdav_configs[@]}"; do for config in "${webdav_configs[@]}"; do
[[ -f "$config" ]] || continue [[ -f "$config" ]] || continue
@@ -76,7 +101,11 @@ for config in "${webdav_configs[@]}"; do
summary_parts+=("$name: ungueltige Verbindungs-ID") summary_parts+=("$name: ungueltige Verbindungs-ID")
continue continue
fi fi
if [[ "$TARGET_CONNECTION_ID" -gt 0 && "$connection_id" != "$TARGET_CONNECTION_ID" ]]; then
continue
fi
matched_count=$((matched_count + 1))
webdav_count=$((webdav_count + 1)) webdav_count=$((webdav_count + 1))
echo "NC Connector WebDAV #$connection_id: $name" echo "NC Connector WebDAV #$connection_id: $name"
output="" output=""
@@ -91,43 +120,55 @@ for config in "${webdav_configs[@]}"; do
fi fi
done done
for config in "${configs[@]}"; do if [[ "$NATIVE_MODE" != "1" ]]; then
[[ -f "$config" ]] || continue for config in "${configs[@]}"; do
name="$(basename "$config")" [[ -f "$config" ]] || continue
connection_id="0" name="$(basename "$config")"
if [[ "$name" =~ ^connection-([0-9]+)\.conf$ ]]; then connection_id="0"
connection_id="${BASH_REMATCH[1]}" if [[ "$name" =~ ^connection-([0-9]+)\.conf$ ]]; then
fi connection_id="${BASH_REMATCH[1]}"
if [[ "$connection_id" -lt 1 ]]; then fi
echo "NC Connector: $name besitzt keine gueltige Verbindungs-ID." >&2 if [[ "$connection_id" -lt 1 ]]; then
failure_count=$((failure_count + 1)) echo "NC Connector: $name besitzt keine gueltige Verbindungs-ID." >&2
summary_parts+=("$name: ungueltige Verbindungs-ID") failure_count=$((failure_count + 1))
continue summary_parts+=("$name: ungueltige Verbindungs-ID")
fi continue
fi
if [[ "$TARGET_CONNECTION_ID" -gt 0 && "$connection_id" != "$TARGET_CONNECTION_ID" ]]; then
continue
fi
piwigo_root="$(read_config_value PIWIGO_ROOT "$config")" matched_count=$((matched_count + 1))
[[ -n "$piwigo_root" ]] || piwigo_root="/var/www/piwigo" piwigo_root="$(read_config_value PIWIGO_ROOT "$config")"
tombstone_dir="${piwigo_root%/}/_data/bratonien-tools/nc-connector-status" [[ -n "$piwigo_root" ]] || piwigo_root="$PIWIGO_ROOT_DEFAULT"
if [[ -f "$tombstone_dir/deleted-$connection_id" ]]; then tombstone_dir="${piwigo_root%/}/_data/bratonien-tools/nc-connector-status"
echo "NC Connector: Verbindung $connection_id wurde geloescht; Laufzeitdateien werden entfernt." if [[ -f "$tombstone_dir/deleted-$connection_id" ]]; then
rm -f -- "$CONFIG_DIR/connection-$connection_id.conf" \ echo "NC Connector: Verbindung $connection_id wurde geloescht; Laufzeitdateien werden entfernt."
"$CONFIG_DIR/connection-$connection_id.db-password" \ rm -f -- "$CONFIG_DIR/connection-$connection_id.conf" \
"$CONFIG_DIR/connection-$connection_id.piwigo-password" \ "$CONFIG_DIR/connection-$connection_id.db-password" \
"$CONFIG_DIR/connection-$connection_id.storages.tsv" \ "$CONFIG_DIR/connection-$connection_id.piwigo-password" \
"$CONFIG_DIR/connection-$connection_id.roots.tsv" "$CONFIG_DIR/connection-$connection_id.storages.tsv" \
rm -f -- "$tombstone_dir/deleted-$connection_id" "$CONFIG_DIR/connection-$connection_id.roots.tsv"
continue rm -f -- "$tombstone_dir/deleted-$connection_id"
fi continue
fi
local_count=$((local_count + 1)) local_count=$((local_count + 1))
echo "NC Connector Local #$connection_id: $name" echo "NC Connector Local #$connection_id: $name"
if env PIWIGO_CONFIG="$config" bash "$SCRIPT_DIR/sync.sh"; then if env PIWIGO_CONFIG="$config" bash "$SCRIPT_DIR/sync.sh"; then
summary_parts+=("Local #$connection_id erfolgreich") summary_parts+=("Local #$connection_id erfolgreich")
else else
failure_count=$((failure_count + 1)) failure_count=$((failure_count + 1))
summary_parts+=("Local #$connection_id fehlgeschlagen") summary_parts+=("Local #$connection_id fehlgeschlagen")
fi fi
done done
fi
if [[ "$TARGET_CONNECTION_ID" -gt 0 && "$matched_count" -eq 0 ]]; then
write_route_status "failed" "FEHLER - Verbindung #$TARGET_CONNECTION_ID" "Keine Laufzeitkonfiguration für Verbindung #$TARGET_CONNECTION_ID gefunden." "0"
echo "NC Connector: keine Laufzeitkonfiguration für Verbindung #$TARGET_CONNECTION_ID gefunden." >&2
exit 1
fi
summary_detail="$(IFS='; '; printf '%s' "${summary_parts[*]}")" summary_detail="$(IFS='; '; printf '%s' "${summary_parts[*]}")"
[[ -n "$summary_detail" ]] || summary_detail="Keine Verbindung wurde ausgefuehrt." [[ -n "$summary_detail" ]] || summary_detail="Keine Verbindung wurde ausgefuehrt."
@@ -144,10 +185,10 @@ if [[ "$failure_count" -eq 0 ]]; then
label="Local" label="Local"
fi fi
write_route_status "$route" "$label" "$summary_detail" "1" write_route_status "$route" "$label" "$summary_detail" "1"
echo "NC Connector: alle Verbindungen wurden erfolgreich verarbeitet." echo "NC Connector: angeforderte Verbindung wurde erfolgreich verarbeitet."
exit 0 exit 0
fi fi
write_route_status "failed" "FEHLER - mindestens eine Verbindung" "$summary_detail" "0" write_route_status "failed" "FEHLER - angeforderte Verbindung" "$summary_detail" "0"
echo "NC Connector: mindestens eine Verbindung ist fehlgeschlagen." >&2 echo "NC Connector: die angeforderte Verbindung ist fehlgeschlagen." >&2
exit 1 exit 1

View File

@@ -75,8 +75,12 @@ done < "$WEBDAV_ROOTS_FILE"
[[ ${#ROOT_ARGS[@]} -gt 0 ]] || { write_status error "Keine WebDAV-Wurzeln konfiguriert"; exit 1; } [[ ${#ROOT_ARGS[@]} -gt 0 ]] || { write_status error "Keine WebDAV-Wurzeln konfiguriert"; exit 1; }
WEBDAV_CONNECT_IP="$(php "$SCRIPT_DIR/lib/resolve-nextcloud-target.php" "$WEBDAV_BASE_URL")"
[[ -n "$WEBDAV_CONNECT_IP" ]] || { write_status error "Nextcloud-Zieladresse konnte nicht ermittelt werden"; exit 1; }
python3 "$SCRIPT_DIR/lib/build_webdav_placeholder_source.py" \ python3 "$SCRIPT_DIR/lib/build_webdav_placeholder_source.py" \
--base-url "$WEBDAV_BASE_URL" \ --base-url "$WEBDAV_BASE_URL" \
--connect-ip "$WEBDAV_CONNECT_IP" \
--user "$WEBDAV_USER" \ --user "$WEBDAV_USER" \
--password-file "$WEBDAV_PASSWORD_FILE" \ --password-file "$WEBDAV_PASSWORD_FILE" \
"${ROOT_ARGS[@]}" \ "${ROOT_ARGS[@]}" \
@@ -134,21 +138,33 @@ if [[ "${PIWIGO_SYNC_ENABLED:-0}" == "1" ]]; then
exit "$PIWIGO_EXIT" exit "$PIWIGO_EXIT"
fi fi
MEDIA_UNIT="bratonien-nc-media-${CONNECTION_ID}-$(date +%s)" if [[ "${BRATONIEN_NC_NATIVE:-0}" == "1" ]]; then
if ! systemd-run \ if command -v timeout >/dev/null 2>&1; then
--quiet \ if ! timeout 30m env PIWIGO_CONFIG="$CONFIG_FILE" bash "$SCRIPT_DIR/build-webdav-media.sh"; then
--collect \ write_status error "Bildaufbereitung ist fehlgeschlagen oder hat das 30-Minuten-Limit erreicht"
--unit="$MEDIA_UNIT" \ exit 1
--property=RuntimeMaxSec=30min \ fi
--setenv="PIWIGO_CONFIG=$CONFIG_FILE" \ elif ! env PIWIGO_CONFIG="$CONFIG_FILE" bash "$SCRIPT_DIR/build-webdav-media.sh"; then
/usr/bin/env bash "$SCRIPT_DIR/build-webdav-media.sh"; then write_status error "Bildaufbereitung ist fehlgeschlagen"
write_status error "Bildaufbereitung konnte nicht im Hintergrund gestartet werden" exit 1
exit 1 fi
else
MEDIA_UNIT="bratonien-nc-media-${CONNECTION_ID}-$(date +%s)"
if ! systemd-run \
--quiet \
--collect \
--unit="$MEDIA_UNIT" \
--property=RuntimeMaxSec=30min \
--setenv="PIWIGO_CONFIG=$CONFIG_FILE" \
/usr/bin/env bash "$SCRIPT_DIR/build-webdav-media.sh"; then
write_status error "Bildaufbereitung konnte nicht im Hintergrund gestartet werden"
exit 1
fi
fi fi
if grep -q 'Piwigo-Synchronisierung per API erfolgreich' <<<"$PIWIGO_OUTPUT"; then if grep -q 'Piwigo-Synchronisierung per API erfolgreich' <<<"$PIWIGO_OUTPUT"; then
write_status ok \ write_status ok \
"WebDAV eingelesen und Piwigo synchronisiert; Bildaufbereitung läuft im Hintergrund" \ "WebDAV eingelesen und Piwigo synchronisiert; Bildaufbereitung abgeschlossen" \
"" \ "" \
"api" \ "api" \
"ok" \ "ok" \
@@ -157,7 +173,7 @@ if [[ "${PIWIGO_SYNC_ENABLED:-0}" == "1" ]]; then
"Fallback wurde nicht benötigt" "Fallback wurde nicht benötigt"
elif grep -q 'Piwigo-Datenbanksynchronisierung per Benutzername/Passwort-Fallback erfolgreich' <<<"$PIWIGO_OUTPUT"; then elif grep -q 'Piwigo-Datenbanksynchronisierung per Benutzername/Passwort-Fallback erfolgreich' <<<"$PIWIGO_OUTPUT"; then
write_status ok \ write_status ok \
"WebDAV eingelesen und Piwigo über Fallback synchronisiert; Bildaufbereitung läuft im Hintergrund" \ "WebDAV eingelesen und Piwigo über Fallback synchronisiert; Bildaufbereitung abgeschlossen" \
"" \ "" \
"fallback" \ "fallback" \
"not_used" \ "not_used" \
@@ -165,7 +181,7 @@ if [[ "${PIWIGO_SYNC_ENABLED:-0}" == "1" ]]; then
"ok" \ "ok" \
"Benutzername/Passwort-Fallback erfolgreich" "Benutzername/Passwort-Fallback erfolgreich"
else else
write_status ok "WebDAV eingelesen und Piwigo synchronisiert; Bildaufbereitung läuft im Hintergrund" write_status ok "WebDAV eingelesen und Piwigo synchronisiert; Bildaufbereitung abgeschlossen"
fi fi
else else
write_status ok "WebDAV eingelesen; Piwigo-Synchronisierung ist für diese Verbindung deaktiviert" write_status ok "WebDAV eingelesen; Piwigo-Synchronisierung ist für diese Verbindung deaktiviert"

View File

@@ -93,7 +93,37 @@ function bratonien_tools_resolve_album_rule($category_id, array $categories, arr
$by_id[(int)$category['id']] = $category; $by_id[(int)$category['id']] = $category;
} }
$current = (int)$category_id; $category_id = (int)$category_id;
$root = $by_id[$category_id] ?? null;
$is_private = $root && isset($root['status']) && $root['status'] === 'private';
// Privat ist eine Vererbungsgrenze. Eine direkt auf diesem privaten Album
// gesetzte Regel bleibt moeglich, aber Regeln oeffentlicher Eltern duerfen
// nicht in ein privates Album hineinvererbt werden.
if ($is_private)
{
if (isset($rules[$category_id]))
{
$rule = $rules[$category_id];
if ($rule['mode'] === 'disabled')
{
return array('mode'=>'disabled','profile_id'=>null,'source'=>'album');
}
if ($rule['mode'] === 'profile')
{
return array('mode'=>'profile','profile_id'=>(int)$rule['profile_id'],'source'=>'album');
}
}
$profile_id = $defaults['private_profile'] ?? null;
if (empty($profile_id))
{
return array('mode'=>'disabled','profile_id'=>null,'source'=>'global');
}
return array('mode'=>'profile','profile_id'=>(int)$profile_id,'source'=>'global');
}
$current = $category_id;
$visited = array(); $visited = array();
while ($current > 0 && isset($by_id[$current]) && !isset($visited[$current])) while ($current > 0 && isset($by_id[$current]) && !isset($visited[$current]))
@@ -116,10 +146,7 @@ function bratonien_tools_resolve_album_rule($category_id, array $categories, arr
$current = (int)($by_id[$current]['id_uppercat'] ?? 0); $current = (int)($by_id[$current]['id_uppercat'] ?? 0);
} }
$root = $by_id[(int)$category_id] ?? null; $profile_id = $defaults['public_profile'] ?? null;
$is_private = $root && isset($root['status']) && $root['status'] === 'private';
$profile_id = $is_private ? ($defaults['private_profile'] ?? null) : ($defaults['public_profile'] ?? null);
if (empty($profile_id)) if (empty($profile_id))
{ {
return array('mode'=>'disabled','profile_id'=>null,'source'=>'global'); return array('mode'=>'disabled','profile_id'=>null,'source'=>'global');

View File

@@ -8,6 +8,7 @@ if (!defined('BRATONIEN_TOOLS_PATH'))
define('BRATONIEN_TOOLS_PATH', PHPWG_ROOT_PATH.'plugins/'.BRATONIEN_TOOLS_ID.'/'); define('BRATONIEN_TOOLS_PATH', PHPWG_ROOT_PATH.'plugins/'.BRATONIEN_TOOLS_ID.'/');
} }
require_once(BRATONIEN_TOOLS_PATH.'include/webdav_image_runtime.inc.php'); require_once(BRATONIEN_TOOLS_PATH.'include/webdav_image_runtime.inc.php');
require_once(BRATONIEN_TOOLS_PATH.'include/nc_transport.inc.php');
function bratonien_tools_webdav_image_abort($status, $message) function bratonien_tools_webdav_image_abort($status, $message)
{ {
@@ -120,7 +121,7 @@ $options = array(
CURLOPT_USERPWD => $user.':'.$password, CURLOPT_USERPWD => $user.':'.$password,
CURLOPT_RETURNTRANSFER => false, CURLOPT_RETURNTRANSFER => false,
CURLOPT_FAILONERROR => false, CURLOPT_FAILONERROR => false,
CURLOPT_USERAGENT => 'Bratonien-Tools-WebDAV-Image/0.9.6.1', CURLOPT_USERAGENT => 'Bratonien-Tools-WebDAV-Image/0.9.7.1',
CURLOPT_HEADERFUNCTION => function($ch, $line) CURLOPT_HEADERFUNCTION => function($ch, $line)
{ {
$length = strlen($line); $length = strlen($line);
@@ -147,6 +148,7 @@ $options = array(
return strlen($data); return strlen($data);
}, },
); );
bratonien_tools_nc_transport_apply_curl($options, $url);
if (!empty($_SERVER['HTTP_RANGE'])) if (!empty($_SERVER['HTTP_RANGE']))
{ {
$range = trim((string)$_SERVER['HTTP_RANGE']); $range = trim((string)$_SERVER['HTTP_RANGE']);