show=1|0 // Configure $MVLOG_ROOT and $PUSH_CONFIG below. // Configuration - update to match your server $MVLOG_ROOT = '/var/www/html/mvlog'; $PUSH_CONFIG_CANDIDATES = [ "$MVLOG_ROOT/push.json", getenv('HOME') !== false ? getenv('HOME') . '/.config/mvlog/push.json' : null, '/etc/mvlog/push.json', ]; $LOGFILE = '/var/log/mvlog_notify.log'; $job = $_POST['job'] ?? ''; $show = isset($_POST['show']) ? filter_var($_POST['show'], FILTER_VALIDATE_BOOLEAN) : true; // Basic validation for job name; adjust to your naming policy if needed if (!preg_match('/^[0-9]{8}[-_][A-Za-z0-9._-]+$/', $job)) { http_response_code(400); echo 'invalid job'; exit; } $jobdir = "$MVLOG_ROOT/in-dir/$job"; if (!is_dir($jobdir)) { http_response_code(404); echo 'job not found'; exit; } $enabled_file = "$jobdir/.movmaker-enabled"; $hidden_file = "$jobdir/.mvlog-hidden"; $subs_file = "$MVLOG_ROOT/cache/push_subscriptions.json"; $cache_file = "$MVLOG_ROOT/cache/push_notifications.json"; // Find push config $push_config = null; foreach ($PUSH_CONFIG_CANDIDATES as $candidate) { if (!$candidate) continue; if (file_exists($candidate) && is_readable($candidate)) { $push_config = $candidate; break; } } if ($show) { // Create marker file atomically file_put_contents($enabled_file, date(DATE_ATOM) . PHP_EOL, LOCK_EX); // Try to send push (non-fatal) $sent = try_send_show_notification($job, $jobdir, $MVLOG_ROOT, $subs_file, $cache_file, $push_config, $LOGFILE); if ($sent) { echo 'enabled'; } else { echo 'enabled (no notification)'; } } else { // Remove marker and do not touch notifications @unlink($enabled_file); echo 'disabled'; } exit; function try_send_show_notification($job, $jobdir, $root, $subs_file, $cache_file, $push_config, $logfile) { // Skip if hidden if (file_exists($jobdir . '/.mvlog-hidden')) { error_log("[mvlog] job $job hidden, skipping push\n", 3, $logfile); return false; } // Ensure we have subscriptions if (!file_exists($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 (!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 (file_exists($cache_file)) { $cache = json_decode(@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; } // Ensure push config present if (!$push_config) { error_log("[mvlog] push config not found, skipping push for $job\n", 3, $logfile); return false; } // Determine title (prefer output filename from state file) $title = preg_replace('/_+/', ' ', $job); $title = ucwords($title); $state_file = $jobdir . '/.movmaker-state.json'; if (file_exists($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); } } } $article_id_file = $jobdir . '/.mvlog-id'; $article_id = file_exists($article_id_file) ? strtolower(trim((string)@file_get_contents($article_id_file))) : ''; if (!preg_match('/^[0-9]{14}[a-f0-9]{16}$/', $article_id)) { error_log("[mvlog] article id missing, skipping push for $job\n", 3, $logfile); return false; } $payload = json_encode([ 'title' => 'Show enabled', 'body' => $title, 'url' => './?id=' . rawurlencode($article_id), 'tag' => "mvlog-show-$article_id", ], JSON_UNESCAPED_UNICODE); // Call node send_push.js with subscriptions on stdin $send_script = escapeshellarg($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; }