Files
Medios-Macina/ProviderCore/download.py

101 lines
3.1 KiB
Python
Raw Normal View History

2025-12-12 21:55:38 -08:00
from __future__ import annotations
from pathlib import Path
2025-12-22 02:11:53 -08:00
from typing import Callable, Optional
2025-12-20 23:57:44 -08:00
import sys
2025-12-12 21:55:38 -08:00
import requests
2025-12-20 23:57:44 -08:00
from models import ProgressBar
2025-12-12 21:55:38 -08:00
def sanitize_filename(name: str, *, max_len: int = 150) -> str:
text = str(name or "").strip()
if not text:
return "download"
forbidden = set('<>:"/\\|?*')
cleaned = "".join("_" if c in forbidden else c for c in text)
cleaned = " ".join(cleaned.split()).strip().strip(".")
if not cleaned:
cleaned = "download"
return cleaned[:max_len]
2025-12-22 02:11:53 -08:00
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,
2025-12-22 02:11:53 -08:00
) -> bool:
2025-12-12 21:55:38 -08:00
output_path = Path(output_path)
output_path.parent.mkdir(parents=True, exist_ok=True)
s = session or requests.Session()
2025-12-22 02:11:53 -08:00
bar = ProgressBar() if progress_callback is None else None
2025-12-20 23:57:44 -08:00
downloaded = 0
total = None
2025-12-12 21:55:38 -08:00
try:
with s.get(url, stream=True, timeout=timeout_s) as resp:
resp.raise_for_status()
2025-12-20 23:57:44 -08:00
try:
total_val = int(resp.headers.get("content-length") or 0)
total = total_val if total_val > 0 else None
except Exception:
total = None
2025-12-22 02:11:53 -08:00
label = str(output_path.name or "download")
2025-12-20 23:57:44 -08:00
# Render once immediately so fast downloads still show something.
try:
2025-12-22 02:11:53 -08:00
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)
2025-12-20 23:57:44 -08:00
except Exception:
pass
2025-12-12 21:55:38 -08:00
with open(output_path, "wb") as f:
for chunk in resp.iter_content(chunk_size=1024 * 256):
if chunk:
f.write(chunk)
2025-12-20 23:57:44 -08:00
downloaded += len(chunk)
try:
2025-12-22 02:11:53 -08:00
if progress_callback is not None:
progress_callback(downloaded, total, label)
elif bar is not None:
2025-12-29 17:05:03 -08:00
bar.update(
downloaded=downloaded,
total=total,
label=label,
file=sys.stderr
2025-12-29 17:05:03 -08:00
)
2025-12-20 23:57:44 -08:00
except Exception:
pass
try:
2025-12-22 02:11:53 -08:00
if bar is not None:
bar.finish()
2025-12-20 23:57:44 -08:00
except Exception:
pass
2025-12-12 21:55:38 -08:00
return output_path.exists() and output_path.stat().st_size > 0
except Exception:
2025-12-20 23:57:44 -08:00
try:
2025-12-22 02:11:53 -08:00
if bar is not None:
bar.finish()
2025-12-20 23:57:44 -08:00
except Exception:
pass
2025-12-12 21:55:38 -08:00
try:
if output_path.exists():
output_path.unlink()
except Exception:
pass
return False