This commit is contained in:
2026-01-31 23:41:47 -08:00
parent 753cdfb196
commit 95748698fa
15 changed files with 218 additions and 128 deletions

View File

@@ -1229,7 +1229,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_alldebrid_result(
downloaded_path, hash_hint, tmp_dir = Add_File._maybe_download_provider_result(
result,
pipe_obj,
config,
@@ -1257,7 +1257,7 @@ class Add_File(Cmdlet):
return normalized
@staticmethod
def _maybe_download_alldebrid_result(
def _maybe_download_provider_result(
result: Any,
pipe_obj: models.PipeObject,
config: Dict[str, Any],
@@ -1272,19 +1272,27 @@ class Add_File(Cmdlet):
if candidate:
provider_key = candidate
break
if provider_key != "alldebrid":
if not provider_key:
return None, None, None
try:
from Provider.alldebrid import AllDebrid
except Exception:
provider = get_search_provider(provider_key, config)
if provider is None:
return None, None, None
try:
return AllDebrid.download_for_pipe_result(result, pipe_obj, config)
except Exception as exc:
debug(f"[add-file] AllDebrid download helper failed: {exc}")
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}")
return None, None, None
@staticmethod
def _download_provider_source(
@@ -2098,18 +2106,11 @@ class Add_File(Cmdlet):
store = store_instance if store_instance is not None else Store(config)
backend = store[backend_name]
hydrus_like_backend = False
try:
hydrus_like_backend = str(type(backend).__name__ or "").lower().startswith("hydrus")
except Exception:
hydrus_like_backend = False
is_folder_backend = False
try:
is_folder_backend = type(backend).__name__ == "Folder"
except Exception:
is_folder_backend = False
# Use backend properties to drive metadata deferral behavior.
is_remote_backend = getattr(backend, "is_remote", False)
prefer_defer_tags = getattr(backend, "prefer_defer_tags", False)
# ...
# Prepare metadata from pipe_obj and sidecars
tags, url, title, f_hash = Add_File._prepare_metadata(
result, media_path, pipe_obj, config
@@ -2203,9 +2204,9 @@ class Add_File(Cmdlet):
return 1
upload_tags = tags
if hydrus_like_backend and upload_tags:
if prefer_defer_tags and upload_tags:
upload_tags = []
debug("[add-file] Deferring tag application until after Hydrus upload")
debug(f"[add-file] Deferring tag application for {backend_name} (backend preference)")
debug(
f"[add-file] Storing into backend '{backend_name}' path='{media_path}' title='{title}' hash='{f_hash[:12] if f_hash else 'N/A'}'"
@@ -2227,24 +2228,11 @@ class Add_File(Cmdlet):
##log(f"✓ File added to '{backend_name}': {file_identifier}", file=sys.stderr)
stored_path: Optional[str] = None
# IMPORTANT: avoid calling get_file() for remote backends.
# For Hydrus, get_file() returns a browser URL (often with an access key) and should
# only be invoked by explicit user commands (e.g. get-file).
# IMPORTANT: avoid calling get_file() for remote backends by default to avoid
# unintended network activity or credential exposure in result payloads.
try:
if is_folder_backend:
# Avoid extra DB round-trips for Folder; we can derive the stored path.
hash_for_path: Optional[str] = None
if isinstance(file_identifier, str) and len(file_identifier) == 64:
hash_for_path = file_identifier
elif f_hash and isinstance(f_hash, str) and len(f_hash) == 64:
hash_for_path = f_hash
if hash_for_path:
suffix = media_path.suffix if media_path else ""
filename = f"{hash_for_path}{suffix}" if suffix else hash_for_path
location_path = getattr(backend, "_location", None)
if location_path:
stored_path = str(Path(location_path) / filename)
else:
if not is_remote_backend:
# For local backends, resolving the path is cheap and useful.
maybe_path = backend.get_file(file_identifier)
if isinstance(maybe_path, Path):
stored_path = str(maybe_path)
@@ -2275,7 +2263,7 @@ class Add_File(Cmdlet):
# Keep hash/store for downstream commands (get-tag, get-file, etc.).
resolved_hash = chosen_hash
if hydrus_like_backend and tags:
if prefer_defer_tags and tags:
# Support deferring tag application for batching bulk operations
if defer_tag_association and pending_tag_associations is not None:
try:
@@ -2287,11 +2275,11 @@ class Add_File(Cmdlet):
adder = getattr(backend, "add_tag", None)
if callable(adder):
debug(
f"[add-file] Applying {len(tags)} tag(s) post-upload to Hydrus"
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] Hydrus post-upload tagging failed: {exc}", file=sys.stderr)
log(f"[add-file] Post-upload tagging failed for {backend_name}: {exc}", file=sys.stderr)
# If we have url(s), ensure they get associated with the destination file.
# This mirrors `add-url` behavior but avoids emitting extra pipeline noise.

View File

@@ -432,13 +432,7 @@ class Download_File(Cmdlet):
pass
transfer_label = label
table_type = str(table or "").lower()
if table_type == "tidal" or table_type.startswith("tidal."):
try:
progress.begin_transfer(label=transfer_label, total=None)
except Exception:
pass
# If this looks like a provider item and providers are available, prefer provider.download()
downloaded_path: Optional[Path] = None
attempted_provider_download = False
@@ -448,9 +442,16 @@ class Download_File(Cmdlet):
if provider_key and get_search_provider and SearchResult:
# Reuse helper to derive the provider key from table/provider/source hints.
provider_obj = get_search_provider(provider_key, config)
if provider_obj is not None:
attempted_provider_download = True
sr = SearchResult(
if provider_obj is not None and getattr(provider_obj, "prefers_transfer_progress", False):
try:
progress.begin_transfer(label=transfer_label, total=None)
except Exception:
pass
if provider_obj is not None:
attempted_provider_download = True
sr = SearchResult(
table=str(table),
title=str(title or "Unknown"),
path=str(target or ""),
@@ -563,8 +564,7 @@ class Download_File(Cmdlet):
except Exception as e:
log(f"Error downloading item: {e}", file=sys.stderr)
finally:
table_type = str(table or "").lower()
if table_type == "tidal" or table_type.startswith("tidal."):
if provider_obj is not None and getattr(provider_obj, "prefers_transfer_progress", False):
try:
progress.finish_transfer(label=transfer_label)
except Exception:

View File

@@ -30,9 +30,6 @@ from ._shared import (
)
from SYS import pipeline as ctx
STORAGE_ORIGINS = {"local",
"hydrus"}
class _WorkerLogger:
def __init__(self, worker_id: str) -> None:
@@ -94,7 +91,6 @@ class search_file(Cmdlet):
"provider",
type="string",
description="External provider name (e.g., tidal, youtube, soulseek, etc)",
choices=["bandcamp", "libgen", "soulseek", "youtube", "alldebrid", "loc", "internetarchive", "tidal", "tidal"],
),
CmdletArg(
"open",
@@ -142,22 +138,8 @@ class search_file(Cmdlet):
ext = "".join(ch for ch in ext if ch.isalnum())
return ext[:5]
@staticmethod
def _get_tidal_view_from_query(query: str) -> str:
text = str(query or "").strip()
if not text:
return "track"
if re.search(r"\balbum\s*:", text, flags=re.IGNORECASE):
return "album"
if re.search(r"\bartist\s*:", text, flags=re.IGNORECASE):
return "artist"
return "track"
def _ensure_storage_columns(self, payload: Dict[str, Any]) -> Dict[str, Any]:
"""Ensure storage results have the necessary fields for result_table display."""
store_value = str(payload.get("store") or "").lower()
if store_value not in STORAGE_ORIGINS:
return payload
# Ensure we have title field
if "title" not in payload:
@@ -265,78 +247,37 @@ class search_file(Cmdlet):
provider_text = str(provider_name or "").strip()
provider_lower = provider_text.lower()
id_match = re.search(r"\bid\s*[=:]\s*(\d+)", query, flags=re.IGNORECASE)
parsed_open_id = open_id
if id_match and parsed_open_id is None:
try:
parsed_open_id = int(id_match.group(1))
except Exception:
parsed_open_id = None
query = re.sub(r"\bid\s*[=:]\s*\d+", "", query, flags=re.IGNORECASE).strip()
if not query:
query = "*"
effective_open_id = parsed_open_id if parsed_open_id is not None else open_id
if provider_lower == "youtube":
provider_label = "Youtube"
elif provider_lower == "openlibrary":
provider_label = "OpenLibrary"
elif provider_lower == "loc":
provider_label = "LoC"
else:
provider_label = provider_text[:1].upper() + provider_text[1:] if provider_text else "Provider"
# Dynamic query/filter extraction via 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 {})
search_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:
table_title = f"{provider_label}: {query}".strip().rstrip(":")
# Dynamic table generation via provider
table_title = provider.get_table_title(query, search_filters).strip().rstrip(":")
table_type = provider.get_table_type(query, search_filters)
table_meta = provider.get_table_metadata(query, search_filters)
preserve_order = provider.preserve_order
preserve_order = provider_lower in {"youtube", "openlibrary", "loc", "torrent"}
table_type = provider_name
table_meta: Dict[str, Any] = {"provider": provider_name}
if provider_lower == "tidal":
view = self._get_tidal_view_from_query(query)
table_meta["view"] = view
table_type = f"tidal.{view}"
elif provider_lower == "internetarchive":
# Internet Archive search results are effectively folders (items); selecting @N
# should open a list of downloadable files for the chosen item.
table_type = "internetarchive.folder"
table = Table(table_title)._perseverance(preserve_order)
table.set_table(table_type)
if provider_lower == "alldebrid":
table_meta["view"] = "files" if effective_open_id is not None else "folders"
if effective_open_id is not None:
table_meta["magnet_id"] = effective_open_id
try:
table.set_table_metadata(table_meta)
except Exception:
pass
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:
table.set_source_command("search-file", list(args_list))
# Dynamic source command via provider
source_cmd, source_args = provider.get_source_command(args_list)
table.set_source_command(source_cmd, source_args)
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)")