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

1"""download - download LROC image files by product ID, text file, or URL.""" 

2 

3import argparse 

4import sys 

5from pathlib import Path 

6 

7COMMAND = "download" 

8 

9 

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 

23 

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 ) 

67 

68 

69def run(args: argparse.Namespace) -> int: 

70 from ..downloader import PdsDownloader 

71 from ..lroc.nac import NacImageCollection 

72 from ._utils import load_sources 

73 

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 

78 

79 output_dir = Path(args.output_dir) if args.output_dir else Path.cwd() 

80 output_dir.mkdir(parents=True, exist_ok=True) 

81 

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://"))] 

84 

85 ok = fail = 0 

86 

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) 

98 

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) 

105 

106 if ok == 0 and fail == 0: 

107 print("Nothing to download.", file=sys.stderr) 

108 return 1 

109 

110 print(f"Done: {ok} succeeded{f', {fail} failed' if fail else ''}.") 

111 return 0 if fail == 0 else 1