import argparse import csv import pathlib import re import sys from download_xinje_file import request, quote_url import urllib.request INVALID_FILENAME_CHARS = r'<>:"/\|?*' def clean_filename(value: str) -> str: value = "".join("_" if ch in INVALID_FILENAME_CHARS else ch for ch in value) value = re.sub(r"\s+", " ", value).strip(" .") return value or "xinje-download" def build_filename(row: dict[str, str]) -> str: base = row.get("download_name") or row.get("title") or row["id"] base = clean_filename(base) version = clean_filename(row.get("version") or "") update_date = clean_filename(row.get("update_date") or "") suffix_parts = [] if version: suffix_parts.append(f"v{version}") if update_date: suffix_parts.append(update_date) suffix = f" ({', '.join(suffix_parts)})" if suffix_parts else "" extension = (row.get("file_type") or "pdf").lower().lstrip(".") return f"{base}{suffix}.{extension}" def load_rows(catalog: pathlib.Path) -> dict[str, dict[str, str]]: with catalog.open("r", encoding="utf-8-sig", newline="") as handle: return {row["id"]: row for row in csv.DictReader(handle)} def download(row: dict[str, str], output_dir: pathlib.Path, skip_existing: bool) -> pathlib.Path: output = output_dir / build_filename(row) if output.exists() and skip_existing: print(f"skip existing {row['id']}: {output}") return output url = quote_url(row["url"]) output.parent.mkdir(parents=True, exist_ok=True) with urllib.request.urlopen(request(url), timeout=120) as response: output.write_bytes(response.read()) print(f"downloaded {row['id']}: {output} ({output.stat().st_size} bytes)") return output def main() -> None: parser = argparse.ArgumentParser(description="Download Xinje catalog files by ID.") parser.add_argument("--catalog", required=True, type=pathlib.Path) parser.add_argument("--output-dir", required=True, type=pathlib.Path) parser.add_argument("--ids", required=True, nargs="+") parser.add_argument("--overwrite", action="store_true") args = parser.parse_args() rows = load_rows(args.catalog) missing = [item for item in args.ids if item not in rows] if missing: raise SystemExit(f"catalog IDs not found: {', '.join(missing)}") for item in args.ids: download(rows[item], args.output_dir, skip_existing=not args.overwrite) if __name__ == "__main__": sys.exit(main())