refactor(download): remove ProviderCore/download.py, move sanitize_filename to SYS.utils, replace callers to use API.HTTP.HTTPClient
This commit is contained in:
@@ -219,17 +219,18 @@ class SharedArgs:
|
||||
SharedArgs.STORE.choices = SharedArgs.get_store_choices(config)
|
||||
"""
|
||||
try:
|
||||
from Store import Store
|
||||
# Use the non-instantiating helper so autocomplete doesn't trigger backend init.
|
||||
from Store.registry import list_configured_backend_names
|
||||
|
||||
# If no config provided, try to load it
|
||||
if config is None:
|
||||
try:
|
||||
from SYS.config import load_config
|
||||
config = load_config()
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
store = Store(config)
|
||||
return store.list_backends()
|
||||
return list_configured_backend_names(config)
|
||||
except Exception:
|
||||
# Fallback to empty list if FileStorage isn't available
|
||||
return []
|
||||
|
||||
@@ -321,9 +321,11 @@ class Add_File(Cmdlet):
|
||||
is_storage_backend_location = False
|
||||
if location:
|
||||
try:
|
||||
store_probe = Store(config)
|
||||
# Use a config-only check to avoid instantiating backends (which may perform network checks).
|
||||
from Store.registry import list_configured_backend_names
|
||||
|
||||
is_storage_backend_location = location in (
|
||||
store_probe.list_backends() or []
|
||||
list_configured_backend_names(config) or []
|
||||
)
|
||||
except Exception:
|
||||
is_storage_backend_location = False
|
||||
|
||||
@@ -70,6 +70,7 @@ class Download_File(Cmdlet):
|
||||
"download-http"],
|
||||
arg=[
|
||||
SharedArgs.URL,
|
||||
SharedArgs.PROVIDER,
|
||||
SharedArgs.PATH,
|
||||
SharedArgs.QUERY,
|
||||
# Prefer -path for output directory to match other cmdlets; keep -output for backwards compatibility.
|
||||
@@ -121,6 +122,7 @@ class Download_File(Cmdlet):
|
||||
|
||||
def run(self, result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
"""Main execution method."""
|
||||
debug(f"[download-file] run invoked with args: {list(args)}")
|
||||
return self._run_impl(result, args, config)
|
||||
|
||||
@staticmethod
|
||||
@@ -889,7 +891,7 @@ class Download_File(Cmdlet):
|
||||
|
||||
return expanded_items
|
||||
|
||||
def _process_provider_items(
|
||||
def _process_provider_items(self,
|
||||
*,
|
||||
piped_items: Sequence[Any],
|
||||
final_output_dir: Path,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, Iterable, Optional, Sequence
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from typing import Any, Dict, Iterable, Sequence
|
||||
|
||||
from . import _shared as sh
|
||||
from SYS.logger import log, debug
|
||||
@@ -68,47 +68,34 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
return 1
|
||||
items = inputs
|
||||
|
||||
# Build rows
|
||||
try:
|
||||
rows = list(provider.adapter(items))
|
||||
table = provider.build_table(items)
|
||||
except Exception as exc:
|
||||
log(f"Provider adapter failed: {exc}", file=sys.stderr)
|
||||
log(f"Provider '{provider.name}' failed: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
cols = provider.get_columns(rows)
|
||||
|
||||
# Emit rows for downstream pipeline consumption (pipable behavior).
|
||||
try:
|
||||
for r in rows:
|
||||
for item in provider.serialize_rows(table.rows):
|
||||
try:
|
||||
item = {
|
||||
"title": getattr(r, "title", None) or None,
|
||||
"path": getattr(r, "path", None) or None,
|
||||
"ext": getattr(r, "ext", None) or None,
|
||||
"size_bytes": getattr(r, "size_bytes", None) or None,
|
||||
"metadata": getattr(r, "metadata", None) or {},
|
||||
"source": getattr(r, "source", None) or provider.name,
|
||||
"_selection_args": provider.selection_args(r),
|
||||
}
|
||||
ctx.emit(item)
|
||||
except Exception:
|
||||
# Best-effort: continue emitting other rows
|
||||
continue
|
||||
except Exception:
|
||||
# Non-fatal: continue to rendering even if emission fails
|
||||
# Non-fatal: rendering still happens
|
||||
pass
|
||||
|
||||
# Render using RichRenderer
|
||||
try:
|
||||
table = RichRenderer().render(rows, cols, provider.metadata)
|
||||
renderable = RichRenderer().render(table.rows, table.columns, table.meta)
|
||||
try:
|
||||
from rich.console import Console
|
||||
|
||||
Console().print(table)
|
||||
Console().print(renderable)
|
||||
except Exception:
|
||||
# Fallback to simple printing
|
||||
for r in rows:
|
||||
print(" ".join(str((c.extractor(r) or "")) for c in cols))
|
||||
for r in table.rows:
|
||||
print(" ".join(str((c.extractor(r) or "")) for c in table.columns))
|
||||
except Exception as exc:
|
||||
log(f"Rendering failed: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
@@ -123,11 +110,11 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
log("Invalid -select value; must be an integer", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
if select_idx < 0 or select_idx >= len(rows):
|
||||
if select_idx < 0 or select_idx >= len(table.rows):
|
||||
log("-select out of range", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
selected = rows[select_idx]
|
||||
selected = table.rows[select_idx]
|
||||
sel_args = provider.selection_args(selected)
|
||||
|
||||
if not run_cmd:
|
||||
|
||||
@@ -40,7 +40,7 @@ from SYS import pipeline as pipeline_context
|
||||
# Playwright & Screenshot Dependencies
|
||||
# ============================================================================
|
||||
|
||||
from tool.playwright import HAS_PLAYWRIGHT, PlaywrightTimeoutError, PlaywrightTool
|
||||
from tool.playwright import PlaywrightTimeoutError, PlaywrightTool
|
||||
|
||||
try:
|
||||
from SYS.config import resolve_output_dir
|
||||
@@ -853,12 +853,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
log(f"Cmdlet: {CMDLET.name}\nSummary: {CMDLET.summary}\nUsage: {CMDLET.usage}")
|
||||
return 0
|
||||
|
||||
if not HAS_PLAYWRIGHT:
|
||||
log(
|
||||
"playwright is required for screenshot capture; install with: pip install playwright; then: playwright install",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
||||
|
||||
progress = PipelineProgress(pipeline_context)
|
||||
|
||||
|
||||
@@ -241,6 +241,16 @@ class search_file(Cmdlet):
|
||||
else:
|
||||
provider_label = provider_text[:1].upper() + provider_text[1:] if provider_text else "Provider"
|
||||
|
||||
normalized_query = str(query or "").strip()
|
||||
provider_filters: Dict[str, Any] = {}
|
||||
try:
|
||||
normalized_query, provider_filters = provider.extract_query_arguments(query)
|
||||
except Exception:
|
||||
provider_filters = {}
|
||||
normalized_query = (normalized_query or "").strip()
|
||||
query = normalized_query or "*"
|
||||
provider_filters = dict(provider_filters or {})
|
||||
|
||||
if provider_lower == "alldebrid" and effective_open_id is not None:
|
||||
table_title = f"{provider_label} Files: {effective_open_id}".strip().rstrip(":")
|
||||
else:
|
||||
@@ -267,17 +277,22 @@ class search_file(Cmdlet):
|
||||
table.set_table_metadata(table_meta)
|
||||
except Exception:
|
||||
pass
|
||||
table.set_source_command("search-file", list(args_list))
|
||||
|
||||
debug(f"[search-file] Calling {provider_name}.search()")
|
||||
if provider_lower == "alldebrid":
|
||||
filters = {"view": "folders"}
|
||||
search_open_id = parsed_open_id if parsed_open_id is not None else open_id
|
||||
if search_open_id is not None:
|
||||
filters = {"view": "files", "magnet_id": search_open_id}
|
||||
results = provider.search(query, limit=limit, filters=filters)
|
||||
if provider_lower == "vimm":
|
||||
# Keep auto-staged download-file from inheriting raw query tokens;
|
||||
# only propagate provider hint so @N expands to a clean downloader call.
|
||||
table.set_source_command("search-file", ["-provider", provider_name])
|
||||
else:
|
||||
results = provider.search(query, limit=limit)
|
||||
table.set_source_command("search-file", list(args_list))
|
||||
|
||||
search_filters = dict(provider_filters)
|
||||
debug(f"[search-file] Calling {provider_name}.search(filters={search_filters})")
|
||||
if provider_lower == "alldebrid":
|
||||
search_open_id = parsed_open_id if parsed_open_id is not None else open_id
|
||||
view_value = "files" if search_open_id is not None else "folders"
|
||||
search_filters["view"] = view_value
|
||||
if search_open_id is not None:
|
||||
search_filters["magnet_id"] = search_open_id
|
||||
results = provider.search(query, limit=limit, filters=search_filters or None)
|
||||
debug(f"[search-file] {provider_name} -> {len(results or [])} result(s)")
|
||||
|
||||
# HIFI artist UX: if there is exactly one artist match, auto-expand
|
||||
@@ -342,6 +357,10 @@ class search_file(Cmdlet):
|
||||
if "table" not in item_dict:
|
||||
item_dict["table"] = table_type
|
||||
|
||||
# Ensure provider source is present so downstream cmdlets (select) can resolve provider
|
||||
if "source" not in item_dict:
|
||||
item_dict["source"] = provider_name
|
||||
|
||||
row_index = len(table.rows)
|
||||
table.add_result(search_result)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from typing import Any, Dict, List, Sequence
|
||||
from . import _shared as sh
|
||||
from SYS.logger import log, debug
|
||||
@@ -89,28 +90,22 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
log("No input provided to select; pipe provider-table output or use a cmdlet that emits items.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
first_src = inputs[0].get("source") if isinstance(inputs[0], dict) else None
|
||||
if not first_src:
|
||||
log("Input items must include 'source' to resolve provider for selection.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
try:
|
||||
provider = get_provider(first_src)
|
||||
except Exception:
|
||||
log(f"Unknown provider: {first_src}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# Model-ize items
|
||||
rows = [_dict_to_result_model(item if isinstance(item, dict) else item) for item in inputs]
|
||||
|
||||
# Attempt to detect provider from first item
|
||||
provider = None
|
||||
first_src = inputs[0].get("source") if isinstance(inputs[0], dict) else None
|
||||
if first_src:
|
||||
try:
|
||||
provider = get_provider(first_src)
|
||||
except Exception:
|
||||
provider = None
|
||||
|
||||
# Columns: ask provider for column spec if available, else build minimal columns
|
||||
if provider:
|
||||
cols = provider.get_columns(rows)
|
||||
else:
|
||||
# Minimal columns built from available keys
|
||||
from SYS.result_table_api import title_column, ext_column
|
||||
|
||||
cols = [title_column()]
|
||||
if any(r.ext for r in rows):
|
||||
cols.append(ext_column())
|
||||
# Columns: provider must supply them (no legacy defaults)
|
||||
cols = provider.get_columns(rows)
|
||||
|
||||
# Render table to console
|
||||
try:
|
||||
@@ -172,26 +167,19 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
"source": raw.source,
|
||||
}
|
||||
else:
|
||||
# try to call to_dict or fallback
|
||||
try:
|
||||
selected = raw.to_dict()
|
||||
except Exception:
|
||||
selected = {"title": getattr(raw, "title", str(raw))}
|
||||
|
||||
# Ensure selection args exist
|
||||
# Ensure selection args exist using provider's selector only
|
||||
if not selected.get("_selection_args"):
|
||||
if provider:
|
||||
try:
|
||||
sel_args = provider.selection_args(rows[idx])
|
||||
selected["_selection_args"] = sel_args
|
||||
except Exception:
|
||||
selected["_selection_args"] = []
|
||||
else:
|
||||
# fallback
|
||||
if selected.get("path"):
|
||||
selected["_selection_args"] = ["-path", selected.get("path")]
|
||||
else:
|
||||
selected["_selection_args"] = ["-title", selected.get("title") or ""]
|
||||
try:
|
||||
sel_args = provider.selection_args(rows[idx])
|
||||
selected["_selection_args"] = sel_args
|
||||
except Exception:
|
||||
log("Selection args missing and provider selector failed.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
selected_items.append(selected)
|
||||
except Exception:
|
||||
|
||||
Reference in New Issue
Block a user