From 9991906afd5c505fe2a96593f76c32e9594c9866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A3=D1=80=D0=BE=D1=88=20=D0=9C=D0=B8=D0=BB=D0=B8=D0=B2?= =?UTF-8?q?=D0=BE=D1=98=D0=B5=D0=B2=D0=B8=D1=9B?= Date: Sun, 17 Sep 2023 16:47:06 +0200 Subject: [PATCH] Configuration file (#56) * Added a simple config parser and updated argument defaults * Fixed import sorting * Update changelog * Added configuration instructions to the readme * Added logging level to configuration file * Fixed import sorting --- CHANGELOG.md | 7 +++---- README.md | 25 +++++++++++++++++++++++++ mpcover/__init__.py | 5 ++++- mpcover/__main__.py | 35 +++++++++++++++++++++++++++++------ mpcover/config.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ mpcover/gui/root.py | 6 +++++- 6 files changed, 111 insertions(+), 12 deletions(-) create mode 100644 mpcover/config.py diff --git a/CHANGELOG.md b/CHANGELOG.md index b41af31..0d67ff6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,8 @@ ## Version 0.2.0 -- [ ] Handle no album art being available when the file is missing. -- [ ] Keybinds for forcing album art refresh. -- [ ] Add ability to change settings through a config file. -- [ ] Better logging. +- [x] Handle no album art being available when the file is missing. +- [x] Keybinds for forcing album art refresh. +- [x] Add ability to change settings through a config file. ## Version 0.1.0 diff --git a/README.md b/README.md index 29248fe..116dcc8 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,28 @@ [![Code QC](https://github.com/milivojevicu/mpcover/actions/workflows/check.yml/badge.svg)](https://github.com/milivojevicu/mpcover/actions/workflows/check.yml) Python program for displaying album covers of music currently playing through MPD using tkinter. + +## Configuration + +The configuration file should be located in the user home directory with the name ".mpcover.init". + +For more information on where the user home directory is, +reference [`os.path.expanduser`](https://docs.python.org/3/library/os.path.html#os.path.expanduser). + +Example configuration file: + +```ini +[connection] +# Connection settings. The password is optional, to leave it unset simply remove +# the "password = ..." line from the configuration file. +port = 6600 +host = localhost +password = example_password + +[logging] +level = info + +[binds] +# The values should be `tkinter` key bind strings. +refresh = r +``` diff --git a/mpcover/__init__.py b/mpcover/__init__.py index 3211bb4..6a11f1e 100644 --- a/mpcover/__init__.py +++ b/mpcover/__init__.py @@ -1,5 +1,8 @@ import logging +from .config import get_config + logging.basicConfig( - level=logging.DEBUG, format="%(asctime)s %(levelname)7s : %(name)s : %(message)s" + level=get_config().get("logging", "level").upper(), + format="%(asctime)s %(levelname)8s : %(name)s -> %(message)s", ) diff --git a/mpcover/__main__.py b/mpcover/__main__.py index 10341ef..6ff553b 100755 --- a/mpcover/__main__.py +++ b/mpcover/__main__.py @@ -1,34 +1,56 @@ #!python import argparse +import configparser import logging +from .config import get_config from .gui import init logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) -def parse_arguments(): - parser = argparse.ArgumentParser(description="Album cover viewer for MPD.") +def parse_arguments(config: configparser.ConfigParser) -> argparse.Namespace: + """ + Parse arguments from the command line. + + :arg config: Configuration object read from a file. Used to set default values. + Values from the command line override the values from the config file. + + :return: `argparse.Namespace` object with the parsed arguments. + """ + + parser = argparse.ArgumentParser( + description="Album cover viewer for MPD.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument( "-a", "--address", metavar="ADDRESS", type=str, - default="127.0.0.1", + default=config.get("connection", "host"), help="MPD server IP address", ) + parser.add_argument( - "-p", "--port", metavar="PORT", type=int, default=6600, help="MPD server port" + "-p", + "--port", + metavar="PORT", + type=int, + default=config.get("connection", "port"), + help="MPD server port", ) + parser.add_argument( "-s", "--pass", metavar="PASSWORD", dest="password", type=str, - default=None, + default=config.get("connection", "password", fallback=None), help="password for auth with the MPD server", ) @@ -40,7 +62,8 @@ def run(): Entry point. Called when the `mpcover` command is called. """ - arguments = parse_arguments() + config = get_config() + arguments = parse_arguments(config) address = arguments.address, arguments.port init(address, arguments.password) diff --git a/mpcover/config.py b/mpcover/config.py new file mode 100644 index 0000000..f34f8dc --- /dev/null +++ b/mpcover/config.py @@ -0,0 +1,45 @@ +import configparser +import os.path + +__DEFAULTS_CONNECTION = { + "host": "localhost", + "port": 6600, +} + +__DEFAULTS_LOGGING = { + "level": "info", +} + +__DEFAULTS_BINDS = { + "refresh": "r", +} + +__CONFIG = None + + +def get_config(): + """ + Read configuration from a file. + """ + + global __CONFIG + + if __CONFIG is not None: + return __CONFIG + + config = configparser.ConfigParser() + + # Load default settings. + config.read_dict({"connection": __DEFAULTS_CONNECTION}) + config.read_dict({"logging": __DEFAULTS_LOGGING}) + config.read_dict({"binds": __DEFAULTS_BINDS}) + + # Read user settings from a file. + config.read(os.path.expanduser(os.path.join("~", ".mpcover.ini"))) + + __CONFIG = config + + return config + + +__all__ = "get_config" diff --git a/mpcover/gui/root.py b/mpcover/gui/root.py index ba58810..c32c825 100644 --- a/mpcover/gui/root.py +++ b/mpcover/gui/root.py @@ -1,3 +1,4 @@ +import configparser import io import tkinter as tk from logging import getLogger @@ -6,6 +7,7 @@ from PIL import Image, ImageTk +from ..config import get_config from ..connection import Connection from ..controler import Controler @@ -41,6 +43,8 @@ class Root(tk.Tk): def __init__(self, address: Tuple[str, int], password: Optional[str]): super().__init__() + config: configparser.ConfigParser = get_config() + # Connect to MPD. self.__connection: Connection = Connection(*address) self.__controler: Controler = Controler(self.__connection, password) @@ -105,7 +109,7 @@ def __init__(self, address: Tuple[str, int], password: Optional[str]): self.__get_album_art() # Handle whole window keybinds. - self.bind("r", self.__get_album_art) + self.bind(config.get("binds", "refresh"), self.__get_album_art) def on_close(self): """