diff --git a/ChannelsConfigFile/ChannelsConfigFile.py b/ChannelsConfigFile/ChannelsConfigFile.py index 129ba8f..7266092 100644 --- a/ChannelsConfigFile/ChannelsConfigFile.py +++ b/ChannelsConfigFile/ChannelsConfigFile.py @@ -1,19 +1,37 @@ -from pathlib import Path -from typing import Union, Dict, List -import logging -import json import atexit +import json +import logging +from pathlib import Path +from typing import Union, Dict, List, Callable, Tuple + import discord import utils +from . import vNoneTov1_0 logger = logging.getLogger(__name__) +# Functions used to convert between configuration versions, in a dictionary of: old_version -> converter +# Each converter takes the old configuration and returns the converted configuration and the new version +config_version_converters: Dict[Union[None, str], Callable[[Dict], Tuple[Dict, str]]] = { + None: vNoneTov1_0.convert +} + class ChannelsConfigFile: """ Wrapper for the channels configuration file, telling which channels to watch and which channels were created by the bot. """ + version = "1.0" + + @staticmethod + def empty_config() -> Dict: + """ + :return: An empty configuration, used to initialise the configuration file + """ + return { + "__version__": ChannelsConfigFile.version, + } def __init__(self, config_file_path: Union[str, Path]): self.config_file = None # File descriptor to the on-disk config file @@ -30,13 +48,15 @@ class ChannelsConfigFile: if config_file_path.stat().st_size == 0: # Config file is empty logger.warning(f"Config file {config_file_path} is empty, using empty config") - self.config = {} + self.config = self.empty_config() self.save_to_file() # So that the file is proper json else: # Config file is not empty - self.reload_from_disk() + has_been_converted = self.reload_from_disk() + if has_been_converted: + self.save_to_file() else: # Config file does not exist self.config_file = config_file_path.open("w+") - self.config = {} + self.config = self.empty_config() self.save_to_file() # So that the file is proper json # Verifying that attributes have been initialised properly @@ -45,9 +65,13 @@ class ChannelsConfigFile: atexit.register(self.save_to_file) - def reload_from_disk(self) -> None: + def reload_from_disk(self) -> bool: """ Reload the configuration from the on-disk file + + :return: Whether the configuration had to be converted to a more recent version because the on-file was an earlier version + :except json.JSONDecodeError: if the file is not valid json + :except ValueError: if the version of the configuration in the file is not known or can not be converted to a more recent one """ logger.debug("Loading channels configuration file from disk") self.config_file.seek(0) # Moving to beginning of file @@ -56,7 +80,27 @@ class ChannelsConfigFile: except json.JSONDecodeError as e: logger.critical(f"JSON Error when parsing channels config file: {e}") raise e + # Checking configuration version and converting if needed + config_version = self.config["__version__"] if "__version__" in self.config else None + has_been_converted = False + if config_version != self.version: + logger.info(f"Channels configuration file is an older version, converting (on-file: {config_version}, current: {self.version})...") + + # Performing the conversion + while config_version != self.version: + if config_version in config_version_converters: + logger.debug(f"Converting from {config_version}") + self.config, config_version = config_version_converters[config_version](self.config) + logger.debug(f"Converted to {config_version}") + else: + logger.critical(f"Impossible to find converter to convert from {config_version}") + raise ValueError(f"Configuration loading: impossible to convert on-file config to current version") + has_been_converted = True + + assert self.config["__version__"] == self.version + logger.debug("Loaded channels configuration file from disk") + return has_been_converted def save_to_file(self, indent = 2) -> None: """ diff --git a/ChannelsConfigFile/vNoneTov1_0.py b/ChannelsConfigFile/vNoneTov1_0.py new file mode 100644 index 0000000..8a93d29 --- /dev/null +++ b/ChannelsConfigFile/vNoneTov1_0.py @@ -0,0 +1,13 @@ +""" +Converter for converting ChannelsConfigFile from version "Versions weren't a thing back then" to version 1.0 +""" + +from typing import Dict, Tuple + + +def convert(config: Dict) -> Tuple[Dict, str]: + assert "__version__" not in config + # This first simple converter only adds version information to the config + # this poses the basis for version conversion + config["__version__"] = "1.0" + return config, "1.0"