Improve Config file validation and error messages

This commit is contained in:
Emma Nora Theuer 2025-02-27 12:50:45 +01:00
parent c31c452f7e
commit 42f8e5c2d7
3 changed files with 92 additions and 107 deletions

View file

@ -52,7 +52,7 @@ pip install wallman
#+END_SRC
** Installing manually
+ Install libnotify and feh, (preferably, also install the python dependecies from there) from your package manager
+ Install libnotify and feh, (preferably, also install the python dependencies from there) from your package manager
#+BEGIN_SRC shell
git clone https://git.entheuer.de/emma/Wallman.git
cd Wallman/
@ -65,6 +65,7 @@ mkdir -p ~/.config/wallman
cp sample_config.toml ~/.config/wallman/wallman.toml
sudo mkdir -p /etc/wallman/
sudo cp -r icons /etc/wallman
sudo cp -r DefaultFallbackWallpaper /etc/wallman/DefaultFallbackWallpaper.jpg
python -m build
sudo python -m installer --destdir=/ dist/*.whl
#+END_SRC
@ -85,8 +86,8 @@ In general, you need to always define 3 variables and you can optionally add thr
The amount of wallpapers that you use in each set. It should be an integer.
+ Optional: notify: bool
This defaults to "false". Enable to set send a desktop notification when the wallpaper is changed. The program will still work correctly, even if this option is not defined at all.
+ Optional: fallback_wallpaper: bool
Wallpaper to be set if an error is found in the config or the wallpaper intended to be set cannot be found. Defaults to None. If none is set and the config has been written incorrectly, a ConfigError is raised and the program is exited. If an error in the config occurs but the fallback wallpaper has been defined, it will be set and wallman will exit with Code 1. If The config is written correctly but the wallpaper intended to be set can't be found, wallman will set the fallback wallpaper and continue to try setting future wallpapers.
+ Optional: fallback_wallpaper: string
Wallpaper to be set if an error is found in the config or the wallpaper intended to be set cannot be found. Defaults to "/etc/wallman/DefaultFallbackWallpaper.jpg". If none is set and the config has been written incorrectly, a ConfigError is raised and the program is exited. If an error in the config occurs but the fallback wallpaper has been defined, it will be set and wallman will raise a config error. If The config is written correctly but the wallpaper intended to be set can't be found, wallman will set the fallback wallpaper and continue to try setting future wallpapers. You can both change this location (recommended) or the wallpaper that the path points to.
+ Optional: loglevel: string
Loglevel to be used by wallman. Defaults to INFO. Choices MUST be DEBUG, INFO, WARNING, ERROR or CRITICAL. Using any capitalization is valid, all caps is reccomended. Wallman will crash if a value is specified that is not one of the specified ones.
+ Optional: systray: bool
@ -116,7 +117,6 @@ The keys in the dictionary once again do not matter, the names of the keys in ea
** Technical Details
+ Improve Modularity (Partially done)
+ Make the enabled flag in wallpaper_sets actually useful by making the used_sets field optional
+ Drop the feh dependecy and set wallpapers using pywlroots or python-xlib
** Features

View file

@ -5,7 +5,7 @@ enable_wallpaper_sets = true
used_sets = ["anime", "nature"]
wallpapers_per_set = 5
notify = false
fallback_wallpaper = "/etc/wallman/DefaultFallbackWallpaper.jpg"
fallback_wallpaper = "/etc/wallman/DefaultFallbackWallpaper.jpg" # Feel free to use a different filepath for this!
loglevel = "INFO"
systray = true
behavior = "fill"

View file

@ -1,4 +1,3 @@
from sys import exit
from os import chdir, getenv, system, path
import logging
import tomllib
@ -14,36 +13,26 @@ global logger
logger = logging.getLogger("wallman")
class _Config:
# Initializes the most important config values. TODO: Add handling for the empty config edge case and the FileNotFound case
# Initializes the most important config values.
def __init__(self) -> None:
# Config file
self.config_file: ConfigFile = self._initialize_config() # Full config
# Dictionaries
self.config_general: ConfigGeneral = self.config_file["general"]
self.config_changing_times: Dict[str, str] = self.config_file["changing_times"]
# Values in Dicts
self.config_wallpaper_sets_enabled: bool = self.config_general["enable_wallpaper_sets"]
self.config_used_sets: List[str] = self.config_general["used_sets"]
self.config_wallpapers_per_set: int = self.config_general["wallpapers_per_set"]
# Config general
valid_general: bool = self._initialize_general()
if not valid_general:
logger.critical("The general dictionary was not found or contains errors")
print("CRITICAL: The general dictionary was not found or contains errors")
raise ConfigError("The general dictionary was not found or contains errors")
# Changing times
valid_changing_times: bool = self._initialize_changing_times()
if not valid_changing_times:
logger.critical("The amount of provided changing times does not match the amount of wallpapers per set, or the dictionary has not been found in the config file.")
print("CRITICAL: The amount of provided changing times does not match the amount of wallpapers per set, or the dictionary has not been found in the config file.")
self.config_total_changing_times: int = len(self.config_changing_times)
self.config_log_level: str = self.config_general.get("loglevel", "INFO").upper()
self.config_behavior: str = self._set_behavior()
# Setup logging
self._set_log_level()
# HACK: Add a function to handle these try/except blocks cleanlier.
try:
self.config_notify: bool = self.config_general["notify"]
except KeyError:
self.config_notify: bool = False
logger.warning("'notify' is not set in dictionary general in the config file, defaulting to 'false'.")
try:
self.config_systray: bool = self.config_general["systray"]
except KeyError:
self.config_systray: bool = True
logger.warning("'systray' is not set in the dictionary general in the config file, defaulting to 'true'.")
# Setup systray.
if self.config_systray:
self._verify_systray_deps()
# Wallpaper sets
valid_wallpaper_amount: bool = self._check_wallpaper_amount()
if not valid_wallpaper_amount:
raise ConfigError("The amount of wallpapers in a set does not match the amount of wallpapers_per_set provided in general.")
# Read config
def _initialize_config(self) -> ConfigFile:
@ -109,12 +98,13 @@ class _Config:
return behavior
def _set_fallback_wallpaper(self) -> None:
if self.config_general["fallback_wallpaper"]:
system(f"feh {self.config_behavior} --no-fehbg {self.config_general['fallback_wallpaper']}")
logger.info("The fallback Wallpaper has been set.")
else:
logger.critical("An Error occured and no fallback wallpaper was provided, exiting...")
raise ConfigError("An error occured and no fallback wallpaper has been set, exiting...")
if self.config_fallback_wallpaper:
successfully_set: int = system(f"feh {self.config_behavior} --no-fehbg {self.config_fallback_wallpaper}")
if successfully_set == 0:
logger.info("The fallback Wallpaper has been set.")
else:
logger.critical("An Error occured and no fallback wallpaper was provided, exiting...")
raise ConfigError("An error occured and no fallback wallpaper has been set, exiting...")
def _initialize_general(self) -> bool:
# Create Config General Dict
@ -122,32 +112,75 @@ class _Config:
self.config_general: ConfigGeneral = self.config_file["general"]
except KeyError:
print("CRITICAL: No general dictionary found in Config file.")
return False
raise ConfigError("The general dictionary could not be found in the config, exiting!")
# Set up logger.
self.config_log_level = self.config_general.get("log_level", "INFO").upper()
self._set_log_level()
logger.debug(f"Log level has been set to {self.config_log_level}")
logger.debug("Logger initialized successfully")
# Set up fallback wallpaper
self.config_fallback_wallpaper: str = self.config_general.get("fallback_wallpaper", "/etc/wallman/DefaultFallbackWallpaper.jpg")
logger.debug(f"Set fallback wallpaper: {self.config_fallback_wallpaper}")
# Wallpapers per set
try:
self.config_wallpapers_per_set: int = self.config_general["wallpapers_per_set"]
logger.debug(f"Set config_wallpapers_per_set to {self.config_wallpapers_per_set}")
except KeyError:
print("CRITICAL: No option wallpapers_per_set provided in the general dictionary. Attempting to set the fallback wallpaper")
logger.critical("No option wallpapers_per_set provided in the general dictionary. Attempting to set the fallback wallpaper")
self._set_fallback_wallpaper()
return False
# Are wallpaper sets enabled to begin with?
try:
self.config_wallpaper_sets_enabled: bool = self.config_general["enable_wallpaper_sets"]
logger.debug(f"Set config_wallpaper_sets_enabled to {self.config_wallpaper_sets_enabled}")
except KeyError:
logger.critical("No option enable_wallpaper_sets provided in the general dictionary. Attempting to set the fallback wallpaper")
print("CRITICAL: No option enable_wallpaper_sets provided in the general dictionary. Attempting to set the fallback wallpaper")
self._set_fallback_wallpaper()
return False
# Configure used sets
if self.config_wallpaper_sets_enabled:
try:
self.config_used_sets: List[str] = self.config_general["used_sets"]
logger.debug(f"These wallpaper sets are in use: {self.config_used_sets}")
except KeyError:
print("CRITICAL: No array used_sets provided in the general dictionary. Attempting to set the fallback wallpaper.")
logger.critical("No array used_sets provided in the general dictionary. Attempting to set the fallback wallpaper.")
self._set_fallback_wallpaper()
return False
# Systray
try:
self.config_systray: bool = self.config_general["systray"]
logger.debug(f"config_systray has been set to: {self.config_systray}")
except KeyError:
self.config_systray: bool = True
logger.warning("No option systray found in general. Defaulting to true...")
if self.config_systray:
self._verify_systray_deps()
# Wallpaper behavior
self.config_behavior = self._set_behavior()
# Notifications
try:
self.config_notify: bool = self.config_general["notify"]
logger.debug(f"Set config_notify to {self.config_notify}.")
except KeyError:
self.config_notify: bool = False
logger.warning("notify is not set in dictionary general in the config file, defaulting to 'false'.")
return True
class ConfigValidity(_ConfigLib):
# TODO: Add handling for the empty config case.
def __init__(self):
super().__init__()
def _check_fallback_wallpaper(self) -> bool:
# TODO: Make self.config_general["fallback_wallpaper"] a class-wide variable in ConfigLib
if self.config_general["fallback_wallpaper"]:
logger.debug("A fallback wallpaper has been defined.")
return True
else:
logger.warning("No fallback wallpaper has been provided. If the config is written incorrectly, the program will not be able to be executed.")
def _initialize_changing_times(self) -> bool:
try:
self.config_changing_times: Dict[str, str] = self.config_file["changing_times"]
logger.debug(f"Changing times are {self.config_changing_times}")
except KeyError:
logger.critical("No dictionary called changing_times has been found in the config file.")
print("CRITICAL: No dictionary called changing_times has been found in the config file.")
return False
return self._wallpapers_per_set_and_changing_times_match()
def _check_wallpapers_per_set_and_changing_times(self) -> bool:
def _wallpapers_per_set_and_changing_times_match(self) -> bool:
# Check if the amount of wallpapers_per_set and given changing times match
if self.config_total_changing_times == self.config_wallpapers_per_set:
logger.debug("The amount of changing times and wallpapers per set is set correctly")
@ -155,47 +188,13 @@ class ConfigValidity(_ConfigLib):
else:
try:
self._set_fallback_wallpaper()
logger.error("The amount of changing_times and the amount of wallpapers_per_set does not match, the fallback wallpaper has been set.")
print("ERROR: The amount of changing_times and the amount of wallpapers_per_set does not match, the fallback wallpaper has been set.")
logger.critical("The amount of changing_times and the amount of wallpapers_per_set does not match, the fallback wallpaper has been set.")
print("CRITICAL: The amount of changing_times and the amount of wallpapers_per_set does not match, the fallback wallpaper has been set.")
return False
except ConfigError:
logger.critical("The amount of changing times and the amount of wallpapers per set does not match, exiting...")
raise ConfigError("Please provide an amount of changing_times equal to wallpapers_per_set, exiting...")
def _check_general_validity(self) -> bool:
# FIXME!
# TODO: Adjust it to check for the actually required variables existing rather than check if a number of options is set, which is highly error prone.
if len(self.config_general) < 3:
try:
self._set_fallback_wallpaper()
logger.error("An insufficient amount of elements has been provided for general, the fallback wallpaper has been set.")
print("ERROR: An insufficient amount of wallpapers has been provided for general, the fallback wallpaper has been set.")
return False
except ConfigError:
logger.critical("An insufficient amount of elements for general has been provided, exiting...")
raise ConfigError("general should have at least 3 elements, exiting...")
else:
logger.debug("A valid amount of options has been provided in general")
return True
def _check_wallpaper_dicts(self) -> bool:
# This block checks if a dictionary for each wallpaper set exists
for wallpaper_set in self.config_used_sets:
if wallpaper_set in self.config_file:
logger.debug(f"The dictionary {wallpaper_set} has been found in config.")
return True
# TODO split this into smaller pieces. This goes too deep.
else:
try:
self._set_fallback_wallpaper()
logger.error(f"The dictionary {wallpaper_set} has not been found in the config, the fallback wallpaper has been set.")
print(f"ERROR: The dictionary {wallpaper_set} has not been found in the config, the fallback wallpaper has been set.")
return False
except ConfigError:
logger.critical(f"No dictionary {wallpaper_set} has been found in the config exiting...")
raise ConfigError(f"The dictionary {wallpaper_set} has not been found in the config, exiting...")
return False
print("CRITICAL: The amount of changing times and the amount of wallpapers per set does not match, exiting...")
raise ConfigError("The amount of changing times and the amount of wallpapers per set does not match.")
def _check_wallpaper_amount(self) -> bool:
# This block checks if if each wallpaper set dictionary provides enough wallpapers to satisfy wallpapers_per_set
@ -211,23 +210,9 @@ class ConfigValidity(_ConfigLib):
return False
except ConfigError:
logger.critical(f"Dictionary {wallpaper_set} does not have sufficient entries, exciting...")
raise ConfigError(f"Dictionary {wallpaper_set} does not have the correct amount of entries, exciting...")
print(f"Dictionary {wallpaper_set} does not have sufficient entries, exciting...")
return False
def validate_config(self) -> bool:
# HACK: Consider using different exit codes for different errors to help users debug.
if not self._check_fallback_wallpaper():
pass
if not self._check_wallpapers_per_set_and_changing_times():
exit(1)
if not self._check_general_validity():
exit(1)
if not self._check_wallpaper_dicts():
exit(1)
if not self._check_wallpaper_amount():
exit(1)
logger.debug("The config file has been validated successfully (No Errors)")
return True
# TODO: Improve modularity. See notes inside the class for more details.
# TODO: Ensure functionality and if needed add handling for the 1 wallpaper per set case.