Coverage for lunar_toolkit/cli/download.py: 24%
46 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-19 02:58 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-19 02:58 +0000
1"""download - download LROC image files by product ID, text file, or URL."""
3import argparse
4import sys
5from pathlib import Path
7COMMAND = "download"
10def add_parser(sub: argparse._SubParsersAction) -> None:
11 p = sub.add_parser(
12 COMMAND,
13 help="Download LROC image files to a local directory.",
14 formatter_class=argparse.RawDescriptionHelpFormatter,
15 epilog="""
16Examples
17--------
18 lunar download M1096757719LE M1096757719RE
19 lunar download M1096757719LE --output-dir ./raw
20 lunar download --from-file ids.txt --output-dir ./raw
21 lunar download https://pds.lroc.im-ldi.com/data/.../M1096757719LE.IMG
22 lunar download M1096757719LC --dataset cdr # CDR image
24Files already in --output-dir are skipped (use --force to download them again);
25interrupted downloads resume from their .part file.
26""",
27 )
28 p.add_argument(
29 "sources",
30 nargs="*",
31 metavar="SOURCE",
32 help="Product IDs (e.g. M1096757719LE) or PDS URLs.",
33 )
34 p.add_argument(
35 "--from-file",
36 metavar="FILE",
37 help="Text file of product IDs or URLs, one per line (#comments ignored).",
38 )
39 p.add_argument(
40 "--output-dir",
41 default=None,
42 metavar="DIR",
43 help="Destination directory (default: current working directory).",
44 )
45 p.add_argument(
46 "--dataset",
47 choices=["edr", "cdr"],
48 default="edr",
49 help="Download each image's raw EDR or calibrated CDR file (default: edr); "
50 "IDs are looked up in the index matching their LE/RE or LC/RC suffix.",
51 )
52 p.add_argument("--force", action="store_true", help="Download again even if the file exists.")
53 p.add_argument(
54 "--workers",
55 type=int,
56 default=8,
57 metavar="N",
58 help="Parallel download workers (default: 8).",
59 )
60 p.add_argument(
61 "--data-dir",
62 type=Path,
63 default=None,
64 metavar="DIR",
65 help="Override Parquet data directory.",
66 )
69def run(args: argparse.Namespace) -> int:
70 from ..downloader import PdsDownloader
71 from ..lroc.nac import NacImageCollection
72 from ._utils import load_sources
74 sources = load_sources(args)
75 if not sources:
76 print("No sources provided. Pass product IDs, URLs, or --from-file.", file=sys.stderr)
77 return 1
79 output_dir = Path(args.output_dir) if args.output_dir else Path.cwd()
80 output_dir.mkdir(parents=True, exist_ok=True)
82 raw_urls = [s.strip() for s in sources if s.strip().startswith(("http://", "https://"))]
83 id_sources = [s for s in sources if not s.strip().startswith(("http://", "https://"))]
85 ok = fail = 0
87 if id_sources:
88 collection = NacImageCollection.from_sources(id_sources, data_dir=args.data_dir)
89 if not collection:
90 print("No product IDs found in the index.", file=sys.stderr)
91 else:
92 if args.dataset == "cdr":
93 results = collection.download_cdrs(output_dir, workers=args.workers, force=args.force)
94 else:
95 results = collection.download_edrs(output_dir, workers=args.workers, force=args.force)
96 ok += sum(1 for p in results.values() if p is not None)
97 fail += sum(1 for p in results.values() if p is None)
99 if raw_urls:
100 print(f"Downloading {len(raw_urls)} URL(s) to {output_dir} ...")
101 dl = PdsDownloader(max_workers=args.workers)
102 url_results = dl.download_urls(raw_urls, output_dir, force=args.force)
103 ok += sum(1 for p in url_results.values() if p is not None)
104 fail += sum(1 for p in url_results.values() if p is None)
106 if ok == 0 and fail == 0:
107 print("Nothing to download.", file=sys.stderr)
108 return 1
110 print(f"Done: {ok} succeeded{f', {fail} failed' if fail else ''}.")
111 return 0 if fail == 0 else 1