diff --git a/cmdnat/pipe.py b/cmdnat/pipe.py index a2a868a..18b97d4 100644 --- a/cmdnat/pipe.py +++ b/cmdnat/pipe.py @@ -173,6 +173,97 @@ def _apply_log_filter(lines: Sequence[str], filter_text: Optional[str]) -> List[ return filtered +def _slice_mpv_log_to_latest_run(lines: Sequence[str]) -> List[str]: + startup_pattern = re.compile(r"\bmpv v\d", re.IGNORECASE) + collected = list(lines) + if not collected: + return [] + for idx in range(len(collected) - 1, -1, -1): + text = str(collected[idx] or "") + if startup_pattern.search(text): + return collected[idx:] + return collected + + +def _get_mpv_property(prop_name: str) -> Optional[Any]: + try: + resp = _send_ipc_command( + { + "command": ["get_property", prop_name], + }, + silent=True, + ) + if resp and resp.get("error") == "success": + return resp.get("data") + except Exception: + pass + return None + + +def _get_latest_mpv_run_marker(log_db_path: str) -> Optional[tuple[str, str]]: + try: + import sqlite3 + + conn = sqlite3.connect(log_db_path, timeout=5.0) + cur = conn.cursor() + cur.execute( + ( + "SELECT timestamp, message FROM logs " + "WHERE module = 'mpv' AND (" + "message LIKE '[helper] version=% started ipc=%' " + "OR message LIKE 'medeia lua loaded version=%'" + ") " + "ORDER BY timestamp DESC LIMIT 1" + ) + ) + row = cur.fetchone() + cur.close() + conn.close() + if row and row[0]: + return str(row[0]), str(row[1] or "") + except Exception: + pass + return None + + +def _get_mpv_logs_for_latest_run( + log_db_path: str, + *, + log_filter_text: Optional[str], + limit: int = 200, +) -> tuple[Optional[str], Optional[str], List[tuple[Any, Any, Any, Any]]]: + marker = _get_latest_mpv_run_marker(log_db_path) + marker_ts = marker[0] if marker else None + marker_msg = marker[1] if marker else None + try: + import sqlite3 + + conn = sqlite3.connect(log_db_path, timeout=5.0) + cur = conn.cursor() + query = "SELECT timestamp, level, module, message FROM logs WHERE module = 'mpv'" + params: List[str] = [] + if marker_ts: + query += " AND timestamp >= ?" + params.append(marker_ts) + else: + cutoff = (datetime.utcnow() - timedelta(hours=6)).strftime("%Y-%m-%d %H:%M:%S") + query += " AND timestamp >= ?" + params.append(cutoff) + if log_filter_text: + query += " AND LOWER(message) LIKE ?" + params.append(f"%{log_filter_text.lower()}%") + query += " ORDER BY timestamp DESC LIMIT ?" + params.append(str(max(1, int(limit)))) + cur.execute(query, tuple(params)) + rows = cur.fetchall() + cur.close() + conn.close() + rows.reverse() + return marker_ts, marker_msg, rows + except Exception: + return marker_ts, marker_msg, [] + + def _try_enable_mpv_file_logging(mpv_log_path: str, *, attempts: int = 3) -> bool: """Best-effort enable mpv log-file + verbose level on a running instance. @@ -2217,7 +2308,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int: tail_lines: List[str] = [] for _ in range(8): - tail_lines = _tail_text_file(mpv_log_path, max_lines=200) + tail_lines = _tail_text_file(mpv_log_path, max_lines=400, max_bytes=262144) if tail_lines: break try: @@ -2227,9 +2318,17 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int: except Exception: break - filtered_tail = _apply_log_filter(tail_lines, log_filter_text) + latest_run_tail = _slice_mpv_log_to_latest_run(tail_lines) + filtered_tail = _apply_log_filter(latest_run_tail, log_filter_text) + + helper_heartbeat = _get_mpv_property("user-data/medeia-pipeline-ready") + helper_status = "not running" + if helper_heartbeat not in (None, "", "0", False): + helper_status = f"running ({helper_heartbeat})" + + print(f"Pipeline helper: {helper_status}") if filtered_tail: - title = "MPV log (tail" + title = "MPV log (latest run tail" if log_filter_text: title += f" filtered by '{log_filter_text}'" title += "):" @@ -2240,7 +2339,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int: if log_filter_text: print(f"MPV log (tail): ") else: - print("MPV log (tail): ") + print("MPV log (latest run tail): ") print( "Note: On some Windows builds, mpv cannot start writing to --log-file after launch." ) @@ -2252,27 +2351,21 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int: try: import sqlite3 log_db_path = str((Path(__file__).resolve().parent.parent / "logs.db")) - conn = sqlite3.connect(log_db_path, timeout=5.0) - cur = conn.cursor() - query = "SELECT timestamp, level, module, message FROM logs WHERE module = 'mpv'" - params: List[str] = [] - cutoff_24h = (datetime.utcnow() - timedelta(hours=24)).strftime("%Y-%m-%d %H:%M:%S") - query += " AND timestamp >= ?" - params.append(cutoff_24h) - if log_filter_text: - query += " AND LOWER(message) LIKE ?" - params.append(f"%{log_filter_text.lower()}%") - query += " ORDER BY timestamp DESC LIMIT 200" - cur.execute(query, tuple(params)) - mpv_logs = cur.fetchall() - cur.close() - conn.close() + marker_ts, marker_msg, mpv_logs = _get_mpv_logs_for_latest_run( + log_db_path, + log_filter_text=log_filter_text, + limit=250, + ) if log_filter_text: print( - f"MPV logs from database (mpv module, last 24h, filtered by '{log_filter_text}', most recent first):" + f"MPV logs from database (latest run, filtered by '{log_filter_text}', chronological):" ) else: - print("MPV logs from database (mpv module, last 24h, most recent first):") + print("MPV logs from database (latest run, chronological):") + if marker_ts: + print(f"Latest run started: {marker_ts}") + if marker_msg: + print(f"Run marker: {marker_msg}") if mpv_logs: for timestamp, level, _module, message in mpv_logs: ts = str(timestamp or "").strip() @@ -2282,9 +2375,9 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int: print(f"[{level}] {message}") else: if log_filter_text: - print(f"(no mpv logs found matching '{log_filter_text}')") + print(f"(no latest-run mpv logs found matching '{log_filter_text}')") else: - print("(no mpv logs found)") + print("(no latest-run mpv logs found)") except Exception as e: debug(f"Could not fetch database logs: {e}") pass @@ -2482,7 +2575,7 @@ CMDLET = Cmdlet( CmdletArg( name="log", type="flag", - description="Enable pipeable debug output, write an mpv log file, and optionally specify a filter string right after -log to search stored mpv logs from the last 24 hours", + description="Enable pipeable debug output, write an mpv log file, and optionally specify a filter string right after -log to search stored mpv logs from the latest observed run", ), CmdletArg( name="borderless",