62 lines
1.9 KiB
Python
62 lines
1.9 KiB
Python
# Copyright 2026 Umbra Tools (umbratools.com)
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
"""Web-UI maintenance banner state for long-running offline phases.
|
|
|
|
During the LLM speaker re-attribution run the Mac's Qwen3-TTS models must
|
|
stay unloaded (16 GB RAM), so voice re-generation is temporarily offline.
|
|
The run script records that state here, the web routes surface it through
|
|
a template global, and this module's pid-liveness check makes a crashed
|
|
run self-heal the banner away instead of lingering until a human notices.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import contextlib
|
|
import json
|
|
import os
|
|
import time
|
|
|
|
from umbraaudio.config import PROJECT_BASE
|
|
|
|
MAINTENANCE_FILE = PROJECT_BASE / ".umbra" / "voice_maintenance.json"
|
|
|
|
|
|
def set_voice_maintenance(reason: str) -> None:
|
|
"""Record that voice re-generation is temporarily unavailable."""
|
|
payload = {
|
|
"pid": os.getpid(),
|
|
"started_at": int(time.time()),
|
|
"reason": reason,
|
|
}
|
|
MAINTENANCE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
tmp = MAINTENANCE_FILE.with_suffix(".json.tmp")
|
|
tmp.write_text(json.dumps(payload, indent=2))
|
|
tmp.replace(MAINTENANCE_FILE)
|
|
|
|
|
|
def clear_voice_maintenance() -> None:
|
|
"""Remove the maintenance marker so the banner disappears."""
|
|
with contextlib.suppress(FileNotFoundError):
|
|
MAINTENANCE_FILE.unlink()
|
|
|
|
|
|
def active_voice_maintenance() -> dict | None:
|
|
"""Return the marker dict while a live process owns it, else None.
|
|
|
|
The pid check means a run that died without clearing the file stops
|
|
showing the banner as soon as its process is gone.
|
|
"""
|
|
try:
|
|
with open(MAINTENANCE_FILE) as f:
|
|
data = json.load(f)
|
|
except (FileNotFoundError, json.JSONDecodeError, OSError):
|
|
return None
|
|
pid = data.get("pid")
|
|
if not isinstance(pid, int) or pid <= 0:
|
|
return None
|
|
try:
|
|
os.kill(pid, 0)
|
|
except (ProcessLookupError, PermissionError):
|
|
return None
|
|
return data
|