pre-migration commit
This commit is contained in:
+41
-8
@@ -3,11 +3,37 @@ from __future__ import annotations
|
||||
from typing import Any, Dict, List, Optional, Set
|
||||
|
||||
from .base import API, ApiError
|
||||
from SYS.logger import debug
|
||||
from SYS.logger import debug, debug_panel
|
||||
|
||||
DEFAULT_BASE_URL = "https://tidal-api.binimum.org"
|
||||
|
||||
|
||||
def _debug_payload_summary(title: str, payload: Any) -> None:
|
||||
try:
|
||||
keys = list(payload.keys()) if isinstance(payload, dict) else []
|
||||
except Exception:
|
||||
keys = []
|
||||
|
||||
preview = ", ".join(str(key) for key in keys[:8]) if keys else "<none>"
|
||||
if keys and len(keys) > 8:
|
||||
preview = f"{preview}, ..."
|
||||
|
||||
try:
|
||||
payload_size = len(str(payload))
|
||||
except Exception:
|
||||
payload_size = "<unknown>"
|
||||
|
||||
debug_panel(
|
||||
title,
|
||||
[
|
||||
("type", type(payload).__name__),
|
||||
("keys", preview),
|
||||
("size", payload_size),
|
||||
],
|
||||
border_style="cyan",
|
||||
)
|
||||
|
||||
|
||||
def stringify(value: Any) -> str:
|
||||
"""Helper to ensure we have a stripped string or empty."""
|
||||
return str(value or "").strip()
|
||||
@@ -242,14 +268,14 @@ class Tidal(API):
|
||||
|
||||
# 1. Fetch info (metadata) - fetch raw to ensure all fields are available for merging
|
||||
info_resp = self._get_json("info/", params={"id": track_int})
|
||||
debug(f"[API.Tidal] info_resp (len={len(str(info_resp))}): {info_resp}")
|
||||
_debug_payload_summary("API.Tidal info", info_resp)
|
||||
info_data = info_resp.get("data") if isinstance(info_resp, dict) else info_resp
|
||||
if not isinstance(info_data, dict) or "id" not in info_data:
|
||||
info_data = info_resp if isinstance(info_resp, dict) and "id" in info_resp else {}
|
||||
|
||||
# 2. Fetch track (manifest/bit depth)
|
||||
track_resp = self.track(track_id)
|
||||
debug(f"[API.Tidal] track_resp (len={len(str(track_resp))}): {track_resp}")
|
||||
_debug_payload_summary("API.Tidal track", track_resp)
|
||||
# Note: track() method in this class currently returns raw JSON, so we handle it similarly.
|
||||
track_data = track_resp.get("data") if isinstance(track_resp, dict) else track_resp
|
||||
if not isinstance(track_data, dict):
|
||||
@@ -259,7 +285,7 @@ class Tidal(API):
|
||||
lyrics_data = {}
|
||||
try:
|
||||
lyr_resp = self.lyrics(track_id)
|
||||
debug(f"[API.Tidal] lyrics_resp (len={len(str(lyr_resp))}): {lyr_resp}")
|
||||
_debug_payload_summary("API.Tidal lyrics", lyr_resp)
|
||||
lyrics_data = lyr_resp.get("lyrics") or lyr_resp if isinstance(lyr_resp, dict) else {}
|
||||
except Exception:
|
||||
pass
|
||||
@@ -271,11 +297,8 @@ class Tidal(API):
|
||||
if isinstance(track_data, dict):
|
||||
merged_md.update(track_data)
|
||||
|
||||
debug(f"[API.Tidal] merged_md keys: {list(merged_md.keys())}")
|
||||
|
||||
# Derived tags and normalized/parsed info
|
||||
tags = build_track_tags(merged_md)
|
||||
debug(f"[API.Tidal] generated tags: {tags}")
|
||||
parsed_info = parse_track_item(merged_md)
|
||||
|
||||
# Structure for return
|
||||
@@ -285,7 +308,17 @@ class Tidal(API):
|
||||
"tags": list(tags),
|
||||
"lyrics": lyrics_data,
|
||||
}
|
||||
debug(f"[API.Tidal] returning full_track_metadata keys: {list(res.keys())}")
|
||||
debug_panel(
|
||||
"API.Tidal full track metadata",
|
||||
[
|
||||
("track_id", track_int),
|
||||
("metadata_keys", len(merged_md)),
|
||||
("tags", len(tags)),
|
||||
("has_lyrics", bool(lyrics_data)),
|
||||
("result_keys", ", ".join(res.keys())),
|
||||
],
|
||||
border_style="cyan",
|
||||
)
|
||||
return res
|
||||
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"(rapidgator\\.net/file/[0-9]{7,8})"
|
||||
],
|
||||
"regexp": "((rapidgator\\.net|rg\\.to|rapidgator\\.asia)/file/([0-9a-zA-Z]{32}))|((rapidgator\\.net/file/[0-9]{7,8}))",
|
||||
"status": false
|
||||
"status": true
|
||||
},
|
||||
"turbobit": {
|
||||
"name": "turbobit",
|
||||
@@ -463,7 +463,7 @@
|
||||
"isra\\.cloud/\\?op=report_file&id=([0-9a-zA-Z]{12})"
|
||||
],
|
||||
"regexp": "((isra\\.cloud/[0-9a-zA-Z]{12}))|(isra\\.cloud/\\?op=report_file&id=([0-9a-zA-Z]{12}))",
|
||||
"status": false,
|
||||
"status": true,
|
||||
"hardRedirect": [
|
||||
"isra\\.cloud/([0-9a-zA-Z]{12})"
|
||||
]
|
||||
@@ -494,7 +494,7 @@
|
||||
"mediafire\\.com/(\\?|download/|file/|download\\.php\\?)([0-9a-z]{15})"
|
||||
],
|
||||
"regexp": "mediafire\\.com/(\\?|download/|file/|download\\.php\\?)([0-9a-z]{15})",
|
||||
"status": true
|
||||
"status": false
|
||||
},
|
||||
"mixdrop": {
|
||||
"name": "mixdrop",
|
||||
|
||||
@@ -506,28 +506,21 @@ class CmdletIntrospection:
|
||||
if normalized_arg == "plugin":
|
||||
canonical_cmd = (cmd_name or "").replace("_", "-").lower()
|
||||
try:
|
||||
from ProviderCore.registry import list_search_plugins, list_upload_plugins
|
||||
from ProviderCore.registry import (
|
||||
list_search_plugin_names,
|
||||
list_upload_plugin_names,
|
||||
)
|
||||
except Exception:
|
||||
list_search_plugins = None # type: ignore
|
||||
list_upload_plugins = None # type: ignore
|
||||
list_search_plugin_names = None # type: ignore
|
||||
list_upload_plugin_names = None # type: ignore
|
||||
|
||||
provider_choices: List[str] = []
|
||||
|
||||
if canonical_cmd in {"add-file"} and list_upload_plugins is not None:
|
||||
providers = list_upload_plugins(config) or {}
|
||||
available = [
|
||||
name for name, is_ready in providers.items() if is_ready
|
||||
]
|
||||
return sorted(available) if available else sorted(providers.keys())
|
||||
if canonical_cmd in {"add-file"} and list_upload_plugin_names is not None:
|
||||
return list_upload_plugin_names() or []
|
||||
|
||||
if list_search_plugins is not None:
|
||||
providers = list_search_plugins(config) or {}
|
||||
available = [
|
||||
name for name, is_ready in providers.items() if is_ready
|
||||
]
|
||||
provider_choices = sorted(available) if available else sorted(
|
||||
providers.keys()
|
||||
)
|
||||
if list_search_plugin_names is not None:
|
||||
provider_choices = list_search_plugin_names() or []
|
||||
|
||||
if provider_choices:
|
||||
return provider_choices
|
||||
@@ -579,11 +572,90 @@ class CmdletIntrospection:
|
||||
class CmdletCompleter(Completer):
|
||||
"""Prompt-toolkit completer for the Medeia cmdlet REPL."""
|
||||
|
||||
_CMDLET_NAME_REFRESH_SECONDS = 2.0
|
||||
|
||||
def __init__(self, *, config_loader: "ConfigLoader") -> None:
|
||||
self._config_loader = config_loader
|
||||
self.cmdlet_names = CmdletIntrospection.cmdlet_names()
|
||||
self._cmdlet_names_refreshed_at = time.monotonic()
|
||||
self._cmdlet_args_cache: Dict[Tuple[str, int], List[str]] = {}
|
||||
self._query_args_cache: Dict[Tuple[str, int], List[Dict[str, Any]]] = {}
|
||||
self._arg_choices_cache: Dict[Tuple[str, str, int], List[str]] = {}
|
||||
self._inline_query_choices_cache: Dict[Tuple[str, str, int], List[str]] = {}
|
||||
|
||||
def _refresh_cmdlet_names(self) -> None:
|
||||
now = time.monotonic()
|
||||
if self.cmdlet_names and (now - self._cmdlet_names_refreshed_at) < self._CMDLET_NAME_REFRESH_SECONDS:
|
||||
return
|
||||
self.cmdlet_names = CmdletIntrospection.cmdlet_names(force=False)
|
||||
self._cmdlet_names_refreshed_at = now
|
||||
|
||||
@staticmethod
|
||||
def _config_cache_key(config: Dict[str, Any]) -> int:
|
||||
return id(config) if isinstance(config, dict) else 0
|
||||
|
||||
def _cmdlet_args(self, cmd_name: str, config: Dict[str, Any]) -> List[str]:
|
||||
key = (str(cmd_name or "").lower(), self._config_cache_key(config))
|
||||
cached = self._cmdlet_args_cache.get(key)
|
||||
if cached is not None:
|
||||
return cached
|
||||
value = CmdletIntrospection.cmdlet_args(cmd_name, config)
|
||||
self._cmdlet_args_cache[key] = value
|
||||
return value
|
||||
|
||||
def _query_args(self, cmd_name: str, config: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
key = (str(cmd_name or "").lower(), self._config_cache_key(config))
|
||||
cached = self._query_args_cache.get(key)
|
||||
if cached is not None:
|
||||
return cached
|
||||
value = CmdletIntrospection.query_args(cmd_name, config)
|
||||
self._query_args_cache[key] = value
|
||||
return value
|
||||
|
||||
def _arg_choices(
|
||||
self,
|
||||
*,
|
||||
cmd_name: str,
|
||||
arg_name: str,
|
||||
config: Dict[str, Any],
|
||||
force: bool = False,
|
||||
) -> List[str]:
|
||||
key = (
|
||||
str(cmd_name or "").lower(),
|
||||
str(arg_name or "").lower(),
|
||||
self._config_cache_key(config),
|
||||
)
|
||||
if not force:
|
||||
cached = self._arg_choices_cache.get(key)
|
||||
if cached is not None:
|
||||
return cached
|
||||
value = CmdletIntrospection.arg_choices(
|
||||
cmd_name=cmd_name,
|
||||
arg_name=arg_name,
|
||||
config=config,
|
||||
force=force,
|
||||
)
|
||||
self._arg_choices_cache[key] = value
|
||||
return value
|
||||
|
||||
def _inline_query_choices(
|
||||
self,
|
||||
provider_name: str,
|
||||
field_name: str,
|
||||
config: Dict[str, Any],
|
||||
) -> List[str]:
|
||||
key = (
|
||||
str(provider_name or "").lower(),
|
||||
str(field_name or "").lower(),
|
||||
self._config_cache_key(config),
|
||||
)
|
||||
cached = self._inline_query_choices_cache.get(key)
|
||||
if cached is not None:
|
||||
return cached
|
||||
value = plugin_inline_query_choices(provider_name, field_name, config)
|
||||
self._inline_query_choices_cache[key] = value
|
||||
return value
|
||||
|
||||
def _used_arg_logicals(
|
||||
cmd_name: str,
|
||||
stage_tokens: List[str],
|
||||
@@ -595,7 +667,7 @@ class CmdletCompleter(Completer):
|
||||
Example: if the user has typed `download-file -url ...`, then `url`
|
||||
is considered used and should not be suggested again (even as `--url`).
|
||||
"""
|
||||
arg_flags = CmdletIntrospection.cmdlet_args(cmd_name, config)
|
||||
arg_flags = self._cmdlet_args(cmd_name, config)
|
||||
allowed = {a.lstrip("-").strip().lower()
|
||||
for a in arg_flags if a}
|
||||
if not allowed:
|
||||
@@ -636,8 +708,7 @@ class CmdletCompleter(Completer):
|
||||
document: Document,
|
||||
complete_event
|
||||
): # type: ignore[override]
|
||||
# Refresh cmdlet names from introspection to pick up dynamic updates
|
||||
self.cmdlet_names = CmdletIntrospection.cmdlet_names(force=True)
|
||||
self._refresh_cmdlet_names()
|
||||
|
||||
text = document.text_before_cursor
|
||||
tokens = text.split()
|
||||
@@ -660,7 +731,7 @@ class CmdletCompleter(Completer):
|
||||
if ends_with_space:
|
||||
cmd_name = current.replace("_", "-")
|
||||
|
||||
config = self._config_loader.load()
|
||||
config = self._config_loader.load_shared()
|
||||
|
||||
if cmd_name == "help":
|
||||
for cmd in self.cmdlet_names:
|
||||
@@ -670,7 +741,7 @@ class CmdletCompleter(Completer):
|
||||
if cmd_name not in self.cmdlet_names:
|
||||
return
|
||||
|
||||
arg_names = CmdletIntrospection.cmdlet_args(cmd_name, config)
|
||||
arg_names = self._cmdlet_args(cmd_name, config)
|
||||
seen_logicals: Set[str] = set()
|
||||
for arg in arg_names:
|
||||
arg_low = arg.lower()
|
||||
@@ -701,13 +772,13 @@ class CmdletCompleter(Completer):
|
||||
current_token = stage_tokens[-1].lower()
|
||||
prev_token = stage_tokens[-2].lower() if len(stage_tokens) > 1 else ""
|
||||
|
||||
config = self._config_loader.load()
|
||||
config = self._config_loader.load_shared()
|
||||
|
||||
provider_name = None
|
||||
if cmd_name == "search-file":
|
||||
provider_name = self._flag_value(stage_tokens, "-plugin", "--plugin")
|
||||
|
||||
query_specs = CmdletIntrospection.query_args(cmd_name, config)
|
||||
query_specs = self._query_args(cmd_name, config)
|
||||
query_flag_index = -1
|
||||
for idx, tok in enumerate(stage_tokens):
|
||||
if str(tok or "").strip().lower() in {"-query", "--query"}:
|
||||
@@ -754,7 +825,7 @@ class CmdletCompleter(Completer):
|
||||
|
||||
inline_choices = []
|
||||
if cmd_name == "search-file" and provider_name:
|
||||
inline_choices = plugin_inline_query_choices(provider_name, field, config)
|
||||
inline_choices = self._inline_query_choices(provider_name, field, config)
|
||||
|
||||
choice_pool = inline_choices or field_choices.get(field, [])
|
||||
if choice_pool:
|
||||
@@ -800,7 +871,7 @@ class CmdletCompleter(Completer):
|
||||
field, partial = inline_token.split(":", 1)
|
||||
field = field.strip().lower()
|
||||
partial_lower = partial.strip().lower()
|
||||
inline_choices = plugin_inline_query_choices(provider_name, field, config)
|
||||
inline_choices = self._inline_query_choices(provider_name, field, config)
|
||||
if inline_choices:
|
||||
filtered = (
|
||||
[c for c in inline_choices if partial_lower in str(c).lower()]
|
||||
@@ -814,11 +885,11 @@ class CmdletCompleter(Completer):
|
||||
yield Completion(suggestion, start_position=start_pos)
|
||||
return
|
||||
|
||||
choices = CmdletIntrospection.arg_choices(
|
||||
choices = self._arg_choices(
|
||||
cmd_name=cmd_name,
|
||||
arg_name=prev_token,
|
||||
config=config,
|
||||
force=True
|
||||
force=False,
|
||||
)
|
||||
if choices:
|
||||
choice_list = choices
|
||||
@@ -835,7 +906,7 @@ class CmdletCompleter(Completer):
|
||||
# is considered used and should not be suggested again (even as `--url`).
|
||||
return
|
||||
|
||||
arg_names = CmdletIntrospection.cmdlet_args(cmd_name, config)
|
||||
arg_names = self._cmdlet_args(cmd_name, config)
|
||||
used_logicals = self._used_arg_logicals(cmd_name, stage_tokens, config)
|
||||
logical_seen: Set[str] = set()
|
||||
for arg in arg_names:
|
||||
@@ -869,9 +940,15 @@ class ConfigLoader:
|
||||
def __init__(self, *, root: Path) -> None:
|
||||
self._root = root
|
||||
|
||||
def load_shared(self) -> Dict[str, Any]:
|
||||
try:
|
||||
return load_config(emit_summary=False)
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
def load(self) -> Dict[str, Any]:
|
||||
try:
|
||||
return deepcopy(load_config())
|
||||
return deepcopy(self.load_shared())
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
@@ -567,6 +567,15 @@ def list_search_plugins(config: Optional[Dict[str, Any]] = None) -> Dict[str, bo
|
||||
return availability
|
||||
|
||||
|
||||
def list_search_plugin_names() -> List[str]:
|
||||
"""Return registered search-provider names without instantiating plugins."""
|
||||
return sorted(
|
||||
info.canonical_name
|
||||
for info in REGISTRY.iter_providers()
|
||||
if info.supports_search
|
||||
)
|
||||
|
||||
|
||||
def get_upload_plugin(name: str,
|
||||
config: Optional[Dict[str, Any]] = None) -> Optional[FileProvider]:
|
||||
plugin = get_plugin(name, config)
|
||||
@@ -591,6 +600,15 @@ def list_upload_plugins(config: Optional[Dict[str, Any]] = None) -> Dict[str, bo
|
||||
return availability
|
||||
|
||||
|
||||
def list_upload_plugin_names() -> List[str]:
|
||||
"""Return registered upload-provider names without instantiating plugins."""
|
||||
return sorted(
|
||||
info.canonical_name
|
||||
for info in REGISTRY.iter_providers()
|
||||
if info.supports_upload
|
||||
)
|
||||
|
||||
|
||||
def match_plugin_name_for_url(url: str) -> Optional[str]:
|
||||
raw_url = str(url or "").strip()
|
||||
raw_url_lower = raw_url.lower()
|
||||
@@ -666,14 +684,20 @@ def plugin_inline_query_choices(
|
||||
if not pname or not field:
|
||||
return []
|
||||
|
||||
plugin = get_search_plugin(pname, config)
|
||||
if plugin is None:
|
||||
plugin = get_plugin(pname, config)
|
||||
if plugin is None:
|
||||
return []
|
||||
|
||||
try:
|
||||
mapping = _collect_inline_choice_mapping(plugin)
|
||||
mapping: Dict[str, List[Dict[str, Any]]] = {}
|
||||
info = REGISTRY.get(pname)
|
||||
if info is not None:
|
||||
mapping = _collect_inline_choice_mapping(info.provider_class)
|
||||
|
||||
if not mapping:
|
||||
plugin = get_search_plugin(pname, config)
|
||||
if plugin is None:
|
||||
plugin = get_plugin(pname, config)
|
||||
if plugin is None:
|
||||
return []
|
||||
mapping = _collect_inline_choice_mapping(plugin)
|
||||
|
||||
if not mapping:
|
||||
return []
|
||||
|
||||
|
||||
@@ -338,7 +338,7 @@ def get_cmdlet_arg_choices(
|
||||
if config is None:
|
||||
from SYS.config import load_config
|
||||
|
||||
config = load_config()
|
||||
config = load_config(emit_summary=False)
|
||||
except Exception as exc:
|
||||
logger.exception("Failed to load config for matrix default choices: %s", exc)
|
||||
config = config or {}
|
||||
|
||||
+1
-1
@@ -111,7 +111,7 @@ class SharedArgs:
|
||||
try:
|
||||
from SYS.config import load_config
|
||||
|
||||
config = load_config()
|
||||
config = load_config(emit_summary=False)
|
||||
except Exception:
|
||||
SharedArgs._cached_available_stores = []
|
||||
return
|
||||
|
||||
+32
-20
@@ -28,6 +28,7 @@ _SAVE_LOCK_STALE_SECONDS = 3600 # consider lock stale after 1 hour
|
||||
|
||||
_CONFIG_CACHE: Dict[str, Any] = {}
|
||||
_LAST_SAVED_CONFIG: Dict[str, Any] = {}
|
||||
_CONFIG_SUMMARY_PENDING = False
|
||||
_CONFIG_SAVE_MAX_RETRIES = 5
|
||||
_CONFIG_SAVE_RETRY_DELAY = 0.15
|
||||
_CONFIG_MISSING = object()
|
||||
@@ -84,9 +85,28 @@ def global_config() -> List[Dict[str, Any]]:
|
||||
|
||||
def clear_config_cache() -> None:
|
||||
"""Clear the configuration cache and baseline snapshot."""
|
||||
global _CONFIG_CACHE, _LAST_SAVED_CONFIG
|
||||
global _CONFIG_CACHE, _LAST_SAVED_CONFIG, _CONFIG_SUMMARY_PENDING
|
||||
_CONFIG_CACHE = {}
|
||||
_LAST_SAVED_CONFIG = {}
|
||||
_CONFIG_SUMMARY_PENDING = False
|
||||
|
||||
|
||||
def _log_config_load_summary(config: Dict[str, Any]) -> None:
|
||||
try:
|
||||
provs = list(config.get("provider", {}).keys()) if isinstance(config.get("provider"), dict) else []
|
||||
stores = list(config.get("store", {}).keys()) if isinstance(config.get("store"), dict) else []
|
||||
mtime = None
|
||||
try:
|
||||
mtime = datetime.datetime.fromtimestamp(db.db_path.stat().st_mtime, datetime.timezone.utc).isoformat().replace('+00:00', 'Z')
|
||||
except Exception:
|
||||
mtime = None
|
||||
summary = (
|
||||
f"Loaded config from {db.db_path.name}: providers={len(provs)} ({', '.join(provs[:10])}{'...' if len(provs)>10 else ''}), "
|
||||
f"stores={len(stores)} ({', '.join(stores[:10])}{'...' if len(stores)>10 else ''}), mtime={mtime}"
|
||||
)
|
||||
log(summary)
|
||||
except Exception:
|
||||
logger.exception("Failed to build config load summary from %s", db.db_path)
|
||||
|
||||
|
||||
def get_nested_config_value(config: Dict[str, Any], *path: str) -> Any:
|
||||
@@ -624,9 +644,12 @@ def _count_changed_entries(old_config: Dict[str, Any], new_config: Dict[str, Any
|
||||
return len(changed) + len(removed)
|
||||
|
||||
|
||||
def load_config() -> Dict[str, Any]:
|
||||
global _CONFIG_CACHE, _LAST_SAVED_CONFIG
|
||||
def load_config(*, emit_summary: bool = True) -> Dict[str, Any]:
|
||||
global _CONFIG_CACHE, _LAST_SAVED_CONFIG, _CONFIG_SUMMARY_PENDING
|
||||
if _CONFIG_CACHE:
|
||||
if emit_summary and _CONFIG_SUMMARY_PENDING:
|
||||
_log_config_load_summary(_CONFIG_CACHE)
|
||||
_CONFIG_SUMMARY_PENDING = False
|
||||
return _CONFIG_CACHE
|
||||
|
||||
# Load strictly from database
|
||||
@@ -635,24 +658,13 @@ def load_config() -> Dict[str, Any]:
|
||||
_sync_alldebrid_api_key(db_config)
|
||||
_CONFIG_CACHE = db_config
|
||||
_LAST_SAVED_CONFIG = deepcopy(db_config)
|
||||
try:
|
||||
# Log a compact summary to help detect startup overwrites/mismatches
|
||||
provs = list(db_config.get("provider", {}).keys()) if isinstance(db_config.get("provider"), dict) else []
|
||||
stores = list(db_config.get("store", {}).keys()) if isinstance(db_config.get("store"), dict) else []
|
||||
mtime = None
|
||||
try:
|
||||
mtime = datetime.datetime.fromtimestamp(db.db_path.stat().st_mtime, datetime.timezone.utc).isoformat().replace('+00:00', 'Z')
|
||||
except Exception:
|
||||
mtime = None
|
||||
summary = (
|
||||
f"Loaded config from {db.db_path.name}: providers={len(provs)} ({', '.join(provs[:10])}{'...' if len(provs)>10 else ''}), "
|
||||
f"stores={len(stores)} ({', '.join(stores[:10])}{'...' if len(stores)>10 else ''}), mtime={mtime}"
|
||||
)
|
||||
log(summary)
|
||||
if emit_summary:
|
||||
_log_config_load_summary(db_config)
|
||||
_CONFIG_SUMMARY_PENDING = False
|
||||
else:
|
||||
_CONFIG_SUMMARY_PENDING = True
|
||||
|
||||
# Forensics disabled: audit/mismatch/backup detection removed to simplify code.
|
||||
except Exception:
|
||||
logger.exception("Failed to build config load summary from %s", db.db_path)
|
||||
# Forensics disabled: audit/mismatch/backup detection removed to simplify code.
|
||||
return db_config
|
||||
|
||||
_LAST_SAVED_CONFIG = {}
|
||||
|
||||
+1
-4
@@ -146,7 +146,7 @@ def debug_panel(
|
||||
def debug(*args, **kwargs) -> None:
|
||||
"""Print debug message if debug logging is enabled.
|
||||
|
||||
Automatically prepends [filename.function_name] to all output.
|
||||
Automatically routes through log() so debug output keeps the caller prefix.
|
||||
"""
|
||||
if not _DEBUG_ENABLED:
|
||||
return
|
||||
@@ -166,9 +166,6 @@ def debug(*args, **kwargs) -> None:
|
||||
_debug_db_log(caller_name=caller_name, message=f"<rich:{type(renderable).__name__}>")
|
||||
return
|
||||
|
||||
# Prepend DEBUG label
|
||||
args = ("DEBUG:", *args)
|
||||
|
||||
# Use the same logic as log()
|
||||
log(*args, file=target_file, **kwargs)
|
||||
|
||||
|
||||
@@ -1354,9 +1354,6 @@ class Table:
|
||||
"")
|
||||
).lower()
|
||||
|
||||
# Debug logging
|
||||
# print(f"DEBUG: Processing dict result. Store: {store_val}, Keys: {list(visible_data.keys())}")
|
||||
|
||||
if store_val == "local":
|
||||
# Find title field
|
||||
title_field = next(
|
||||
@@ -1373,8 +1370,6 @@ class Table:
|
||||
# Only use title suffix as fallback when ext is missing.
|
||||
if not str(visible_data.get("ext") or "").strip():
|
||||
visible_data["ext"] = extension
|
||||
# print(f"DEBUG: Split extension. Title: {visible_data[title_field]}, Ext: {extension}")
|
||||
|
||||
# Ensure 'ext' is present so it gets picked up by priority_groups in correct order
|
||||
if "ext" not in visible_data:
|
||||
visible_data["ext"] = ""
|
||||
|
||||
+10
-2
@@ -1045,8 +1045,16 @@ class HydrusNetwork(Store):
|
||||
if total_candidates <= hydrate_limit:
|
||||
return ids_out, hashes_out
|
||||
|
||||
debug(
|
||||
f"{prefix} limiting metadata hydration to {hydrate_limit} of {total_candidates} candidate(s)"
|
||||
debug_panel(
|
||||
"Hydrus metadata hydration cap",
|
||||
[
|
||||
("store", self.NAME),
|
||||
("candidates", total_candidates),
|
||||
("hydrate_limit", hydrate_limit),
|
||||
("freeform_mode", freeform_mode),
|
||||
("fallback_scan", fallback_scan),
|
||||
],
|
||||
border_style="cyan",
|
||||
)
|
||||
|
||||
if ids_out:
|
||||
|
||||
+1
-1
@@ -249,7 +249,7 @@ class SharedArgs:
|
||||
if config is None:
|
||||
try:
|
||||
from SYS.config import load_config
|
||||
config = load_config()
|
||||
config = load_config(emit_summary=False)
|
||||
except Exception:
|
||||
SharedArgs._cached_available_stores = []
|
||||
return
|
||||
|
||||
+20
-3
@@ -13,7 +13,7 @@ import html
|
||||
import time
|
||||
from urllib.parse import urlparse, parse_qs, unquote, urljoin
|
||||
|
||||
from SYS.logger import log, debug
|
||||
from SYS.logger import log, debug, debug_panel
|
||||
from SYS.payload_builders import build_file_result_payload, normalize_file_extension
|
||||
from ProviderCore.registry import get_search_plugin, list_search_plugins
|
||||
from SYS.rich_display import (
|
||||
@@ -1560,9 +1560,26 @@ class search_file(Cmdlet):
|
||||
source_cmd, source_args = provider.get_source_command(args_list)
|
||||
table.set_source_command(source_cmd, source_args)
|
||||
|
||||
debug(f"[search-file] Calling {plugin_name}.search(filters={search_filters})")
|
||||
debug_panel(
|
||||
"search-file provider request",
|
||||
[
|
||||
("provider", plugin_name),
|
||||
("query", query),
|
||||
("limit", limit),
|
||||
("filters", search_filters or "<none>"),
|
||||
],
|
||||
border_style="cyan",
|
||||
)
|
||||
results = provider.search(query, limit=limit, filters=search_filters or None)
|
||||
debug(f"[search-file] {plugin_name} -> {len(results or [])} result(s)")
|
||||
debug_panel(
|
||||
"search-file provider response",
|
||||
[
|
||||
("provider", plugin_name),
|
||||
("results", len(results or [])),
|
||||
("table", table_type),
|
||||
],
|
||||
border_style="cyan",
|
||||
)
|
||||
|
||||
# Allow providers to apply provider-specific UX transforms (e.g. auto-expansion)
|
||||
try:
|
||||
|
||||
@@ -5,7 +5,7 @@ from urllib.parse import urlparse
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from ProviderCore.base import Provider, SearchResult
|
||||
from SYS.logger import log, debug
|
||||
from SYS.logger import log, debug, debug_panel
|
||||
|
||||
from tool.playwright import PlaywrightTool
|
||||
|
||||
@@ -58,7 +58,14 @@ class Bandcamp(Provider):
|
||||
if not base or not discography_url:
|
||||
return []
|
||||
|
||||
debug(f"[bandcamp] Scraping artist page: {discography_url}")
|
||||
debug_panel(
|
||||
"bandcamp artist scrape",
|
||||
[
|
||||
("url", discography_url),
|
||||
("limit", limit),
|
||||
],
|
||||
border_style="cyan",
|
||||
)
|
||||
page.goto(discography_url)
|
||||
page.wait_for_load_state("domcontentloaded")
|
||||
|
||||
@@ -301,7 +308,14 @@ class Bandcamp(Provider):
|
||||
return []
|
||||
|
||||
def _scrape_url(self, page: Any, url: str, limit: int) -> List[SearchResult]:
|
||||
debug(f"[bandcamp] Scraping: {url}")
|
||||
debug_panel(
|
||||
"bandcamp search",
|
||||
[
|
||||
("url", url),
|
||||
("limit", limit),
|
||||
],
|
||||
border_style="cyan",
|
||||
)
|
||||
|
||||
page.goto(url)
|
||||
page.wait_for_load_state("domcontentloaded")
|
||||
@@ -20,7 +20,7 @@ from ProviderCore.base import Provider, SearchResult, parse_inline_query_argumen
|
||||
from SYS.field_access import get_field
|
||||
from Provider.tidal_manifest import resolve_tidal_manifest_path
|
||||
from SYS import pipeline as pipeline_context
|
||||
from SYS.logger import debug, log
|
||||
from SYS.logger import debug, debug_panel, log
|
||||
|
||||
URL_API = (
|
||||
"https://triton.squid.wtf",
|
||||
@@ -1136,7 +1136,15 @@ class HIFI(Provider):
|
||||
md = dict(getattr(result, "full_metadata") or {})
|
||||
|
||||
track_id = self._extract_track_id_from_result(result)
|
||||
debug(f"[hifi] download: track_id={track_id}, manifest_present={bool(md.get('manifest'))}, tag_count={len(result.tag) if result.tag else 0}")
|
||||
debug_panel(
|
||||
"hifi download",
|
||||
[
|
||||
("track_id", track_id or "<missing>"),
|
||||
("manifest_present", bool(md.get("manifest"))),
|
||||
("tag_count", len(result.tag) if result.tag else 0),
|
||||
],
|
||||
border_style="cyan",
|
||||
)
|
||||
|
||||
# Enrichment: fetch full metadata if manifest or detailed info (like tags/lyrics) is missing.
|
||||
# We check for 'manifest' because it's required for DASH playback.
|
||||
@@ -1144,20 +1152,46 @@ class HIFI(Provider):
|
||||
has_lyrics = bool(md.get("_tidal_lyrics_subtitles")) or bool(md.get("lyrics"))
|
||||
|
||||
if track_id and (not md.get("manifest") or not md.get("artist") or len(result.tag or []) <= 1 or not has_lyrics):
|
||||
debug(f"[hifi] Enriching track data (reason: manifest={not md.get('manifest')}, lyrics={not has_lyrics}, tags={len(result.tag or [])})")
|
||||
debug_panel(
|
||||
"hifi enrichment request",
|
||||
[
|
||||
("track_id", track_id),
|
||||
("needs_manifest", not md.get("manifest")),
|
||||
("needs_lyrics", not has_lyrics),
|
||||
("current_tags", len(result.tag or [])),
|
||||
],
|
||||
border_style="cyan",
|
||||
)
|
||||
# Multi-part enrichment from API: metadata, tags, and lyrics.
|
||||
full_data = self._fetch_all_track_data(track_id)
|
||||
debug(f"[hifi] download: enrichment full_data present={bool(full_data)}")
|
||||
debug_panel(
|
||||
"hifi enrichment response",
|
||||
[
|
||||
("track_id", track_id),
|
||||
("full_data", bool(full_data)),
|
||||
(
|
||||
"metadata_keys",
|
||||
len(full_data.get("metadata") or {}) if isinstance(full_data, dict) else 0,
|
||||
),
|
||||
(
|
||||
"tag_count",
|
||||
len(full_data.get("tags") or []) if isinstance(full_data, dict) else 0,
|
||||
),
|
||||
(
|
||||
"has_lyrics",
|
||||
bool(full_data.get("lyrics")) if isinstance(full_data, dict) else False,
|
||||
),
|
||||
],
|
||||
border_style="cyan",
|
||||
)
|
||||
if isinstance(full_data, dict):
|
||||
# 1. Update metadata
|
||||
api_md = full_data.get("metadata")
|
||||
if isinstance(api_md, dict):
|
||||
debug(f"[hifi] download: updating metadata with {len(api_md)} keys")
|
||||
md.update(api_md)
|
||||
|
||||
# 2. Update tags (re-sync result.tag so cmdlet sees them)
|
||||
api_tags = full_data.get("tags")
|
||||
debug(f"[hifi] download: enrichment tags={api_tags}")
|
||||
if isinstance(api_tags, list) and api_tags:
|
||||
result.tag = set(api_tags)
|
||||
|
||||
@@ -18,7 +18,7 @@ from pathlib import Path
|
||||
from API.HTTP import HTTPClient
|
||||
from ProviderCore.base import Provider, SearchResult, parse_inline_query_arguments
|
||||
from ProviderCore.inline_utils import resolve_filter
|
||||
from SYS.logger import debug
|
||||
from SYS.logger import debug, debug_panel
|
||||
from SYS.provider_helpers import TableProviderMixin
|
||||
from tool.playwright import PlaywrightTool
|
||||
|
||||
@@ -178,7 +178,17 @@ class Vimm(TableProviderMixin, Provider):
|
||||
if region_param:
|
||||
params.append(("region", region_param))
|
||||
url = f"{base}?{urlencode(params)}"
|
||||
debug(f"[vimm] search: query={q} url={url} filters={normalized_filters}")
|
||||
debug_panel(
|
||||
"vimm search",
|
||||
[
|
||||
("query", q),
|
||||
("url", url),
|
||||
("system", system_param or "<any>"),
|
||||
("region", region_param or "<any>"),
|
||||
("filters", normalized_filters or "<none>"),
|
||||
],
|
||||
border_style="cyan",
|
||||
)
|
||||
|
||||
try:
|
||||
with HTTPClient(timeout=9.0) as client:
|
||||
@@ -210,7 +220,15 @@ class Vimm(TableProviderMixin, Provider):
|
||||
|
||||
results = [self._apply_selection_defaults(r, referer=url, detail_url=getattr(r, "path", "")) for r in (results or [])]
|
||||
|
||||
debug(f"[vimm] results={len(results)}")
|
||||
debug_panel(
|
||||
"vimm search results",
|
||||
[
|
||||
("query", q),
|
||||
("results", len(results)),
|
||||
("url", url),
|
||||
],
|
||||
border_style="cyan",
|
||||
)
|
||||
return results[: int(limit)]
|
||||
|
||||
def extract_query_arguments(self, query: str) -> Tuple[str, Dict[str, Any]]:
|
||||
@@ -77,7 +77,7 @@ def _ensure_interactive_stdin() -> None:
|
||||
sys.stdin.flush()
|
||||
except Exception as e:
|
||||
if "--debug" in sys.argv:
|
||||
print(f"DEBUG: Failed to re-open stdin: {e}")
|
||||
print(f"[bootstrap] Failed to re-open stdin: {e}")
|
||||
|
||||
|
||||
def run(cmd: list[str], quiet: bool = False, debug: bool = False, cwd: Optional[Path] = None, env: Optional[dict[str, str]] = None, check: bool = True) -> subprocess.CompletedProcess:
|
||||
@@ -1799,10 +1799,10 @@ if (Test-Path (Join-Path $repo 'CLI.py')) {
|
||||
raise RuntimeError("Failed to create mm.bat shim")
|
||||
|
||||
if args.debug:
|
||||
print(f"DEBUG: Created mm.bat ({len(bat_text)} bytes)")
|
||||
print(f"DEBUG: Repo path embedded in shim: {repo}")
|
||||
print(f"DEBUG: Venv location: {repo}/.venv")
|
||||
print(f"DEBUG: Shim directory: {user_bin}")
|
||||
print(f"[bootstrap] Created mm.bat ({len(bat_text)} bytes)")
|
||||
print(f"[bootstrap] Repo path embedded in shim: {repo}")
|
||||
print(f"[bootstrap] Venv location: {repo}/.venv")
|
||||
print(f"[bootstrap] Shim directory: {user_bin}")
|
||||
|
||||
# Add user_bin to PATH for current and future sessions
|
||||
str_bin = str(user_bin)
|
||||
@@ -1832,7 +1832,7 @@ if (Test-Path (Join-Path $repo 'CLI.py')) {
|
||||
text=True
|
||||
)
|
||||
if args.debug and result.stderr:
|
||||
print(f"DEBUG: PowerShell output: {result.stderr}")
|
||||
print(f"[bootstrap] PowerShell output: {result.stderr}")
|
||||
|
||||
# Also reload PATH in current session for immediate availability
|
||||
reload_cmd = (
|
||||
@@ -1849,7 +1849,7 @@ if (Test-Path (Join-Path $repo 'CLI.py')) {
|
||||
)
|
||||
except Exception as e:
|
||||
if args.debug:
|
||||
print(f"DEBUG: Could not persist PATH to registry: {e}", file=sys.stderr)
|
||||
print(f"[bootstrap] Could not persist PATH to registry: {e}", file=sys.stderr)
|
||||
|
||||
if not args.quiet:
|
||||
print(f"Installed global launcher to: {user_bin}")
|
||||
@@ -1927,7 +1927,7 @@ if (Test-Path (Join-Path $repo 'CLI.py')) {
|
||||
'VENV="$REPO/.venv"\n'
|
||||
"# Debug mode: set MM_DEBUG=1 to print repository, venv, and import diagnostics\n"
|
||||
'if [ -n "${MM_DEBUG:-}" ]; then\n'
|
||||
' echo "MM_DEBUG: diagnostics" >&2\n'
|
||||
' echo "[mm-debug] diagnostics" >&2\n'
|
||||
' echo "Resolved REPO: $REPO" >&2\n'
|
||||
' echo "Resolved VENV: $VENV" >&2\n'
|
||||
' echo "VENV exists: $( [ -d "$VENV" ] && echo yes || echo no )" >&2\n'
|
||||
@@ -1943,7 +1943,7 @@ if (Test-Path (Join-Path $repo 'CLI.py')) {
|
||||
" $pycmd - <<'PY'\nimport sys, importlib, traceback, importlib.util\nprint('sys.executable:', sys.executable)\nprint('sys.path (first 8):', sys.path[:8])\nfor mod in ('CLI','medeia_macina','scripts.cli_entry'):\n try:\n spec = importlib.util.find_spec(mod)\n print(mod, 'spec:', spec)\n if spec:\n m = importlib.import_module(mod)\n print(mod, 'loaded at', getattr(m, '__file__', None))\n except Exception:\n print(mod, 'import failed')\n traceback.print_exc()\nPY\n"
|
||||
" fi\n"
|
||||
" done\n"
|
||||
' echo "MM_DEBUG: end diagnostics" >&2\n'
|
||||
' echo "[mm-debug] end diagnostics" >&2\n'
|
||||
"fi\n"
|
||||
"\n"
|
||||
"# Automatically check for updates if this is a git repository\n"
|
||||
|
||||
Reference in New Issue
Block a user