movmaker-webui/lib/send_push.php
2026-05-29 13:29:12 +02:00

142 lines
5.2 KiB
PHP

<?php
/**
* send_push.php
* Helper function to send "Show enabled" web-push notifications from the webserver.
*
* Function: try_send_show_notification(string $job, string $jobdir, string $mvlog_root = null, string $logfile = null): bool
*
* Returns true on successful send and cache update, false otherwise.
*/
function try_send_show_notification($job, $jobdir, $mvlog_root = null, $logfile = null)
{
if ($mvlog_root === null) $mvlog_root = dirname(__DIR__);
if ($logfile === null) $logfile = '/var/log/mvlog_notify.log';
$subs_file = $mvlog_root . '/cache/push_subscriptions.json';
$cache_file = $mvlog_root . '/cache/push_notifications.json';
$push_config_candidates = [
$mvlog_root . '/push.json',
(getenv('HOME') !== false ? getenv('HOME') . '/.config/mvlog/push.json' : null),
'/etc/mvlog/push.json',
];
// Don't notify for hidden jobs
if (is_file($jobdir . '/.mvlog-hidden')) {
error_log("[mvlog] job $job hidden, skipping push\n", 3, $logfile);
return false;
}
// Gather subscriptions
if (!is_file($subs_file)) {
error_log("[mvlog] subscriptions file missing, skipping push for $job\n", 3, $logfile);
return false;
}
$subs_raw = @file_get_contents($subs_file);
if (!is_string($subs_raw) || trim($subs_raw) === '') {
error_log("[mvlog] subscriptions empty, skipping push for $job\n", 3, $logfile);
return false;
}
$subs = json_decode($subs_raw, true);
if (!is_array($subs) || count($subs) === 0) {
error_log("[mvlog] subscriptions invalid/empty, skipping push for $job\n", 3, $logfile);
return false;
}
// Read cache and check if already shown
$cache = [];
if (is_file($cache_file)) {
$cache = json_decode((string)@file_get_contents($cache_file), true) ?: [];
}
$shown = isset($cache['shown']) && is_array($cache['shown']) ? $cache['shown'] : [];
if (isset($shown[$job])) {
error_log("[mvlog] push already sent for $job, skipping\n", 3, $logfile);
return false;
}
// Determine push config file
$push_config = null;
foreach ($push_config_candidates as $candidate) {
if (!$candidate) continue;
if (is_file($candidate) && is_readable($candidate)) { $push_config = $candidate; break; }
}
if (!$push_config) {
error_log("[mvlog] push config not found, skipping push for $job\n", 3, $logfile);
return false;
}
// Determine a title/url for the notification. Prefer existing output filename if present.
$title = preg_replace('/_+/', ' ', $job);
$title = ucwords($title);
$state_file = $jobdir . '/.movmaker-state.json';
if (is_file($state_file)) {
$state = json_decode(@file_get_contents($state_file), true) ?: [];
if (!empty($state['output']) && is_string($state['output'])) {
$out = pathinfo($state['output'], PATHINFO_FILENAME);
if ($out) {
$title = preg_replace('/_+/', ' ', $out);
$title = ucwords($title);
}
}
}
$payload = json_encode([
'title' => 'Show enabled',
'body' => $title,
'url' => "https://bubulescu.org/mvlog/?job={$job}",
'tag' => "mvlog-show-{$job}",
], JSON_UNESCAPED_UNICODE);
// Call node send_push.js with subscriptions on stdin
$send_script = escapeshellarg($mvlog_root . '/send_push.js');
$push_config_esc = escapeshellarg($push_config);
$payload_esc = escapeshellarg($payload);
$cmd = "node $send_script $push_config_esc $payload_esc";
$descriptorspec = [
0 => ['pipe', 'r'], // stdin
1 => ['pipe', 'w'], // stdout
2 => ['pipe', 'w'], // stderr
];
$proc = proc_open($cmd, $descriptorspec, $pipes);
if (!is_resource($proc)) {
error_log("[mvlog] failed to start node send_push process for $job\n", 3, $logfile);
return false;
}
// Write subscriptions to stdin
fwrite($pipes[0], $subs_raw);
fclose($pipes[0]);
$stdout = stream_get_contents($pipes[1]); fclose($pipes[1]);
$stderr = stream_get_contents($pipes[2]); fclose($pipes[2]);
$rc = proc_close($proc);
if ($rc !== 0) {
error_log("[mvlog] send_push failed for $job rc=$rc stdout=" . trim($stdout) . " stderr=" . trim($stderr) . "\n", 3, $logfile);
return false;
}
// On success, record in cache['shown']
if (!is_array($cache)) $cache = [];
if (!isset($cache['shown']) || !is_array($cache['shown'])) $cache['shown'] = [];
$cache['shown'][$job] = ['sent_at' => date(DATE_ATOM)];
// Atomic write: write to tmp then rename
$tmp = $cache_file . '.tmp.' . bin2hex(random_bytes(6));
$json = json_encode($cache, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
if (@file_put_contents($tmp, $json, LOCK_EX) === false) {
error_log("[mvlog] failed to write cache temp file for $job\n", 3, $logfile);
return false;
}
@chmod($tmp, 0664);
if (!@rename($tmp, $cache_file)) {
@unlink($tmp);
error_log("[mvlog] failed to atomically write cache file for $job\n", 3, $logfile);
return false;
}
error_log("[mvlog] show-notification sent: $job\n", 3, $logfile);
return true;
}