This commit is contained in:
nose
2025-12-22 02:11:53 -08:00
parent d0b821b5dd
commit 16316bb3fd
20 changed files with 4218 additions and 2422 deletions

View File

@@ -1,7 +1,7 @@
from __future__ import annotations
from pathlib import Path
from typing import Optional
from typing import Callable, Optional
import sys
import requests
@@ -22,13 +22,20 @@ def sanitize_filename(name: str, *, max_len: int = 150) -> str:
return cleaned[:max_len]
def download_file(url: str, output_path: Path, *, session: Optional[requests.Session] = None, timeout_s: float = 30.0) -> bool:
def download_file(
url: str,
output_path: Path,
*,
session: Optional[requests.Session] = None,
timeout_s: float = 30.0,
progress_callback: Optional[Callable[[int, Optional[int], str], None]] = None,
) -> bool:
output_path = Path(output_path)
output_path.parent.mkdir(parents=True, exist_ok=True)
s = session or requests.Session()
bar = ProgressBar()
bar = ProgressBar() if progress_callback is None else None
downloaded = 0
total = None
@@ -41,9 +48,14 @@ def download_file(url: str, output_path: Path, *, session: Optional[requests.Ses
except Exception:
total = None
label = str(output_path.name or "download")
# Render once immediately so fast downloads still show something.
try:
bar.update(downloaded=0, total=total, label=str(output_path.name or "download"), file=sys.stderr)
if progress_callback is not None:
progress_callback(0, total, label)
elif bar is not None:
bar.update(downloaded=0, total=total, label=label, file=sys.stderr)
except Exception:
pass
@@ -53,18 +65,23 @@ def download_file(url: str, output_path: Path, *, session: Optional[requests.Ses
f.write(chunk)
downloaded += len(chunk)
try:
bar.update(downloaded=downloaded, total=total, label=str(output_path.name or "download"), file=sys.stderr)
if progress_callback is not None:
progress_callback(downloaded, total, label)
elif bar is not None:
bar.update(downloaded=downloaded, total=total, label=label, file=sys.stderr)
except Exception:
pass
try:
bar.finish()
if bar is not None:
bar.finish()
except Exception:
pass
return output_path.exists() and output_path.stat().st_size > 0
except Exception:
try:
bar.finish()
if bar is not None:
bar.finish()
except Exception:
pass
try:

View File

@@ -6,8 +6,9 @@ This module is the single source of truth for provider discovery.
from __future__ import annotations
from typing import Any, Dict, Optional, Type
from typing import Any, Dict, Optional, Sequence, Type
import sys
from urllib.parse import urlparse
from SYS.logger import log
@@ -141,6 +142,45 @@ def list_file_providers(config: Optional[Dict[str, Any]] = None) -> Dict[str, bo
return availability
def match_provider_name_for_url(url: str) -> Optional[str]:
"""Return a registered provider name that claims the URL's domain.
Providers can declare domains via a class attribute `URL_DOMAINS` (sequence of strings).
This matcher is intentionally cheap (no provider instantiation, no network).
"""
try:
parsed = urlparse(str(url))
host = (parsed.hostname or "").strip().lower()
except Exception:
host = ""
if not host:
return None
for name, provider_class in _PROVIDERS.items():
domains = getattr(provider_class, "URL_DOMAINS", None)
if not isinstance(domains, (list, tuple)):
continue
for d in domains:
dom = str(d or "").strip().lower()
if not dom:
continue
if host == dom or host.endswith("." + dom):
return name
return None
def get_provider_for_url(url: str, config: Optional[Dict[str, Any]] = None) -> Optional[Provider]:
"""Instantiate and return the matching provider for a URL, if any."""
name = match_provider_name_for_url(url)
if not name:
return None
return get_provider(name, config)
__all__ = [
"SearchResult",
"Provider",
@@ -152,5 +192,7 @@ __all__ = [
"list_search_providers",
"get_file_provider",
"list_file_providers",
"match_provider_name_for_url",
"get_provider_for_url",
"download_soulseek_file",
]