This commit is contained in:
2026-02-02 02:32:28 -08:00
parent f0a82c2403
commit 4f7bac464f
6 changed files with 128 additions and 62 deletions

1
.gitignore vendored
View File

@@ -247,3 +247,4 @@ medios*
mypy.ini
\logs\*
logs
logs.db

View File

@@ -436,9 +436,9 @@ local function get_mpv_ipc_path()
-- Fallback: fixed pipe/socket name used by MPV/mpv_ipc.py
local sep = package and package.config and package.config:sub(1, 1) or '/'
if sep == '\\' then
return '\\\\.\\pipe\\mpv-medeia-macina'
return '\\\\.\\pipe\\mpv-medios-macina'
end
return '/tmp/mpv-medeia-macina.sock'
return '/tmp/mpv-medios-macina.sock'
end
local function ensure_mpv_ipc_server()

View File

@@ -21,7 +21,7 @@ from typing import Any, Dict, Optional, List, BinaryIO, Tuple, cast
from SYS.logger import debug
# Fixed pipe name for persistent MPV connection across all Python sessions
FIXED_IPC_PIPE_NAME = "mpv-medeia-macina"
FIXED_IPC_PIPE_NAME = "mpv-medios-macina"
MPV_LUA_SCRIPT_PATH = str(Path(__file__).resolve().parent / "LUA" / "main.lua")
_LYRIC_PROCESS: Optional[subprocess.Popen] = None

View File

@@ -653,6 +653,28 @@ def set_last_result_table(
logger.exception("Failed to sort overlay result_table and reorder items")
def set_last_result_table_overlay(
result_table: Optional[Any],
items: Optional[List[Any]] = None,
subject: Optional[Any] = None
) -> None:
"""Store a display table and items WITHOUT affecting history stack.
Used by action cmdlets (get-metadata, get-tag, get-url) to display detail
panels or filtered results without disrupting the primary search-result history.
"""
state = _get_pipeline_state()
state.display_table = result_table
state.display_items = items or []
state.display_subject = subject
def set_last_result_table_preserve_history(
result_table: Optional[Any],
items: Optional[List[Any]] = None,
subject: Optional[Any] = None
) -> None:
"""Compatibility alias for set_last_result_table_overlay."""
set_last_result_table_overlay(result_table, items=items, subject=subject)
def set_last_result_items_only(items: Optional[List[Any]]) -> None:
"""
@@ -1835,6 +1857,8 @@ class PipelineExecutor:
except Exception:
stage_table = None
debug(f"@N: stage_table={stage_table is not None}, display_table={display_table is not None}")
# Prefer selecting from the last selectable *table* (search/playlist)
# rather than from display-only emitted items, unless we're explicitly
# selecting from an overlay table.
@@ -1846,9 +1870,11 @@ class PipelineExecutor:
items_list = ctx.get_last_selectable_result_items() or []
else:
items_list = ctx.get_last_result_items() or []
except Exception:
except Exception as exc:
debug(f"@N: Exception getting items_list: {exc}")
items_list = []
debug(f"@N: selection_indices={selection_indices}, items_list length={len(items_list)}")
resolved_items = items_list if items_list else []
if items_list:
filtered = [
@@ -1920,12 +1946,15 @@ class PipelineExecutor:
filtered = track_items
table_type_hint = "tidal.track"
debug(f"@N: calling _maybe_run_class_selector with filtered={len(filtered)} items, stage_is_last={not stages}")
if PipelineExecutor._maybe_run_class_selector(
ctx,
config,
filtered,
stage_is_last=(not stages)):
debug(f"@N: _maybe_run_class_selector returned True, returning False")
return False, None
debug(f"@N: _maybe_run_class_selector returned False, continuing")
from cmdlet._shared import coerce_to_pipe_object
@@ -1934,6 +1963,7 @@ class PipelineExecutor:
filtered_pipe_objs
if len(filtered_pipe_objs) > 1 else filtered_pipe_objs[0]
)
debug(f"@N: coerced piped_result, stages={stages}")
if pipeline_session and worker_manager:
try:
@@ -2069,10 +2099,12 @@ class PipelineExecutor:
return False
if not stages:
debug(f"@N: stages is empty, checking auto_stage and metadata")
if isinstance(table_type, str) and table_type.startswith("metadata."):
print("Auto-applying metadata selection via get-tag")
stages.append(["get-tag"])
elif auto_stage:
debug(f"@N: Found auto_stage={auto_stage}, appending")
try:
print(f"Auto-running selection via {auto_stage[0]}")
except Exception:
@@ -2084,9 +2116,7 @@ class PipelineExecutor:
stages.append(list(auto_stage))
debug(f"Inserted auto stage before row action: {stages[-1]}")
# If the caller included a selection (e.g., @1) try to attach
# the selection args immediately to the inserted auto stage so
# the expansion is effective in a single pass.
# Attach selection args to auto stage
if selection_indices:
try:
if not _apply_row_action_to_stage(len(stages) - 1):
@@ -2115,41 +2145,45 @@ class PipelineExecutor:
except Exception:
logger.exception("Failed to attach selection args to auto-inserted stage")
# If no auto stage inserted and there are selection-action tokens available
# for the single selected row, apply it as the pipeline stage so a bare
# `@N` runs the intended action (e.g., get-file for hash-backed rows).
# Look for row_action in payload if still no stages
if not stages and selection_indices and len(selection_indices) == 1:
debug(f"@N: No stages and no auto_stage, looking for row_action in payload")
try:
idx = selection_indices[0]
debug(f"@N initial selection idx={idx} last_items={len(ctx.get_last_result_items() or [])}")
debug(f"@N: idx={idx}, looking for row_action")
row_action = None
try:
row_action = ctx.get_current_stage_table_row_selection_action(idx)
except Exception:
logger.exception("Failed to get current_stage_table row selection action for idx %s", idx)
debug(f"@N: row_action from table={row_action}")
except Exception as exc:
debug(f"@N: Exception getting row_selection_action: {exc}")
row_action = None
if not row_action:
debug(f"@N: row_action not found from table, checking payload")
try:
items = ctx.get_last_result_items() or []
debug(f"@N: got items, length={len(items)}")
if 0 <= idx < len(items):
maybe = items[idx]
try:
if isinstance(maybe, dict):
debug(f"@N payload: hash={maybe.get('hash')} store={maybe.get('store')} _selection_args={maybe.get('_selection_args')} _selection_action={maybe.get('_selection_action')}")
debug(f"@N: payload is dict with _selection_action={maybe.get('_selection_action')}")
else:
debug(f"@N payload object type: {type(maybe).__name__}")
debug(f"@N: payload type={type(maybe).__name__}")
except Exception:
logger.exception("Failed to debug selection payload for index %s", idx)
pass
if isinstance(maybe, dict):
candidate = maybe.get("_selection_action")
if isinstance(candidate, (list, tuple)):
row_action = [str(x) for x in candidate if x is not None]
except Exception:
debug(f"@N: extracted row_action from payload={row_action}")
except Exception as exc:
debug(f"@N: Exception checking payload: {exc}")
row_action = None
if row_action:
debug(f"@N applying row action -> {row_action}")
debug(f"@N: FOUND row_action, appending {row_action}")
stages.append(row_action)
if pipeline_session and worker_manager:
try:
@@ -2161,15 +2195,6 @@ class PipelineExecutor:
logger.exception("Failed to record pipeline log step for applied row action (pipeline_session=%r)", getattr(pipeline_session, 'worker_id', None))
except Exception:
logger.exception("Failed to apply single-row selection action")
stages.append(row_action)
if pipeline_session and worker_manager:
try:
worker_manager.log_step(
pipeline_session.worker_id,
f"@N applied row action -> {' '.join(row_action)}",
)
except Exception:
logger.exception("Failed to record pipeline log step for applied row action (pipeline_session=%r)", getattr(pipeline_session, 'worker_id', None))
else:
first_cmd = stages[0][0] if stages and stages[0] else None
if isinstance(table_type, str) and table_type.startswith("metadata.") and first_cmd not in (
@@ -2224,6 +2249,7 @@ class PipelineExecutor:
# selection-expansion logic can still run (e.g., for example selectors).
return True, piped_result
else:
debug(f"@N: No items to select from (items_list empty)")
print("No previous results to select from\n")
return False, None
@@ -2342,6 +2368,7 @@ class PipelineExecutor:
ctx = sys.modules[__name__]
try:
debug(f"execute_tokens: tokens={tokens}")
self._try_clear_pipeline_stop(ctx)
# REPL guard: stage-local tables should not persist across independent

View File

@@ -795,6 +795,16 @@ class Table:
elif isinstance(result, str):
row.add_column("Result", result)
# Extract selection metadata from payload if available (for @N expansion)
if isinstance(result, dict):
sel_args = result.get("_selection_args")
if isinstance(sel_args, (list, tuple)):
row.selection_args = [str(x) for x in sel_args if x is not None]
sel_action = result.get("_selection_action")
if isinstance(sel_action, (list, tuple)):
row.selection_action = [str(x) for x in sel_action if x is not None]
return self
def get_row_payload(self, row_index: int) -> Optional[Any]:
@@ -803,6 +813,24 @@ class Table:
return getattr(self.rows[row_index], "payload", None)
return None
def get_row_selection_args(self, row_index: int) -> Optional[List[str]]:
"""Return selection arguments for the row at ``row_index`` from its payload."""
payload = self.get_row_payload(row_index)
if isinstance(payload, dict):
args = payload.get("_selection_args")
if isinstance(args, (list, tuple)):
return [str(x) for x in args if x is not None]
return None
def get_row_selection_action(self, row_index: int) -> Optional[List[str]]:
"""Return primary selection action for the row at ``row_index`` from its payload."""
payload = self.get_row_payload(row_index)
if isinstance(payload, dict):
action = payload.get("_selection_action")
if isinstance(action, (list, tuple)):
return [str(x) for x in action if x is not None]
return None
def get_payloads(self) -> List[Any]:
"""Return the payloads for every row, preserving table order."""
payloads: List[Any] = []

View File

@@ -663,7 +663,8 @@ class search_file(Cmdlet):
if backend is None:
# Last-resort: instantiate full registry for this backend only
from Store import Store as _Store
_store = _Store(config=config)
_store = _Store(config=config, suppress_debug=True)
if _store.is_available(backend_name):
backend = _store[backend_name]
except Exception:
backend = None
@@ -812,8 +813,12 @@ class search_file(Cmdlet):
target_backend = get_backend_instance(config, backend_to_search, suppress_debug=True)
if target_backend is None:
from Store import Store as _Store
_store = _Store(config=config)
_store = _Store(config=config, suppress_debug=True)
if _store.is_available(backend_to_search):
target_backend = _store[backend_to_search]
else:
debug(f"[search-file] Requested backend '{backend_to_search}' not found")
return 1
except Exception as exc:
log(f"Backend '{backend_to_search}' not found: {exc}", file=sys.stderr)
db.update_worker_status(worker_id, "error")
@@ -838,8 +843,13 @@ class search_file(Cmdlet):
backend = get_backend_instance(config, backend_name, suppress_debug=True)
if backend is None:
from Store import Store as _Store
_store = _Store(config=config)
_store = _Store(config=config, suppress_debug=True)
if _store.is_available(backend_name):
backend = _store[backend_name]
else:
# Configured backend name exists but has no registered implementation or failed to load.
# (e.g. 'all-debrid' being treated as a store but having no store provider).
continue
searched_backends.append(backend_name)