diff --git a/mvlog_worker.py b/mvlog_worker.py index 25340d2..e318da2 100755 --- a/mvlog_worker.py +++ b/mvlog_worker.py @@ -12,6 +12,7 @@ import logging import hashlib import json import os +import re import shutil import subprocess import sys @@ -28,6 +29,7 @@ LOCK_NAME = ".movmaker-lock" ENABLED_NAME = ".movmaker-enabled" PREVIEW_NAME = ".movmaker-preview" HIDDEN_NAME = ".mvlog-hidden" +ARTICLE_ID_NAME = ".mvlog-id" INTRO_LOGO_ALPHA = "0.5" MVLOG_WORKER_RECOMMENDED_INTERVAL_MINUTES = 5 @@ -60,6 +62,23 @@ def remote_file_exists(host: str | None, path: str) -> bool: return ssh(host, f"test -f {remote_q(path)}", check=False).returncode == 0 +def read_remote_text(host: str | None, path: str) -> str: + if host is None: + try: + return Path(path).read_text(encoding="utf-8") + except Exception: + return "" + res = ssh(host, f"cat {remote_q(path)} 2>/dev/null", check=False) + return res.stdout if res.returncode == 0 else "" + + +def read_article_id(host: str | None, job_dir: str) -> str | None: + raw = read_remote_text(host, f"{job_dir}/{ARTICLE_ID_NAME}").strip().lower() + if re.fullmatch(r"\d{14}[a-f0-9]{16}", raw): + return raw + return None + + def acquire_lock(host: str, job_dir: str) -> bool: lock = f"{job_dir}/{LOCK_NAME}" cmd = f"mkdir {remote_q(lock)} 2>/dev/null && date -Iseconds > {remote_q(lock + '/started_at')}" @@ -78,7 +97,7 @@ entries = [] for dirpath, dirnames, filenames in os.walk(root): dirnames[:] = [d for d in dirnames if d != '.movmaker-lock'] for name in filenames: - if name in {'.movmaker-state.json', '.movmaker-enabled', '.movmaker-preview', '.mvlog-hidden'}: + if name in {'.movmaker-state.json', '.movmaker-enabled', '.movmaker-preview', '.mvlog-hidden', '.mvlog-id'}: continue path = os.path.join(dirpath, name) rel = os.path.relpath(path, root) @@ -253,6 +272,7 @@ def render_job(local_input: Path, local_out: Path, extra_args: list[str]) -> Pat def process_job(host: str, remote_root: str, job: str, work_dir: Path, extra_args: list[str], force: bool) -> None: job_dir = f"{remote_root}/in-dir/{job}" + article_id = read_article_id(host, job_dir) state = read_state(host, job_dir) full_requested = remote_file_exists(host, f"{job_dir}/{ENABLED_NAME}") preview_requested = remote_file_exists(host, f"{job_dir}/{PREVIEW_NAME}") @@ -284,6 +304,8 @@ def process_job(host: str, remote_root: str, job: str, work_dir: Path, extra_arg "mode": "preview" if preview_mode else "full", "started_at": now(), }) + if article_id: + processing_state["article_id"] = article_id if preview_mode: processing_state["preview_fingerprint"] = fingerprint_before else: @@ -313,6 +335,8 @@ def process_job(host: str, remote_root: str, job: str, work_dir: Path, extra_arg "updated_at": now(), "message": "Input changed during render; will regenerate on next run.", }) + if article_id: + stale_state["article_id"] = article_id if preview_mode: stale_state["preview_fingerprint"] = fingerprint_after stale_state["previous_preview_fingerprint"] = fingerprint_before @@ -341,6 +365,8 @@ def process_job(host: str, remote_root: str, job: str, work_dir: Path, extra_arg "preview_output": preview_output, "preview_generated_at": now(), }) + if article_id: + new_state["article_id"] = article_id write_state(host, job_dir, new_state) print(f"published preview: {job} -> {preview_output}") @@ -365,6 +391,8 @@ def process_job(host: str, remote_root: str, job: str, work_dir: Path, extra_arg "output": output, "generated_at": now(), }) + if article_id: + new_state["article_id"] = article_id write_state(host, job_dir, new_state) print(f"published: {job} -> {output}") @@ -376,11 +404,14 @@ def process_job(host: str, remote_root: str, job: str, work_dir: Path, extra_arg return except Exception as exc: try: - write_state(host, job_dir, { + error_state = { "status": "error", "updated_at": now(), "message": str(exc), - }) + } + if article_id: + error_state["article_id"] = article_id + write_state(host, job_dir, error_state) finally: raise finally: