fd
Some checks failed
smoke-mm / Install & smoke test mm --help (push) Has been cancelled

This commit is contained in:
2025-12-27 03:13:16 -08:00
parent a595453a9b
commit 71b542ae91
8 changed files with 1069 additions and 57 deletions

View File

@@ -40,6 +40,21 @@ def _repo_root() -> Path:
return Path(__file__).resolve().parent.parent
def _runtime_config_root() -> Path:
"""Best-effort config root for runtime execution.
MPV can spawn this helper from an installed location while setting `cwd` to
the repo root (see MPV.mpv_ipc). Prefer `cwd` when it contains `config.conf`.
"""
try:
cwd = Path.cwd().resolve()
if (cwd / "config.conf").exists():
return cwd
except Exception:
pass
return _repo_root()
# Make repo-local packages importable even when mpv starts us from another cwd.
_ROOT = str(_repo_root())
if _ROOT not in sys.path:
@@ -223,19 +238,57 @@ def _run_op(op: str, data: Any) -> Dict[str, Any]:
# Provide store backend choices using the same source as CLI/Typer autocomplete.
if op_name in {"store-choices", "store_choices", "get-store-choices", "get_store_choices"}:
from CLI import MedeiaCLI # noqa: WPS433
# IMPORTANT:
# - Prefer runtime cwd for config discovery (mpv spawns us with cwd=repo_root).
# - Avoid returning a cached empty result if config was loaded before it existed.
try:
from config import reload_config # noqa: WPS433
from Store import Store # noqa: WPS433
backends = MedeiaCLI.get_store_choices()
choices = sorted({str(n) for n in (backends or []) if str(n).strip()})
config_root = _runtime_config_root()
cfg = reload_config(config_dir=config_root)
return {
"success": True,
"stdout": "",
"stderr": "",
"error": None,
"table": None,
"choices": choices,
}
storage = Store(config=cfg, suppress_debug=True)
backends = storage.list_backends() or []
choices = sorted({str(n) for n in backends if str(n).strip()})
# Fallback: if initialization gated all backends (e.g., missing deps or offline stores),
# still return configured instance names so the UI can present something.
if not choices:
store_cfg = cfg.get("store") if isinstance(cfg, dict) else None
if isinstance(store_cfg, dict):
seen = set()
for _, instances in store_cfg.items():
if not isinstance(instances, dict):
continue
for instance_key, instance_cfg in instances.items():
name = None
if isinstance(instance_cfg, dict):
name = instance_cfg.get("NAME") or instance_cfg.get("name")
candidate = (str(name or instance_key or "").strip())
if candidate:
seen.add(candidate)
choices = sorted(seen)
debug(f"[store-choices] config_dir={config_root} choices={len(choices)}")
return {
"success": True,
"stdout": "",
"stderr": "",
"error": None,
"table": None,
"choices": choices,
}
except Exception as exc:
return {
"success": False,
"stdout": "",
"stderr": "",
"error": f"store-choices failed: {type(exc).__name__}: {exc}",
"table": None,
"choices": [],
}
# Provide yt-dlp format list for a URL (for MPV "Change format" menu).
# Returns a ResultTable-like payload so the Lua UI can render without running cmdlets.
@@ -580,6 +633,17 @@ def main(argv: Optional[list[str]] = None) -> int:
try:
_append_helper_log(f"[helper] version={MEDEIA_MPV_HELPER_VERSION} started ipc={args.ipc}")
try:
_append_helper_log(f"[helper] file={Path(__file__).resolve()} cwd={Path.cwd().resolve()}")
except Exception:
pass
try:
runtime_root = _runtime_config_root()
_append_helper_log(
f"[helper] config_root={runtime_root} exists={bool((runtime_root / 'config.conf').exists())}"
)
except Exception:
pass
debug(f"[mpv-helper] logging to: {_helper_log_path()}")
except Exception:
pass
@@ -679,13 +743,11 @@ def main(argv: Optional[list[str]] = None) -> int:
if (now - last_ready_ts) < 0.75:
return
try:
client.send_command_no_wait(["set_property", READY_PROP, str(int(now))])
client.send_command_no_wait(["set_property_string", READY_PROP, str(int(now))])
last_ready_ts = now
except Exception:
return
_touch_ready()
# Mirror mpv's own log messages into our helper log file so debugging does
# not depend on the mpv on-screen console or mpv's log-file.
try:
@@ -715,6 +777,46 @@ def main(argv: Optional[list[str]] = None) -> int:
except Exception:
return 3
# Mark ready only after the observer is installed to avoid races where Lua
# sends a request before we can receive property-change notifications.
try:
_touch_ready()
_append_helper_log(f"[helper] ready heartbeat armed prop={READY_PROP}")
except Exception:
pass
# Pre-compute store choices at startup and publish to a cached property so Lua
# can read immediately without waiting for a request/response cycle (which may timeout).
try:
startup_choices_payload = _run_op("store-choices", None)
startup_choices = startup_choices_payload.get("choices") if isinstance(startup_choices_payload, dict) else None
if isinstance(startup_choices, list):
preview = ", ".join(str(x) for x in startup_choices[:50])
_append_helper_log(f"[helper] startup store-choices count={len(startup_choices)} items={preview}")
# Publish to a cached property for Lua to read without IPC request.
try:
cached_json = json.dumps({"success": True, "choices": startup_choices}, ensure_ascii=False)
client.send_command_no_wait(["set_property_string", "user-data/medeia-store-choices-cached", cached_json])
_append_helper_log(f"[helper] published store-choices to user-data/medeia-store-choices-cached")
except Exception as exc:
_append_helper_log(f"[helper] failed to publish store-choices: {type(exc).__name__}: {exc}")
else:
_append_helper_log("[helper] startup store-choices unavailable")
except Exception as exc:
_append_helper_log(f"[helper] startup store-choices failed: {type(exc).__name__}: {exc}")
# Also publish config temp directory if available
try:
from config import load_config
cfg = load_config()
temp_dir = cfg.get("temp", "").strip() or os.getenv("TEMP") or "/tmp"
if temp_dir:
client.send_command_no_wait(["set_property_string", "user-data/medeia-config-temp", temp_dir])
_append_helper_log(f"[helper] published config temp to user-data/medeia-config-temp={temp_dir}")
except Exception as exc:
_append_helper_log(f"[helper] failed to publish config temp: {type(exc).__name__}: {exc}")
last_seen_id: Optional[str] = None
try:
@@ -864,7 +966,7 @@ def main(argv: Optional[list[str]] = None) -> int:
try:
# IMPORTANT: don't wait for a response here; waiting would consume
# async events and can drop/skip property-change notifications.
client.send_command_no_wait(["set_property", RESPONSE_PROP, json.dumps(resp, ensure_ascii=False)])
client.send_command_no_wait(["set_property_string", RESPONSE_PROP, json.dumps(resp, ensure_ascii=False)])
except Exception:
# If posting results fails, there's nothing more useful to do.
pass