Skip to content

Commit 1cb57d0

Browse files
committed
Sync settings and CLI arguments
Most CLI arguments can be now saved into the configuration profile JSON. This also cleans up passing around some, but not all args, into various classes and methods, instead of just passing all of settings.
1 parent 5a9cbf6 commit 1cb57d0

File tree

3 files changed

+62
-38
lines changed

3 files changed

+62
-38
lines changed

itch_dl/cli.py

+25-24
Original file line numberDiff line numberDiff line change
@@ -17,50 +17,52 @@ def parse_args() -> argparse.Namespace:
1717
parser = argparse.ArgumentParser(description="Bulk download stuff from Itch.io.")
1818
parser.add_argument("url_or_path",
1919
help="itch.io URL or path to a game jam entries.json file")
20-
parser.add_argument("--api-key", metavar="key", default=None,
21-
help="itch.io API key - https://itch.io/user/settings/api-keys")
2220
parser.add_argument("--profile", metavar="profile", default=None,
2321
help="configuration profile to load")
22+
23+
# These args must match config.py -> Settings class. Make sure all defaults here
24+
# evaluate to False, or apply_args_on_settings will override profile settings.
25+
parser.add_argument("--api-key", metavar="key", default=None,
26+
help="itch.io API key - https://itch.io/user/settings/api-keys")
27+
parser.add_argument("--user-agent", metavar="agent", default=None,
28+
help="user agent to use when sending HTTP requests")
29+
parser.add_argument("--download-to", metavar="path", default=None,
30+
help="directory to save results into (default: current working dir)")
31+
parser.add_argument("--mirror-web", action="store_true",
32+
help="try to fetch assets on game sites")
2433
parser.add_argument("--urls-only", action="store_true",
2534
help="print scraped game URLs without downloading them")
26-
parser.add_argument("--download-to", metavar="path",
27-
help="directory to save results into (default: current dir)")
28-
parser.add_argument("--parallel", metavar="parallel", type=int, default=1,
35+
parser.add_argument("--parallel", metavar="parallel", type=int, default=None,
2936
help="how many threads to use for downloading games (default: 1)")
30-
parser.add_argument("--mirror-web", action="store_true",
31-
help="try to fetch assets on game sites")
3237
parser.add_argument("--filter-files-glob", metavar="glob", default=None,
3338
help="filter downloaded files with a shell-style glob/fnmatch (unmatched files are skipped)")
3439
parser.add_argument("--filter-files-regex", metavar="regex", default=None,
3540
help="filter downloaded files with a Python regex (unmatched files are skipped)")
3641
parser.add_argument("--verbose", action="store_true",
3742
help="print verbose logs")
43+
3844
return parser.parse_args()
3945
# fmt: on
4046

4147

42-
def apply_args_on_settings(args: argparse.Namespace, settings: Settings):
43-
"""Apply settings overrides from provided command line arguments, if set."""
44-
for key in ("api_key", "filter_files_glob", "filter_files_regex"):
45-
value = getattr(args, key)
46-
if value:
47-
setattr(settings, key, value)
48-
49-
5048
def run() -> int:
5149
args = parse_args()
5250
if args.verbose:
5351
logging.getLogger().setLevel(logging.DEBUG)
5452

55-
settings = load_config(profile=args.profile)
56-
apply_args_on_settings(args, settings)
53+
settings: Settings = load_config(args, profile=args.profile)
54+
if settings.verbose:
55+
logging.getLogger().setLevel(logging.DEBUG)
5756

5857
if not settings.api_key:
5958
exit(
6059
"You did not provide an API key which itch-dl requires.\n"
6160
"See https://github.com/DragoonAethis/itch-dl/wiki/API-Keys for more info."
6261
)
6362

63+
url_or_path = args.url_or_path
64+
del args # Do not use `args` beyond this point.
65+
6466
# Check API key validity:
6567
client = ItchApiClient(settings.api_key, settings.user_agent)
6668
profile_req = client.get("/profile")
@@ -70,25 +72,24 @@ def run() -> int:
7072
"See https://github.com/DragoonAethis/itch-dl/wiki/API-Keys for more info."
7173
)
7274

73-
jobs = get_jobs_for_url_or_path(args.url_or_path, settings)
75+
jobs = get_jobs_for_url_or_path(url_or_path, settings)
7476
jobs = list(set(jobs)) # Deduplicate, just in case...
7577
logging.info(f"Found {len(jobs)} URL(s).")
7678

7779
if len(jobs) == 0:
7880
exit("No URLs to download.")
7981

80-
if args.urls_only:
82+
if settings.urls_only:
8183
for job in jobs:
8284
print(job)
8385

8486
return 0
8587

86-
download_to = os.getcwd()
87-
if args.download_to is not None:
88-
download_to = os.path.normpath(args.download_to)
89-
os.makedirs(download_to, exist_ok=True)
88+
# If the download dir is not set, use the current working dir:
89+
settings.download_to = os.path.normpath(settings.download_to or os.getcwd())
90+
os.makedirs(settings.download_to, exist_ok=True)
9091

9192
# Grab all the download keys (there's no way to fetch them per title...):
9293
keys = get_download_keys(client)
9394

94-
return drive_downloads(jobs, download_to, args.mirror_web, settings, keys, parallel=args.parallel)
95+
return drive_downloads(jobs, settings, keys)

itch_dl/config.py

+31-2
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,26 @@
22
import json
33
import logging
44
import platform
5+
import argparse
56
from typing import Optional
67

78
import requests
89
from pydantic import BaseModel
910

1011
from . import __version__
1112

13+
OVERRIDABLE_SETTINGS = (
14+
"api_key",
15+
"user_agent",
16+
"download_to",
17+
"mirror_web",
18+
"urls_only",
19+
"parallel",
20+
"filter_files_glob",
21+
"filter_files_regex",
22+
"verbose",
23+
)
24+
1225

1326
class Settings(BaseModel):
1427
"""Available settings for itch-dl. Make sure all of them
@@ -17,9 +30,16 @@ class Settings(BaseModel):
1730
api_key: Optional[str] = None
1831
user_agent: str = f"python-requests/{requests.__version__} itch-dl/{__version__}"
1932

33+
download_to: Optional[str] = None
34+
mirror_web: bool = False
35+
urls_only: bool = False
36+
parallel: int = 1
37+
2038
filter_files_glob: Optional[str] = None
2139
filter_files_regex: Optional[str] = None
2240

41+
verbose: bool = False
42+
2343

2444
def create_and_get_config_path() -> str:
2545
"""Returns the configuration directory in the appropriate
@@ -37,7 +57,7 @@ def create_and_get_config_path() -> str:
3757
return os.path.join(base_path, "itch-dl")
3858

3959

40-
def load_config(profile: Optional[str] = None) -> Settings:
60+
def load_config(args: argparse.Namespace, profile: Optional[str] = None) -> Settings:
4161
"""Loads the configuration from the file system if it exists,
4262
the returns a Settings object."""
4363
config_path = create_and_get_config_path()
@@ -58,4 +78,13 @@ def load_config(profile: Optional[str] = None) -> Settings:
5878

5979
config_data.update(profile_data)
6080

61-
return Settings(**config_data)
81+
# All settings from the base file:
82+
settings = Settings(**config_data)
83+
84+
# Apply overrides from CLI args:
85+
for key in OVERRIDABLE_SETTINGS:
86+
value = getattr(args, key)
87+
if value:
88+
setattr(settings, key, value)
89+
90+
return settings

itch_dl/downloader.py

+6-12
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,7 @@ class GameMetadata(TypedDict, total=False):
6262

6363

6464
class GameDownloader:
65-
def __init__(self, download_to: str, mirror_web: bool, settings: Settings, keys: Dict[int, str]):
66-
self.download_to = download_to
67-
self.mirror_web = mirror_web
68-
65+
def __init__(self, settings: Settings, keys: Dict[int, str]):
6966
self.settings = settings
7067
self.download_keys = keys
7168
self.client = ItchApiClient(settings.api_key, settings.user_agent)
@@ -258,7 +255,7 @@ def download(self, url: str, skip_downloaded: bool = True):
258255

259256
author, game = match["author"], match["game"]
260257

261-
download_path = os.path.join(self.download_to, author, game)
258+
download_path = os.path.join(self.settings.download_to, author, game)
262259
os.makedirs(download_path, exist_ok=True)
263260

264261
paths: Dict[str, str] = {k: os.path.join(download_path, v) for k, v in TARGET_PATHS.items()}
@@ -372,7 +369,7 @@ def download(self, url: str, skip_downloaded: bool = True):
372369
logging.warning(f"Game {title} has external download URLs: {external_urls}")
373370

374371
# TODO: Mirror JS/CSS assets
375-
if self.mirror_web:
372+
if self.settings.mirror_web:
376373
os.makedirs(paths["screenshots"], exist_ok=True)
377374
for screenshot in metadata["screenshots"]:
378375
if not screenshot:
@@ -406,20 +403,17 @@ def download(self, url: str, skip_downloaded: bool = True):
406403

407404
def drive_downloads(
408405
jobs: List[str],
409-
download_to: str,
410-
mirror_web: bool,
411406
settings: Settings,
412407
keys: Dict[int, str],
413-
parallel: int = 1,
414408
):
415-
downloader = GameDownloader(download_to, mirror_web, settings, keys)
409+
downloader = GameDownloader(settings, keys)
416410
tqdm_args = {
417411
"desc": "Games",
418412
"unit": "game",
419413
}
420414

421-
if parallel > 1:
422-
results = thread_map(downloader.download, jobs, max_workers=parallel, **tqdm_args)
415+
if settings.parallel > 1:
416+
results = thread_map(downloader.download, jobs, max_workers=settings.parallel, **tqdm_args)
423417
else:
424418
results = [downloader.download(job) for job in tqdm(jobs, **tqdm_args)]
425419

0 commit comments

Comments
 (0)