huge refactor of the entire codebase, with the goal of improving maintainability, readability, and extensibility. This commit includes changes to almost every file in the project, including:
This commit is contained in:
+18
-21
@@ -199,10 +199,10 @@ class SharedArgs:
|
||||
type="string",
|
||||
description="http parser",
|
||||
)
|
||||
PROVIDER = CmdletArg(
|
||||
name="provider",
|
||||
PLUGIN = CmdletArg(
|
||||
name="plugin",
|
||||
type="string",
|
||||
description="selects provider",
|
||||
description="selects plugin",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -538,10 +538,13 @@ class Cmdlet:
|
||||
elif low in flags.get('tag', set()):
|
||||
# handle tag
|
||||
"""
|
||||
return {
|
||||
arg.name: self.get_flags(arg.name)
|
||||
for arg in self.arg
|
||||
}
|
||||
registry: Dict[str, set[str]] = {}
|
||||
for arg in self.arg:
|
||||
try:
|
||||
registry[arg.name] = {str(flag).lower() for flag in arg.to_flags()}
|
||||
except Exception:
|
||||
registry[arg.name] = {flag.lower() for flag in self.get_flags(arg.name)}
|
||||
return registry
|
||||
|
||||
|
||||
# Tag groups cache (loaded from JSON config file)
|
||||
@@ -642,10 +645,14 @@ def parse_cmdlet_args(args: Sequence[str],
|
||||
else:
|
||||
flagged_args.append(spec)
|
||||
|
||||
# Register all prefix variants for flagged lookup
|
||||
arg_spec_map[canonical_name.lower()] = canonical_name # bare name
|
||||
arg_spec_map[f"-{canonical_name}".lower()] = canonical_name # single dash
|
||||
arg_spec_map[f"--{canonical_name}".lower()] = canonical_name # double dash
|
||||
# Register all supported flag variants, including legacy aliases.
|
||||
arg_spec_map[canonical_name.lower()] = canonical_name # bare canonical name
|
||||
try:
|
||||
for flag in spec.to_flags():
|
||||
arg_spec_map[str(flag).lower()] = canonical_name
|
||||
except Exception:
|
||||
arg_spec_map[f"-{canonical_name}".lower()] = canonical_name
|
||||
arg_spec_map[f"--{canonical_name}".lower()] = canonical_name
|
||||
|
||||
# Parse arguments
|
||||
i = 0
|
||||
@@ -3143,16 +3150,6 @@ def register_url_with_local_library(
|
||||
"""
|
||||
# Folder store removed; local library URL registration is disabled.
|
||||
return False
|
||||
|
||||
|
||||
try:
|
||||
# Provider-specific implementation lives with the provider code.
|
||||
from Provider.tidal_manifest import resolve_tidal_manifest_path
|
||||
except Exception: # pragma: no cover
|
||||
def resolve_tidal_manifest_path(item: Any) -> Optional[str]:
|
||||
_ = item
|
||||
return None
|
||||
|
||||
def check_url_exists_in_storage(
|
||||
urls: Sequence[str],
|
||||
storage: Any,
|
||||
|
||||
+79
-62
@@ -176,14 +176,14 @@ class Add_File(Cmdlet):
|
||||
super().__init__(
|
||||
name="add-file",
|
||||
summary=
|
||||
"Ingest a local media file to a store backend, file provider, or local directory.",
|
||||
"Ingest a local media file to a store backend, upload plugin, or local directory.",
|
||||
usage=
|
||||
"add-file (-path <filepath> | <piped>) (-storage <location> | -provider <fileprovider>) [-delete]",
|
||||
"add-file (-path <filepath> | <piped>) (-storage <location> | -plugin <upload-plugin>) [-delete]",
|
||||
arg=[
|
||||
SharedArgs.PATH,
|
||||
SharedArgs.STORE,
|
||||
SharedArgs.URL,
|
||||
SharedArgs.PROVIDER,
|
||||
SharedArgs.PLUGIN,
|
||||
CmdletArg(
|
||||
name="delete",
|
||||
type="flag",
|
||||
@@ -198,7 +198,7 @@ class Add_File(Cmdlet):
|
||||
" hydrus: Upload to Hydrus database with metadata tagging",
|
||||
" local: Copy file to local directory",
|
||||
" <path>: Copy file to specified directory",
|
||||
"- File provider options (use -provider):",
|
||||
"- Upload plugin options (use -plugin):",
|
||||
" 0x0: Upload to 0x0.st for temporary hosting",
|
||||
" file.io: Upload to file.io for temporary hosting",
|
||||
" internetarchive: Upload to archive.org (optional tag: ia:<identifier> to upload into an existing item)",
|
||||
@@ -224,13 +224,13 @@ class Add_File(Cmdlet):
|
||||
path_arg = parsed.get("path")
|
||||
location = parsed.get("store")
|
||||
source_url_arg = parsed.get("url")
|
||||
provider_name = parsed.get("provider")
|
||||
plugin_name = parsed.get("plugin")
|
||||
delete_after = parsed.get("delete", False)
|
||||
|
||||
# Convenience: when piping a file into add-file, allow `-path <existing dir>`
|
||||
# to act as the destination export directory.
|
||||
# Example: screen-shot "https://..." | add-file -path "C:\Users\Admin\Desktop"
|
||||
if path_arg and not location and not provider_name:
|
||||
if path_arg and not location and not plugin_name:
|
||||
try:
|
||||
candidate_dir = Path(str(path_arg))
|
||||
if candidate_dir.exists() and candidate_dir.is_dir():
|
||||
@@ -263,7 +263,7 @@ class Add_File(Cmdlet):
|
||||
dir_scan_results: Optional[List[Dict[str, Any]]] = None
|
||||
explicit_path_list_results: Optional[List[Dict[str, Any]]] = None
|
||||
|
||||
if path_arg and location and not provider_name:
|
||||
if path_arg and location and not plugin_name:
|
||||
# Support comma-separated path lists: -path "file1,file2,file3"
|
||||
# This is the mechanism used by @N expansion for directory tables.
|
||||
try:
|
||||
@@ -403,7 +403,7 @@ class Add_File(Cmdlet):
|
||||
("result_type", type(result).__name__),
|
||||
("items", total_items),
|
||||
("location", location),
|
||||
("provider", provider_name),
|
||||
("plugin", plugin_name),
|
||||
("delete", delete_after),
|
||||
],
|
||||
border_style="cyan",
|
||||
@@ -599,8 +599,8 @@ class Add_File(Cmdlet):
|
||||
export_destination=(Path(location) if location and not is_storage_backend_location else None),
|
||||
store_instance=storage_registry,
|
||||
)
|
||||
if not media_path and provider_name:
|
||||
media_path, file_hash, temp_dir_to_cleanup = Add_File._download_provider_source(
|
||||
if not media_path and plugin_name:
|
||||
media_path, file_hash, temp_dir_to_cleanup = Add_File._download_piped_source(
|
||||
pipe_obj, config, storage_registry
|
||||
)
|
||||
if media_path:
|
||||
@@ -610,7 +610,7 @@ class Add_File(Cmdlet):
|
||||
[
|
||||
("path", media_path),
|
||||
("hash", file_hash or "N/A"),
|
||||
("provider", provider_name or "local"),
|
||||
("plugin", plugin_name or "local"),
|
||||
],
|
||||
border_style="green",
|
||||
)
|
||||
@@ -635,10 +635,10 @@ class Add_File(Cmdlet):
|
||||
progress.step("hashing file")
|
||||
progress.step("ingesting file")
|
||||
|
||||
if provider_name:
|
||||
code = self._handle_provider_upload(
|
||||
if plugin_name:
|
||||
code = self._handle_plugin_upload(
|
||||
media_path,
|
||||
provider_name,
|
||||
plugin_name,
|
||||
pipe_obj,
|
||||
config,
|
||||
delete_after_item
|
||||
@@ -1365,7 +1365,7 @@ class Add_File(Cmdlet):
|
||||
hash_hint = get_field(result, "hash") or get_field(result, "file_hash") or getattr(pipe_obj, "hash", None)
|
||||
return candidate, hash_hint, None
|
||||
|
||||
downloaded_path, hash_hint, tmp_dir = Add_File._maybe_download_provider_result(
|
||||
downloaded_path, hash_hint, tmp_dir = Add_File._maybe_download_plugin_result(
|
||||
result,
|
||||
pipe_obj,
|
||||
config,
|
||||
@@ -1393,45 +1393,41 @@ class Add_File(Cmdlet):
|
||||
return normalized
|
||||
|
||||
@staticmethod
|
||||
def _maybe_download_provider_result(
|
||||
def _maybe_download_plugin_result(
|
||||
result: Any,
|
||||
pipe_obj: models.PipeObject,
|
||||
config: Dict[str, Any],
|
||||
) -> Tuple[Optional[Path], Optional[str], Optional[Path]]:
|
||||
provider_key = None
|
||||
plugin_key = None
|
||||
for source in (
|
||||
pipe_obj.provider,
|
||||
get_field(result, "plugin"),
|
||||
get_field(result, "provider"),
|
||||
get_field(result, "table"),
|
||||
):
|
||||
candidate = Add_File._normalize_provider_key(source)
|
||||
if candidate:
|
||||
provider_key = candidate
|
||||
plugin_key = candidate
|
||||
break
|
||||
|
||||
if not provider_key:
|
||||
if not plugin_key:
|
||||
return None, None, None
|
||||
|
||||
provider = get_search_provider(provider_key, config)
|
||||
if provider is None:
|
||||
from ProviderCore.registry import get_search_plugin
|
||||
|
||||
plugin = get_search_plugin(plugin_key, config)
|
||||
if plugin is None:
|
||||
return None, None, None
|
||||
|
||||
# Check for specialized download helper (used by AllDebrid and potentially others)
|
||||
handler = getattr(provider, "download_for_pipe_result", None)
|
||||
if not callable(handler):
|
||||
# Fallback: check class if it's a classmethod and instance didn't have it (unlikely but safe)
|
||||
handler = getattr(type(provider), "download_for_pipe_result", None)
|
||||
|
||||
if callable(handler):
|
||||
try:
|
||||
return handler(result, pipe_obj, config)
|
||||
except Exception as exc:
|
||||
debug(f"[add-file] Provider '{provider_key}' download helper failed: {exc}")
|
||||
try:
|
||||
return plugin.resolve_pipe_result_download(result, pipe_obj)
|
||||
except Exception as exc:
|
||||
debug(f"[add-file] Plugin '{plugin_key}' download helper failed: {exc}")
|
||||
|
||||
return None, None, None
|
||||
|
||||
@staticmethod
|
||||
def _download_provider_source(
|
||||
def _download_piped_source(
|
||||
pipe_obj: models.PipeObject,
|
||||
config: Dict[str, Any],
|
||||
store_instance: Optional[Any],
|
||||
@@ -2152,23 +2148,23 @@ class Add_File(Cmdlet):
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def _handle_provider_upload(
|
||||
def _handle_plugin_upload(
|
||||
media_path: Path,
|
||||
provider_name: str,
|
||||
plugin_name: str,
|
||||
pipe_obj: models.PipeObject,
|
||||
config: Dict[str,
|
||||
Any],
|
||||
delete_after: bool,
|
||||
) -> int:
|
||||
"""Handle uploading to a file provider (e.g. 0x0)."""
|
||||
from ProviderCore.registry import get_file_provider
|
||||
"""Handle uploading via an upload plugin (e.g. 0x0)."""
|
||||
from ProviderCore.registry import get_upload_plugin
|
||||
|
||||
log(f"Uploading via {provider_name}: {media_path.name}", file=sys.stderr)
|
||||
log(f"Uploading via {plugin_name}: {media_path.name}", file=sys.stderr)
|
||||
|
||||
try:
|
||||
file_provider = get_file_provider(provider_name, config)
|
||||
file_provider = get_upload_plugin(plugin_name, config)
|
||||
if not file_provider:
|
||||
log(f"File provider '{provider_name}' not available", file=sys.stderr)
|
||||
log(f"Upload plugin '{plugin_name}' not available", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
hoster_url = file_provider.upload(str(media_path), pipe_obj=pipe_obj)
|
||||
@@ -2183,8 +2179,8 @@ class Add_File(Cmdlet):
|
||||
# Update PipeObject and emit
|
||||
extra_updates: Dict[str,
|
||||
Any] = {
|
||||
"provider": provider_name,
|
||||
"provider_url": hoster_url,
|
||||
"plugin": plugin_name,
|
||||
"plugin_url": hoster_url,
|
||||
}
|
||||
if isinstance(pipe_obj.extra, dict):
|
||||
# Also track hoster URL as a url for downstream steps
|
||||
@@ -2197,7 +2193,7 @@ class Add_File(Cmdlet):
|
||||
Add_File._update_pipe_object_destination(
|
||||
pipe_obj,
|
||||
hash_value=f_hash or "unknown",
|
||||
store=provider_name or "provider",
|
||||
store=plugin_name or "plugin",
|
||||
path=file_path,
|
||||
tag=pipe_obj.tag,
|
||||
title=pipe_obj.title or (media_path.name if media_path else None),
|
||||
@@ -2445,9 +2441,6 @@ class Add_File(Cmdlet):
|
||||
try:
|
||||
adder = getattr(backend, "add_tag", None)
|
||||
if callable(adder):
|
||||
debug(
|
||||
f"[add-file] Applying {len(tags)} tag(s) post-upload to {backend_name}"
|
||||
)
|
||||
adder(resolved_hash, list(tags))
|
||||
except Exception as exc:
|
||||
log(f"[add-file] Post-upload tagging failed for {backend_name}: {exc}", file=sys.stderr)
|
||||
@@ -2479,48 +2472,72 @@ class Add_File(Cmdlet):
|
||||
try:
|
||||
setter = getattr(backend, "set_note", None)
|
||||
if callable(setter):
|
||||
debug(
|
||||
f"[add-file] Writing sub note (len={len(str(sub_note))}) to {backend_name}:{resolved_hash}"
|
||||
)
|
||||
setter(resolved_hash, "sub", sub_note)
|
||||
except Exception as exc:
|
||||
debug(f"[add-file] sub note write failed: {exc}")
|
||||
debug_panel(
|
||||
"add-file note write failed",
|
||||
[
|
||||
("store", backend_name),
|
||||
("hash", resolved_hash),
|
||||
("note", "sub"),
|
||||
("error", exc),
|
||||
],
|
||||
border_style="yellow",
|
||||
)
|
||||
|
||||
lyric_note = Add_File._get_note_text(result, pipe_obj, "lyric")
|
||||
if lyric_note:
|
||||
try:
|
||||
setter = getattr(backend, "set_note", None)
|
||||
if callable(setter):
|
||||
debug(
|
||||
f"[add-file] Writing lyric note (len={len(str(lyric_note))}) to {backend_name}:{resolved_hash}"
|
||||
)
|
||||
setter(resolved_hash, "lyric", lyric_note)
|
||||
except Exception as exc:
|
||||
debug(f"[add-file] lyric note write failed: {exc}")
|
||||
debug_panel(
|
||||
"add-file note write failed",
|
||||
[
|
||||
("store", backend_name),
|
||||
("hash", resolved_hash),
|
||||
("note", "lyric"),
|
||||
("error", exc),
|
||||
],
|
||||
border_style="yellow",
|
||||
)
|
||||
|
||||
chapters_note = Add_File._get_note_text(result, pipe_obj, "chapters")
|
||||
if chapters_note:
|
||||
try:
|
||||
setter = getattr(backend, "set_note", None)
|
||||
if callable(setter):
|
||||
debug(
|
||||
f"[add-file] Writing chapters note (len={len(str(chapters_note))}) to {backend_name}:{resolved_hash}"
|
||||
)
|
||||
setter(resolved_hash, "chapters", chapters_note)
|
||||
except Exception as exc:
|
||||
debug(f"[add-file] chapters note write failed: {exc}")
|
||||
debug_panel(
|
||||
"add-file note write failed",
|
||||
[
|
||||
("store", backend_name),
|
||||
("hash", resolved_hash),
|
||||
("note", "chapters"),
|
||||
("error", exc),
|
||||
],
|
||||
border_style="yellow",
|
||||
)
|
||||
|
||||
caption_note = Add_File._get_note_text(result, pipe_obj, "caption")
|
||||
if caption_note:
|
||||
try:
|
||||
setter = getattr(backend, "set_note", None)
|
||||
if callable(setter):
|
||||
debug(
|
||||
f"[add-file] Writing caption note (len={len(str(caption_note))}) to {backend_name}:{resolved_hash}"
|
||||
)
|
||||
setter(resolved_hash, "caption", caption_note)
|
||||
except Exception as exc:
|
||||
debug(f"[add-file] caption note write failed: {exc}")
|
||||
debug_panel(
|
||||
"add-file note write failed",
|
||||
[
|
||||
("store", backend_name),
|
||||
("hash", resolved_hash),
|
||||
("note", "caption"),
|
||||
("error", exc),
|
||||
],
|
||||
border_style="yellow",
|
||||
)
|
||||
|
||||
meta: Dict[str,
|
||||
Any] = {}
|
||||
|
||||
+205
-2023
File diff suppressed because it is too large
Load Diff
+141
-869
File diff suppressed because it is too large
Load Diff
+13
-47
@@ -154,47 +154,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
|
||||
if urls_to_download and len(urls_to_download) >= 2:
|
||||
try:
|
||||
# Compute a batch hint (audio vs video + single-format id) once.
|
||||
mode_hint: Optional[str] = None
|
||||
forced_format: Optional[str] = None
|
||||
try:
|
||||
from tool.ytdlp import YtDlpTool, list_formats
|
||||
|
||||
sample_url = urls_to_download[0]
|
||||
cookiefile = None
|
||||
try:
|
||||
cookie_path = YtDlpTool(config).resolve_cookiefile()
|
||||
if cookie_path is not None and cookie_path.is_file():
|
||||
cookiefile = str(cookie_path)
|
||||
except Exception:
|
||||
cookiefile = None
|
||||
|
||||
fmts = list_formats(
|
||||
sample_url,
|
||||
no_playlist=False,
|
||||
playlist_items=None,
|
||||
cookiefile=cookiefile
|
||||
)
|
||||
if isinstance(fmts, list) and fmts:
|
||||
has_video = False
|
||||
for f in fmts:
|
||||
if not isinstance(f, dict):
|
||||
continue
|
||||
vcodec = str(f.get("vcodec", "none") or "none").strip().lower()
|
||||
if vcodec and vcodec != "none":
|
||||
has_video = True
|
||||
break
|
||||
mode_hint = "video" if has_video else "audio"
|
||||
|
||||
if len(fmts) == 1 and isinstance(fmts[0], dict):
|
||||
fid = str(fmts[0].get("format_id") or "").strip()
|
||||
if fid:
|
||||
forced_format = fid
|
||||
except Exception:
|
||||
mode_hint = None
|
||||
forced_format = None
|
||||
|
||||
from cmdlet.download_file import Download_File
|
||||
from ProviderCore.registry import get_plugin_for_url
|
||||
|
||||
expanded: List[Dict[str, Any]] = []
|
||||
downloaded_any = False
|
||||
@@ -207,12 +167,18 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
expanded.append(it)
|
||||
continue
|
||||
|
||||
downloaded = Download_File.download_streaming_url_as_pipe_objects(
|
||||
u,
|
||||
config,
|
||||
mode_hint=mode_hint,
|
||||
ytdl_format_hint=forced_format,
|
||||
)
|
||||
downloaded = []
|
||||
try:
|
||||
plugin = get_plugin_for_url(u, config)
|
||||
except Exception:
|
||||
plugin = None
|
||||
if plugin is not None and hasattr(plugin, "download_url_as_pipe_objects"):
|
||||
try:
|
||||
downloaded = plugin.download_url_as_pipe_objects(u)
|
||||
except TypeError:
|
||||
downloaded = plugin.download_url_as_pipe_objects(u, output_dir=None)
|
||||
except Exception:
|
||||
downloaded = []
|
||||
if downloaded:
|
||||
expanded.extend(downloaded)
|
||||
downloaded_any = True
|
||||
|
||||
+15
-15
@@ -7,7 +7,7 @@ from . import _shared as sh
|
||||
from SYS.logger import log
|
||||
from SYS import pipeline as ctx
|
||||
|
||||
from SYS.result_table_adapters import get_provider
|
||||
from SYS.result_table_adapters import get_plugin
|
||||
from SYS.result_table_renderers import RichRenderer
|
||||
|
||||
Cmdlet = sh.Cmdlet
|
||||
@@ -16,19 +16,19 @@ parse_cmdlet_args = sh.parse_cmdlet_args
|
||||
|
||||
|
||||
CMDLET = Cmdlet(
|
||||
name="provider-table",
|
||||
summary="Render a provider's result set and optionally run a follow-up cmdlet using the selected row.",
|
||||
usage="provider-table -provider <name> [-sample] [-select <n>] [-run-cmd <name>]",
|
||||
name="plugin-table",
|
||||
summary="Render a plugin's result set and optionally run a follow-up cmdlet using the selected row.",
|
||||
usage="plugin-table -plugin <name> [-sample] [-select <n>] [-run-cmd <name>]",
|
||||
arg=[
|
||||
CmdletArg("provider", type="string", description="Provider name to render (default: example)"),
|
||||
CmdletArg("sample", type="flag", description="Use provider sample/demo items when available."),
|
||||
CmdletArg("plugin", type="string", description="Plugin name to render (default: example)"),
|
||||
CmdletArg("sample", type="flag", description="Use plugin sample/demo items when available."),
|
||||
CmdletArg("select", type="int", description="1-based row index to select and use for follow-up command."),
|
||||
CmdletArg("run-cmd", type="string", description="Cmdlet to invoke with the selected row's selector args."),
|
||||
],
|
||||
detail=[
|
||||
"Use a registered provider to build a table and optionally run another cmdlet with selection args.",
|
||||
"Use a registered plugin to build a table and optionally run another cmdlet with selection args.",
|
||||
"Emits pipeline-friendly dicts enriched with `_selection_args` so you can use @N syntax to select and chain.",
|
||||
"Example: provider-table -provider example -sample | @1 | add-file -store my_store",
|
||||
"Example: plugin-table -plugin example -sample | @1 | add-file -store my_store",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -36,15 +36,15 @@ CMDLET = Cmdlet(
|
||||
def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
parsed = parse_cmdlet_args(args, CMDLET)
|
||||
|
||||
provider_name = parsed.get("provider") or "example"
|
||||
plugin_name = parsed.get("plugin") or "example"
|
||||
use_sample = bool(parsed.get("sample", False))
|
||||
run_cmd = parsed.get("run-cmd")
|
||||
select_raw = parsed.get("select")
|
||||
|
||||
try:
|
||||
provider = get_provider(provider_name)
|
||||
provider = get_plugin(plugin_name)
|
||||
except Exception:
|
||||
log(f"Unknown provider: {provider_name}", file=sys.stderr)
|
||||
log(f"Unknown plugin: {plugin_name}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# Obtain items to feed to the adapter
|
||||
@@ -55,23 +55,23 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
mod = __import__(provider.adapter.__module__, fromlist=["*"])
|
||||
items = getattr(mod, "SAMPLE_ITEMS", None)
|
||||
if items is None:
|
||||
log("Provider does not expose SAMPLE_ITEMS; no sample available", file=sys.stderr)
|
||||
log("Plugin does not expose SAMPLE_ITEMS; no sample available", file=sys.stderr)
|
||||
return 1
|
||||
except Exception:
|
||||
log("Failed to load provider sample", file=sys.stderr)
|
||||
log("Failed to load plugin sample", file=sys.stderr)
|
||||
return 1
|
||||
else:
|
||||
# Require input for non-sample runs
|
||||
inputs = list(result) if isinstance(result, Iterable) else []
|
||||
if not inputs:
|
||||
log("No input provided. Use -sample for demo or pipe provider items in.", file=sys.stderr)
|
||||
log("No input provided. Use -sample for demo or pipe plugin items in.", file=sys.stderr)
|
||||
return 1
|
||||
items = inputs
|
||||
|
||||
try:
|
||||
table = provider.build_table(items)
|
||||
except Exception as exc:
|
||||
log(f"Provider '{provider.name}' failed: {exc}", file=sys.stderr)
|
||||
log(f"Plugin '{provider.name}' failed: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# Emit rows for downstream pipeline consumption (pipable behavior).
|
||||
|
||||
+31
-85
@@ -15,7 +15,7 @@ from urllib.parse import urlparse, parse_qs, unquote, urljoin
|
||||
|
||||
from SYS.logger import log, debug
|
||||
from SYS.payload_builders import build_file_result_payload, normalize_file_extension
|
||||
from ProviderCore.registry import get_search_provider, list_search_providers
|
||||
from ProviderCore.registry import get_search_plugin, list_search_plugins
|
||||
from SYS.rich_display import (
|
||||
show_provider_config_panel,
|
||||
show_store_config_panel,
|
||||
@@ -169,8 +169,8 @@ class search_file(Cmdlet):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
name="search-file",
|
||||
summary="Search storage backends (Hydrus) or external providers (via -provider).",
|
||||
usage="search-file [-query <query>] [-store BACKEND] [-limit N] [-provider NAME]",
|
||||
summary="Search storage backends (Hydrus) or external plugins (via -plugin).",
|
||||
usage="search-file [-query <query>] [-store BACKEND] [-limit N] [-plugin NAME]",
|
||||
arg=[
|
||||
CmdletArg(
|
||||
"limit",
|
||||
@@ -179,11 +179,7 @@ class search_file(Cmdlet):
|
||||
),
|
||||
SharedArgs.STORE,
|
||||
SharedArgs.QUERY,
|
||||
CmdletArg(
|
||||
"provider",
|
||||
type="string",
|
||||
description="External provider name (e.g., tidal, youtube, soulseek, etc)",
|
||||
),
|
||||
SharedArgs.PLUGIN,
|
||||
CmdletArg(
|
||||
"open",
|
||||
type="integer",
|
||||
@@ -209,10 +205,10 @@ class search_file(Cmdlet):
|
||||
"search-file 'example.com/path' -query 'ext:pdf' # Web: site:example.com filetype:pdf",
|
||||
"search-file -query 'site:example.com filetype:epub history' # Web: site-scoped search",
|
||||
"",
|
||||
"Provider search (-provider):",
|
||||
"search-file -provider youtube 'tutorial' # Search YouTube provider",
|
||||
"search-file -provider alldebrid '*' # List AllDebrid magnets",
|
||||
"search-file -provider alldebrid -open 123 '*' # Show files for a magnet",
|
||||
"Plugin search (-plugin):",
|
||||
"search-file -plugin youtube 'tutorial' # Search YouTube plugin",
|
||||
"search-file -plugin alldebrid '*' # List AllDebrid magnets",
|
||||
"search-file -plugin alldebrid -open 123 '*' # Show files for a magnet",
|
||||
],
|
||||
exec=self.run,
|
||||
)
|
||||
@@ -1451,10 +1447,10 @@ class search_file(Cmdlet):
|
||||
self._set_storage_display_columns(payload)
|
||||
return payload
|
||||
|
||||
def _run_provider_search(
|
||||
def _run_plugin_search(
|
||||
self,
|
||||
*,
|
||||
provider_name: str,
|
||||
plugin_name: str,
|
||||
query: str,
|
||||
limit: int,
|
||||
limit_set: bool,
|
||||
@@ -1463,9 +1459,9 @@ class search_file(Cmdlet):
|
||||
refresh_mode: bool,
|
||||
config: Dict[str, Any],
|
||||
) -> int:
|
||||
"""Execute external provider search."""
|
||||
"""Execute external plugin search."""
|
||||
|
||||
if not provider_name or not query:
|
||||
if not plugin_name or not query:
|
||||
from SYS import pipeline as ctx_mod
|
||||
progress = None
|
||||
if hasattr(ctx_mod, "get_pipeline_state"):
|
||||
@@ -1476,10 +1472,10 @@ class search_file(Cmdlet):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
log("Error: search-file -provider requires both provider and query", file=sys.stderr)
|
||||
log("Error: search-file -plugin requires both plugin and query", file=sys.stderr)
|
||||
log(f"Usage: {self.usage}", file=sys.stderr)
|
||||
|
||||
providers_map = list_search_providers(config)
|
||||
providers_map = list_search_plugins(config)
|
||||
available = [n for n, a in providers_map.items() if a]
|
||||
unconfigured = [n for n, a in providers_map.items() if not a]
|
||||
|
||||
@@ -1500,7 +1496,7 @@ class search_file(Cmdlet):
|
||||
if hasattr(ctx_mod, "get_pipeline_state"):
|
||||
progress = ctx_mod.get_pipeline_state().live_progress
|
||||
|
||||
provider = get_search_provider(provider_name, config)
|
||||
provider = get_search_plugin(plugin_name, config)
|
||||
if not provider:
|
||||
if progress:
|
||||
try:
|
||||
@@ -1508,9 +1504,9 @@ class search_file(Cmdlet):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
show_provider_config_panel([provider_name])
|
||||
show_provider_config_panel([plugin_name])
|
||||
|
||||
providers_map = list_search_providers(config)
|
||||
providers_map = list_search_plugins(config)
|
||||
available = [n for n, a in providers_map.items() if a]
|
||||
if available:
|
||||
show_available_providers_panel(available)
|
||||
@@ -1522,7 +1518,7 @@ class search_file(Cmdlet):
|
||||
worker_id,
|
||||
"search-file",
|
||||
title=f"Search: {query}",
|
||||
description=f"Provider: {provider_name}, Query: {query}",
|
||||
description=f"Plugin: {plugin_name}, Query: {query}",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
@@ -1532,7 +1528,7 @@ class search_file(Cmdlet):
|
||||
|
||||
from SYS.result_table import Table
|
||||
|
||||
provider_text = str(provider_name or "").strip()
|
||||
provider_text = str(plugin_name or "").strip()
|
||||
provider_lower = provider_text.lower()
|
||||
|
||||
# Dynamic query/filter extraction via provider
|
||||
@@ -1564,9 +1560,9 @@ 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 {provider_name}.search(filters={search_filters})")
|
||||
debug(f"[search-file] Calling {plugin_name}.search(filters={search_filters})")
|
||||
results = provider.search(query, limit=limit, filters=search_filters or None)
|
||||
debug(f"[search-file] {provider_name} -> {len(results or [])} result(s)")
|
||||
debug(f"[search-file] {plugin_name} -> {len(results or [])} result(s)")
|
||||
|
||||
# Allow providers to apply provider-specific UX transforms (e.g. auto-expansion)
|
||||
try:
|
||||
@@ -1615,7 +1611,7 @@ class search_file(Cmdlet):
|
||||
|
||||
# Ensure provider source is present so downstream cmdlets (select) can resolve provider
|
||||
if "source" not in item_dict:
|
||||
item_dict["source"] = provider_name
|
||||
item_dict["source"] = plugin_name
|
||||
|
||||
row_index = len(table.rows)
|
||||
table.add_result(search_result)
|
||||
@@ -1636,7 +1632,7 @@ class search_file(Cmdlet):
|
||||
return 0
|
||||
|
||||
except Exception as exc:
|
||||
log(f"Error searching provider '{provider_name}': {exc}", file=sys.stderr)
|
||||
log(f"Error searching plugin '{plugin_name}': {exc}", file=sys.stderr)
|
||||
import traceback
|
||||
|
||||
debug(traceback.format_exc())
|
||||
@@ -1728,9 +1724,9 @@ class search_file(Cmdlet):
|
||||
f.lower()
|
||||
for f in (flag_registry.get("limit") or {"-limit", "--limit"})
|
||||
}
|
||||
provider_flags = {
|
||||
plugin_flags = {
|
||||
f.lower()
|
||||
for f in (flag_registry.get("provider") or {"-provider", "--provider"})
|
||||
for f in (flag_registry.get("plugin") or {"-plugin", "--plugin"})
|
||||
}
|
||||
open_flags = {
|
||||
f.lower()
|
||||
@@ -1740,7 +1736,7 @@ class search_file(Cmdlet):
|
||||
# Parse arguments
|
||||
query = ""
|
||||
storage_backend: Optional[str] = None
|
||||
provider_name: Optional[str] = None
|
||||
plugin_name: Optional[str] = None
|
||||
open_id: Optional[int] = None
|
||||
limit = 100
|
||||
limit_set = False
|
||||
@@ -1756,8 +1752,8 @@ class search_file(Cmdlet):
|
||||
query = f"{query} {chunk}".strip() if query else chunk
|
||||
i += 2
|
||||
continue
|
||||
if low in provider_flags and i + 1 < len(args_list):
|
||||
provider_name = args_list[i + 1]
|
||||
if low in plugin_flags and i + 1 < len(args_list):
|
||||
plugin_name = args_list[i + 1]
|
||||
i += 2
|
||||
continue
|
||||
if low in open_flags and i + 1 < len(args_list):
|
||||
@@ -1790,9 +1786,9 @@ class search_file(Cmdlet):
|
||||
|
||||
query = query.strip()
|
||||
|
||||
if provider_name:
|
||||
return self._run_provider_search(
|
||||
provider_name=provider_name,
|
||||
if plugin_name:
|
||||
return self._run_plugin_search(
|
||||
plugin_name=plugin_name,
|
||||
query=query,
|
||||
limit=limit,
|
||||
limit_set=limit_set,
|
||||
@@ -1814,56 +1810,6 @@ class search_file(Cmdlet):
|
||||
if store_filter and not storage_backend:
|
||||
storage_backend = store_filter
|
||||
|
||||
# If the user accidentally used `-store <provider>` or `store:<provider>`,
|
||||
# prefer to treat it as a provider search (providers like 'alldebrid' are not store backends).
|
||||
try:
|
||||
from Store.registry import list_configured_backend_names
|
||||
providers_map = list_search_providers(config)
|
||||
configured = list_configured_backend_names(config or {})
|
||||
if storage_backend:
|
||||
matched = None
|
||||
storage_hint = self._normalize_lookup_target(storage_backend)
|
||||
if storage_hint:
|
||||
for p in (providers_map or {}):
|
||||
if self._normalize_lookup_target(p) == storage_hint:
|
||||
matched = p
|
||||
break
|
||||
if matched and str(storage_backend) not in configured:
|
||||
log(f"Note: Treating '-store {storage_backend}' as provider search for '{matched}'", file=sys.stderr)
|
||||
return self._run_provider_search(
|
||||
provider_name=matched,
|
||||
query=query,
|
||||
limit=limit,
|
||||
limit_set=limit_set,
|
||||
open_id=open_id,
|
||||
args_list=args_list,
|
||||
refresh_mode=refresh_mode,
|
||||
config=config,
|
||||
)
|
||||
elif store_filter:
|
||||
matched = None
|
||||
store_hint = self._normalize_lookup_target(store_filter)
|
||||
if store_hint:
|
||||
for p in (providers_map or {}):
|
||||
if self._normalize_lookup_target(p) == store_hint:
|
||||
matched = p
|
||||
break
|
||||
if matched and str(store_filter) not in configured:
|
||||
log(f"Note: Treating 'store:{store_filter}' as provider search for '{matched}'", file=sys.stderr)
|
||||
return self._run_provider_search(
|
||||
provider_name=matched,
|
||||
query=query,
|
||||
limit=limit,
|
||||
limit_set=limit_set,
|
||||
open_id=open_id,
|
||||
args_list=args_list,
|
||||
refresh_mode=refresh_mode,
|
||||
config=config,
|
||||
)
|
||||
except Exception:
|
||||
# Be conservative: if provider detection fails, fall back to store behaviour
|
||||
pass
|
||||
|
||||
hash_query = parse_hash_query(query)
|
||||
|
||||
web_plan = self._build_web_search_plan(
|
||||
|
||||
Reference in New Issue
Block a user