diff --git a/job_status.php b/job_status.php index fcc46c8..1b8cba4 100644 --- a/job_status.php +++ b/job_status.php @@ -4,8 +4,73 @@ mvlog_require_login(); $config = require __DIR__ . '/config.php'; header('Content-Type: application/json; charset=UTF-8'); header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); + +function mvlog_job_status_log(string $message): void { + $log = __DIR__ . '/logs/job_status_cleanup.log'; + $line = '[' . date('c') . '] ' . $message . "\n"; + @file_put_contents($log, $line, FILE_APPEND | LOCK_EX); + @chmod($log, 0664); +} + +function mvlog_worker_running(): bool { + $out = []; + $rc = 1; + @exec('systemctl is-active --quiet mvlog-worker.service 2>/dev/null', $out, $rc); + if ($rc === 0) return true; + + $ps = @shell_exec('ps -eo args= 2>/dev/null'); + if (!is_string($ps) || $ps === '') return false; + foreach (explode("\n", $ps) as $line) { + if (strpos($line, 'mvlog_worker.py') !== false && strpos($line, '--remote-root /var/www/html') !== false) { + return true; + } + } + return false; +} + +function mvlog_rm_rf(string $path): void { + if (!is_dir($path)) return; + $items = scandir($path); + if (!is_array($items)) return; + foreach ($items as $item) { + if ($item === '.' || $item === '..') continue; + $child = $path . '/' . $item; + if (is_dir($child) && !is_link($child)) mvlog_rm_rf($child); + else @unlink($child); + } + @rmdir($path); +} + +function mvlog_cleanup_stale_processing(string $dir, string $name, array $state, string $lockDir, bool $locked, string $status, string $articleId): array { + $stateFile = $dir . '/.movmaker-state.json'; + $started = ''; + if (is_file($lockDir . '/started_at')) $started = trim((string)@file_get_contents($lockDir . '/started_at')); + if ($started === '') $started = (string)($state['started_at'] ?? $state['updated_at'] ?? ''); + + $backup = ''; + if (is_file($stateFile)) { + $backup = $stateFile . '.bak.' . date('YmdHis'); + @copy($stateFile, $backup); + @chmod($backup, 0664); + } + + $state['status'] = 'error'; + $state['updated_at'] = gmdate('c'); + if ($articleId !== '') $state['article_id'] = $articleId; + $state['message'] = 'Previous render appears abandoned: no mvlog-worker process is running; stale lock/processing state was cleaned by job_status.php. Re-enable Render to retry.'; + $json = json_encode($state, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n"; + @file_put_contents($stateFile, $json, LOCK_EX); + @chmod($stateFile, 0664); + if ($locked) mvlog_rm_rf($lockDir); + + mvlog_job_status_log('cleaned stale processing job=' . $name . ' article_id=' . ($articleId ?: '-') . ' started_at=' . ($started ?: '-') . ' status=' . ($status ?: '-') . ' lock=' . ($locked ? 'yes' : 'no') . ' backup=' . ($backup ?: '-')); + return $state; +} + $jobs = []; $switches = []; +$cleaned = []; +$workerRunning = mvlog_worker_running(); $dirs = glob($config['uploads_dir'] . '/*', GLOB_ONLYDIR) ?: []; foreach ($dirs as $dir) { $name = basename($dir); @@ -13,21 +78,31 @@ foreach ($dirs as $dir) { $lockDir = $dir . '/.movmaker-lock'; $idFile = $dir . '/.mvlog-id'; $state = is_file($stateFile) ? json_decode((string)file_get_contents($stateFile), true) : []; - $status = is_array($state) ? (string)($state['status'] ?? '') : ''; - $articleId = is_array($state) ? strtolower((string)($state['article_id'] ?? '')) : ''; + if (!is_array($state)) $state = []; + $status = (string)($state['status'] ?? ''); + $articleId = strtolower((string)($state['article_id'] ?? '')); if ($articleId === '' && is_file($idFile)) $articleId = strtolower(trim((string)file_get_contents($idFile))); if (!preg_match('/^[0-9]{14}[a-f0-9]{16}$/', $articleId)) $articleId = ''; + $locked = is_dir($lockDir); + + if (!$workerRunning && ($locked || $status === 'processing')) { + $state = mvlog_cleanup_stale_processing($dir, $name, $state, $lockDir, $locked, $status, $articleId); + $status = (string)($state['status'] ?? ''); + $locked = is_dir($lockDir); + $cleaned[] = ['dir' => $name, 'id' => $articleId, 'status' => $status]; + } + $switches[] = [ 'dir' => $name, 'id' => $articleId, 'enabled' => is_file($dir . '/.movmaker-enabled'), 'preview' => is_file($dir . '/.movmaker-preview'), ]; - $locked = is_dir($lockDir); + if ($locked || $status === 'processing') { $started = ''; if (is_file($lockDir . '/started_at')) $started = trim((string)file_get_contents($lockDir . '/started_at')); - if ($started === '' && is_array($state)) $started = (string)($state['started_at'] ?? $state['updated_at'] ?? ''); + if ($started === '') $started = (string)($state['started_at'] ?? $state['updated_at'] ?? ''); $jobs[] = [ 'name' => $name, 'id' => $articleId, @@ -36,4 +111,4 @@ foreach ($dirs as $dir) { ]; } } -echo json_encode(['jobs' => $jobs, 'switches' => $switches, 'checked_at' => date('c')], JSON_UNESCAPED_UNICODE); +echo json_encode(['jobs' => $jobs, 'switches' => $switches, 'cleaned' => $cleaned, 'worker_running' => $workerRunning, 'checked_at' => date('c')], JSON_UNESCAPED_UNICODE);