From 9cb20693e05cae0d64a8b94bbac2ab56fa4ed15c Mon Sep 17 00:00:00 2001 From: imSp4rky Date: Sun, 21 Jun 2026 13:31:08 -0600 Subject: [PATCH] refactor(api): use Event.wait and a shared to_enum helper Replace InputBridge's poll loop with Event.wait(timeout) and collapse the three download_manager enum maps into one to_enum() helper. No behaviour change. --- unshackle/core/api/download_manager.py | 20 +++++++++++--------- unshackle/core/api/input_bridge.py | 12 +++--------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/unshackle/core/api/download_manager.py b/unshackle/core/api/download_manager.py index 3080d58e..183c3203 100644 --- a/unshackle/core/api/download_manager.py +++ b/unshackle/core/api/download_manager.py @@ -155,6 +155,13 @@ class DownloadJob: return result +def to_enum(values: List[str], enum_cls: type[Enum]) -> List[Enum]: + """Map case-insensitive strings (by member name or value) to enum members, dropping unknowns.""" + lookup = {m.name.upper(): m for m in enum_cls} + lookup.update({str(m.value).upper(): m for m in enum_cls}) + return [lookup[v.upper()] for v in values if v.upper() in lookup] + + def _perform_download( job_id: str, service: str, @@ -197,9 +204,7 @@ def _perform_download( if isinstance(vcodec_raw, str): vcodec_raw = [vcodec_raw] if isinstance(vcodec_raw, list) and vcodec_raw and not isinstance(vcodec_raw[0], Video.Codec): - codec_map = {c.name.upper(): c for c in Video.Codec} - codec_map.update({c.value.upper(): c for c in Video.Codec}) - params["vcodec"] = [codec_map[v.upper()] for v in vcodec_raw if v.upper() in codec_map] + params["vcodec"] = to_enum(vcodec_raw, Video.Codec) else: params["vcodec"] = [] @@ -208,17 +213,14 @@ def _perform_download( if isinstance(range_raw, str): range_raw = [range_raw] if isinstance(range_raw, list) and range_raw and not isinstance(range_raw[0], Video.Range): - range_map = {r.name.upper(): r for r in Video.Range} - range_map.update({r.value.upper(): r for r in Video.Range}) - params["range"] = [range_map[r.upper()] for r in range_raw if r.upper() in range_map] + params["range"] = to_enum(range_raw, Video.Range) else: params["range"] = [Video.Range.SDR] sub_format_raw = params.get("sub_format") if sub_format_raw and isinstance(sub_format_raw, str): - sub_map = {c.name.upper(): c for c in Subtitle.Codec} - sub_map.update({c.value.upper(): c for c in Subtitle.Codec}) - params["sub_format"] = sub_map.get(sub_format_raw.upper()) + mapped = to_enum([sub_format_raw], Subtitle.Codec) + params["sub_format"] = mapped[0] if mapped else None if params.get("export"): params["export"] = bool(params["export"]) diff --git a/unshackle/core/api/input_bridge.py b/unshackle/core/api/input_bridge.py index 06140cd3..8aa398a8 100644 --- a/unshackle/core/api/input_bridge.py +++ b/unshackle/core/api/input_bridge.py @@ -10,7 +10,6 @@ response value. from __future__ import annotations import threading -import time from dataclasses import dataclass, field from enum import Enum from typing import Optional @@ -67,14 +66,9 @@ class InputBridge: self._status = AuthStatus.PENDING_INPUT self._response_ready.clear() - deadline = time.monotonic() + timeout - while time.monotonic() < deadline: - if self._response_ready.wait(timeout=0.5): - break - with self._lock: - if self._cancelled: - raise RuntimeError("Session was cancelled") - else: + # cancel() and submit_response() both .set() the event, so a single wait + # returns on signal-or-cancel; no poll loop needed. + if not self._response_ready.wait(timeout=timeout): with self._lock: self._status = AuthStatus.FAILED raise TimeoutError(f"No client response for prompt within {timeout}s")