'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; }