From 15059f5b683b6e5a4c62c2ccd3b56c63bff5d54c Mon Sep 17 00:00:00 2001 From: Terranom674 Date: Sat, 5 Sep 2026 23:28:34 +0200 Subject: [PATCH] Add asynchronous Piwigo preview presentation refresh worker --- runtime/piwigo-presentation-refresh.php | 324 ++++++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 runtime/piwigo-presentation-refresh.php diff --git a/runtime/piwigo-presentation-refresh.php b/runtime/piwigo-presentation-refresh.php new file mode 100644 index 0000000..43ea2a3 --- /dev/null +++ b/runtime/piwigo-presentation-refresh.php @@ -0,0 +1,324 @@ +$value) + { + if (is_array($value) || is_object($value)) $value = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + $parts[] = $key.'='.str_replace(array("\r", "\n"), array('\\r', '\\n'), (string)$value); + } + fwrite(STDOUT, implode(' ', $parts)."\n"); +} + +function bratonien_tools_presentation_worker_categories(array $requested, $all) +{ + $requested = array_fill_keys(array_map('intval', $requested), true); + $categories = array(); + $result = pwg_query('SELECT id, uppercats FROM '.CATEGORIES_TABLE.' ORDER BY id'); + while ($row = pwg_db_fetch_assoc($result)) + { + $id = (int)$row['id']; + if ($id < 1) continue; + if ($all) + { + $categories[$id] = $id; + continue; + } + + $uppercats = array_filter(array_map('intval', explode(',', (string)$row['uppercats']))); + foreach ($uppercats as $ancestor) + { + if (isset($requested[$ancestor])) + { + $categories[$id] = $id; + break; + } + } + } + ksort($categories, SORT_NUMERIC); + return array_values($categories); +} + +function bratonien_tools_presentation_worker_variants(SrcImage $src) +{ + $variants = array(); + foreach (ImageStdParams::get_defined_type_map() as $type=>$params) + { + $variants['standard:'.$type] = new DerivativeImage($params, $src); + } + foreach (ImageStdParams::$custom as $key=>$last_used) + { + $params = bratonien_tools_webdav_materialize_custom_params($key); + if ($params) $variants['custom:'.$key] = new DerivativeImage($params, $src); + } + return $variants; +} + +function bratonien_tools_presentation_worker_call_watermark($url, &$detail=null) +{ + $detail = ''; + $url = html_entity_decode((string)$url, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + $path = (string)parse_url($url, PHP_URL_PATH); + $query = (string)parse_url($url, PHP_URL_QUERY); + $endpoint = '/plugins/'.trim(BRATONIEN_TOOLS_ID, '/').'/watermark.php'; + + if ($path === '' || substr($path, -strlen($endpoint)) !== $endpoint) + { + if (strpos($path, '/bratonien-watermark/') !== false) + { + $detail = 'Bereits vorhanden.'; + return true; + } + $detail = 'Kein Bratonien-Wasserzeichenaufruf erforderlich.'; + return true; + } + if ($query === '') + { + $detail = 'Wasserzeichen-URL enthält keine Parameter.'; + return false; + } + if (!function_exists('exec')) + { + $detail = 'PHP exec() ist deaktiviert.'; + return false; + } + + $runner = BRATONIEN_TOOLS_PATH.'runtime/lib/bratonien-watermark-call.php'; + if (!is_file($runner)) + { + $detail = 'Lokaler Wasserzeichen-Aufrufer fehlt.'; + return false; + } + + $command = escapeshellarg(PHP_BINARY).' '.escapeshellarg($runner) + .' --query='.escapeshellarg($query).' > /dev/null 2>&1'; + $output = array(); + $exit = 1; + @exec($command, $output, $exit); + if ($exit !== 0) + { + $detail = 'Wasserzeichen-Aufrufer Exit '.$exit.'.'; + return false; + } + + $detail = 'Wasserzeichen-Vorschau bereit.'; + return true; +} + +function bratonien_tools_presentation_worker_process(array $category_ids) +{ + global $page; + + if (!$category_ids) return array('images'=>0, 'profiles'=>0, 'variants'=>0, 'generated'=>0, 'missing_base'=>0, 'errors'=>0); + $category_lookup = array_fill_keys($category_ids, true); + $ids_sql = implode(',', array_map('intval', $category_ids)); + + $image_categories = array(); + $result = pwg_query('SELECT image_id, category_id FROM '.IMAGE_CATEGORY_TABLE.' WHERE category_id IN ('.$ids_sql.') ORDER BY image_id, category_id'); + while ($row = pwg_db_fetch_assoc($result)) + { + $image_id = (int)$row['image_id']; + $category_id = (int)$row['category_id']; + if ($image_id > 0 && isset($category_lookup[$category_id])) $image_categories[$image_id][] = $category_id; + } + if (!$image_categories) return array('images'=>0, 'profiles'=>0, 'variants'=>0, 'generated'=>0, 'missing_base'=>0, 'errors'=>0); + + $stats = array('images'=>0, 'profiles'=>0, 'variants'=>0, 'generated'=>0, 'missing_base'=>0, 'errors'=>0); + $image_ids = array_keys($image_categories); + foreach (array_chunk($image_ids, 500) as $chunk) + { + $rows = pwg_query('SELECT * FROM '.IMAGES_TABLE.' WHERE id IN ('.implode(',', array_map('intval', $chunk)).') ORDER BY id'); + while ($row = pwg_db_fetch_assoc($rows)) + { + $image_id = (int)$row['id']; + $stats['images']++; + $profile_contexts = array(); + + foreach ($image_categories[$image_id] ?? array() as $category_id) + { + $rule = bratonien_tools_runtime_effective_rule($category_id); + if ((string)($rule['mode'] ?? '') !== 'profile' || empty($rule['profile_id'])) continue; + $profile_id = (int)$rule['profile_id']; + if ($profile_id < 1 || isset($profile_contexts[$profile_id])) continue; + $profile = bratonien_tools_runtime_get_profile($profile_id); + if (!$profile || empty($profile['active']) || !bratonien_tools_profile_watermark_path($profile)) continue; + $profile_contexts[$profile_id] = $category_id; + } + + if (!$profile_contexts) continue; + + try + { + $src = new SrcImage($row); + $variants = bratonien_tools_presentation_worker_variants($src); + } + catch (Throwable $e) + { + $stats['errors']++; + bratonien_tools_presentation_worker_log('image_error', array('image_id'=>$image_id, 'message'=>$e->getMessage())); + continue; + } + + foreach ($profile_contexts as $profile_id=>$category_id) + { + $stats['profiles']++; + $page['category'] = array('id'=>(int)$category_id); + + foreach ($variants as $variant_name=>$derivative) + { + try + { + if ($derivative->same_as_source()) continue; + $stats['variants']++; + $target = $derivative->get_path(); + if ($target === '' || !is_file($target) || !is_readable($target)) + { + $stats['missing_base']++; + continue; + } + + $detail = ''; + if (!bratonien_tools_presentation_worker_call_watermark($derivative->get_url(), $detail)) + { + $stats['errors']++; + bratonien_tools_presentation_worker_log('variant_error', array( + 'image_id'=>$image_id, + 'category_id'=>$category_id, + 'profile_id'=>$profile_id, + 'variant'=>$variant_name, + 'message'=>$detail, + )); + continue; + } + $stats['generated']++; + } + catch (Throwable $e) + { + $stats['errors']++; + bratonien_tools_presentation_worker_log('variant_error', array( + 'image_id'=>$image_id, + 'category_id'=>$category_id, + 'profile_id'=>$profile_id, + 'variant'=>$variant_name, + 'message'=>$e->getMessage(), + )); + } + } + } + } + } + + unset($page['category']); + return $stats; +} + +try +{ + $queue_dir = bratonien_tools_presentation_refresh_queue_dir(); + if (!is_dir($queue_dir)) exit(0); + + $lock_path = $queue_dir.'/.worker.lock'; + $lock = @fopen($lock_path, 'c'); + if (!$lock) throw new RuntimeException('Presentation-Worker-Lock konnte nicht geöffnet werden.'); + if (!@flock($lock, LOCK_EX)) throw new RuntimeException('Presentation-Worker-Lock konnte nicht gesetzt werden.'); + + try + { + while (true) + { + $request_files = glob($queue_dir.'/*.json') ?: array(); + sort($request_files, SORT_STRING); + if (!$request_files) break; + + $all = false; + $requested = array(); + $valid_files = array(); + $reasons = array(); + foreach ($request_files as $file) + { + $data = json_decode((string)@file_get_contents($file), true); + if (!is_array($data)) + { + bratonien_tools_presentation_worker_log('invalid_request', array('file'=>basename($file))); + @unlink($file); + continue; + } + $valid_files[] = $file; + if (!empty($data['all'])) $all = true; + foreach ((array)($data['categories'] ?? array()) as $category_id) + { + $category_id = (int)$category_id; + if ($category_id > 0) $requested[$category_id] = $category_id; + } + if (!empty($data['reason'])) $reasons[] = (string)$data['reason']; + } + + if (!$valid_files) continue; + $categories = bratonien_tools_presentation_worker_categories(array_values($requested), $all); + $stats = bratonien_tools_presentation_worker_process($categories); + bratonien_tools_presentation_worker_log('complete', array( + 'all'=>$all, + 'requested'=>count($requested), + 'categories'=>count($categories), + 'reasons'=>array_values(array_unique($reasons)), + 'images'=>$stats['images'], + 'profiles'=>$stats['profiles'], + 'variants'=>$stats['variants'], + 'prepared'=>$stats['generated'], + 'missing_base'=>$stats['missing_base'], + 'errors'=>$stats['errors'], + )); + + foreach ($valid_files as $file) @unlink($file); + } + } + finally + { + @flock($lock, LOCK_UN); + fclose($lock); + } + exit(0); +} +catch (Throwable $e) +{ + fwrite(STDERR, '[BRAT-PRESENTATION] fatal '.$e->getMessage()."\n"); + exit(1); +}