| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121 |
- import argparse
- import ast
- import json
- import os
- import pathlib
- import sys
- from bandcamp_dl import __version__
- TEMPLATE = '%{artist}/%{album}/%{track} - %{title}'
- OK_CHARS = '-_~'
- SPACE_CHAR = '-'
- CASE_LOWER = 'lower'
- CASE_UPPER = 'upper'
- CASE_CAMEL = 'camel'
- CASE_NONE = 'none'
- USER_HOME = pathlib.Path.home()
- # For Linux/BSD https://www.freedesktop.org/wiki/Software/xdg-user-dirs/
- # For Windows ans MacOS .appname is fine
- CONFIG_PATH = USER_HOME / (".config" if os.name == "posix" else ".bandcamp-dl") / "bandcamp-dl.json"
- OPTION_MIGRATION_FORWARD = "forward"
- OPTION_MIGRATION_REVERSE = "reverse"
- class Config(dict):
- # TODO: change this to dataclass when support for Python < 3.7 is dropped
- _defaults = {"base_dir": str(USER_HOME), # TODO: pass the Path object instead?
- "template": TEMPLATE,
- "overwrite": False,
- "no_art": False,
- "embed_art": False,
- "embed_lyrics": False,
- "group": False,
- "no_slugify": False,
- "ok_chars": OK_CHARS,
- "space_char": SPACE_CHAR,
- "ascii_only": False,
- "keep_spaces": False,
- "case_mode": CASE_LOWER,
- "no_confirm": False,
- "debug": False,
- "embed_genres": False,
- "cover_quality": 0,
- "untitled_path_from_slug": False,
- "truncate_album": 0,
- "truncate_track": 0}
- def __init__(self, dict_=None):
- if dict_ is None:
- super().__init__(**Config._defaults)
- else:
- super().__init__(**dict_)
- self.__dict__ = self
- self._read_write_config()
- def _read_write_config(self):
- if CONFIG_PATH.exists():
- with pathlib.Path.open(CONFIG_PATH, 'r+') as fobj:
- try:
- user_config = json.load(fobj)
- # change hyphen with underscore
- user_config = {k.replace('-', '_'): v for k, v in user_config.items()}
- # overwrite defaults with user provided config
- if self._update_with_dict(user_config) or \
- set(user_config.keys()).difference(set(self.keys())) :
- # persist migrated options, removal of unsupported options, or missing
- # options with their defaults
- sys.stderr.write(f"Modified configuration has been written to "
- f"`{CONFIG_PATH}'.\n")
- fobj.seek(0) # r/w mode
- fobj.truncate() # r/w mode
- json.dump({k: v for k, v in self.items()}, fobj)
- except json.JSONDecodeError:
- # NOTE: we don't have logger yet
- sys.stderr.write(f"Malformed configuration file `{CONFIG_PATH}'. Check json syntax.\n")
- else:
- # No config found - populate it with the defaults
- os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True)
- with pathlib.Path.open(CONFIG_PATH, mode="w") as fobj:
- conf = {k.replace('_', '-'): v for k, v in self.items()}
- json.dump(conf, fobj)
- sys.stderr.write(f"Configuration has been written to `{CONFIG_PATH}'.\n")
- def _update_with_dict(self, dict_):
- """update this config instance with the persisted key-value
- set, migrating or dropping any unknown options and returning
- true when the underlying config needs updating"""
- modified = False
- for key, val in dict_.items():
- if key not in self:
- modified = True
- if not self._migrate_option(key, val):
- sys.stderr.write(f"Dropping unknown config option '{key}={val}'\n")
- continue
- self[key] = val
- def _migrate_option(self, key, val):
- """where supported, migrate legacy options and their values
- to update this config instance's new option, returning
- true / false to indicate whether or not this key was
- supported"""
- migration_type = migration_key = migration_val = None
- if key == "keep_upper":
- # forward migration
- migration_type = OPTION_MIGRATION_FORWARD
- migration_key = "case_mode"
- migration_val = self.case_mode = CASE_NONE if val else CASE_LOWER
- elif key == "case_mode":
- # reverse migration
- migration_type = OPTION_MIGRATION_REVERSE
- migration_key = "keep_upper"
- migration_val = self.keep_upper = False if val == CASE_LOWER else True
- if val in [CASE_UPPER, CASE_CAMEL]:
- sys.stderr.write(f"Warning, lossy reverse migration, new value '{val}' is not backwards compatible\n")
- if migration_type:
- sys.stderr.write(f"{migration_type.capitalize()} migration of config option: '{key}={val}' -> " \
- f"'{migration_key}={migration_val}'\n")
- return True
- else:
- return False
|