Source code for AutoArchive._infrastructure.configuration._configuration_factory

# _configuration_factory.py
#
# Project: AutoArchive
# License: GNU GPLv3
#
# Copyright (C) 2003 - 2022 Róbert Čerňanský



""":class:`ConfigurationFactory` class."""



__all__ = ["ConfigurationFactory"]



# {{{ INCLUDES

import os

from AutoArchive._infrastructure.utils import Utils
from AutoArchive._infrastructure.configuration import ArchiverTypes
from . import Options, OptionsUtils
from ._configuration import *
from ._cmdline_arguments_processor import _CmdlineArgumentsProcessor
from ._config_file_processor import _ConfigFileProcessor

# }}} INCLUDES



# {{{ CLASSES

# SMELL: Creation of archive specifications directory should perhaps go to Archiving?
[docs]class ConfigurationFactory: """Support of configuration options and a persistent storage. During :meth:`makeConfiguration` call it creates :class:`.IConfiguration` implementation instance. It also creates :term:`user configuration directory` and :term:`archive specifications directory` if either of them does not exists. The :class:`.ConfigurationBase`-like class provides access to configuration options. It is instantiated and populated during :meth:`makeConfiguration` call from :attr:`.ApplicationContext.appEnvironment.options` (command-line options) and from configuration files. The :term:`system configuration file` is "/etc/aa/aa.conf". The :term:`user configuration file` location is determined by values of :attr:`.Options.USER_CONFIG_FILE` and :attr:`.Options.USER_CONFIG_DIR` options. The :term:`user configuration directory` is automatically created if it does not exists. The format of configuration files is defined by the standard :mod:`configparser` module (without interpolation). The :class:`.ConfigurationBase` implementation instance is populated in the way that same options specified in multiple sources overrides each other so that the order of precedence from highest to lowest is following: command-line, user configuration file, system configuration file. However the implementation recognizes certain types of options that does not follow this rule (see :meth:`.ConfigurationBase.__getitem__()`). Some of the options, if not specified in neither of possible sources, has some predefined default value. The list of these options with their predefined value follows: * :attr:`.Options.ARCHIVER`\ : :attr:`.ArchiverTypes.TarGz` * :attr:`.Options.DEST_DIR`\ : ``os.curdir`` * :attr:`.Options.RESTART_AFTER_LEVEL`\ : 10 * :attr:`.Options.ARCHIVE_SPECS_DIR`\ : :attr:`.Options.USER_CONFIG_DIR` + "archive_specs" * :attr:`.Options.USER_CONFIG_FILE`\ : :attr:`.Options.USER_CONFIG_DIR` + "aa.conf" * :attr:`.Options.USER_CONFIG_DIR`\ : "~/.config/aa" * :attr:`.Options.NUMBER_OF_OLD_BACKUPS`\ : 1 * options of type ``bool``\ : ``False``""" # system configuration file - full path __SYSTEM_CONFIG_FILE = "/etc/aa/aa.conf" # user configuration directory __FACTORY_USER_CONFIG_DIR = os.path.expanduser("~/.config/aa") # configuration file name __CONFIG_FILE_NAME = "aa.conf"
[docs] @classmethod def makeConfiguration(cls, appEnvironment, systemConfigFile = __SYSTEM_CONFIG_FILE): """Creates and populates configuration. :param appEnvironment: Application environment. :type appEnvironment: :class:`.AppEnvironment` :param systemConfigFile: Path to the system configuration file. :type systemConfigFile: `str`""" configuration = cls.__createAndPopulateConfig(appEnvironment, systemConfigFile) userDirectoryCreated = cls.__createUserConfigDirectory(configuration) cls.__createArchiveSpecsDirectory(configuration, userDirectoryCreated) return configuration
# {{{ helpers @classmethod def __createAndPopulateConfig(cls, appEnvironment, systemConfigFile): cmdlineArgumentsProcessor = _CmdlineArgumentsProcessor(appEnvironment.commandLineOptions) cmdlineUserConfigDir, cmdlineUserConfigFile = cls.__getUserPathsFromCmdline( cmdlineArgumentsProcessor, appEnvironment) # create IConfiguration which will be registered to Component Accessor configuration = _Configuration() cls.__prePopulateWithDefaults(configuration) # populate configuration with config. file options configFileProcessor = _ConfigFileProcessor( appEnvironment, systemConfigFile, cls.__FACTORY_USER_CONFIG_DIR, lambda usrDir: os.path.join(usrDir, cls.__CONFIG_FILE_NAME), cmdlineUserConfigDir, cmdlineUserConfigFile) configFileProcessor.populateConfiguration(configuration) # populate configuration with command line options cmdlineArgumentsProcessor.populateConfiguration(configuration) cls.__postPopulateWithDefaults(configuration) return configuration @staticmethod def __getUserPathsFromCmdline(cmdlineArgumentsProcessor, appEnvironment) -> tuple[str, str]: # create temporary IConfiguration and populate it with command line options cmdlineConfiguration = _Configuration() cmdlineArgumentsProcessor.populateConfiguration(cmdlineConfiguration) # determine and create user config directory if it does not exists cmdlineUserConfigDir = cmdlineConfiguration[Options.USER_CONFIG_DIR] # get user config file passed as an command-line option, if any and show error if it does not exists cmdlineUserConfigFile = cmdlineConfiguration[Options.USER_CONFIG_FILE] if cmdlineUserConfigFile: if not os.path.isfile(cmdlineUserConfigFile): Utils.printError(str.format("Configuration file \"{}\" does not exists.", cmdlineUserConfigFile), appEnvironment.executableName) return cmdlineUserConfigDir, cmdlineUserConfigFile @staticmethod def __prePopulateWithDefaults(configuration): configuration._addOrReplaceOption(str(Options.ARCHIVER), OptionsUtils.archiverTypeToStr(ArchiverTypes.TarGz)) configuration._addOrReplaceOption(str(Options.DEST_DIR), os.curdir) configuration._addOrReplaceOption(str(Options.RESTART_AFTER_LEVEL), 10) configuration._addOrReplaceOption(str(Options.NUMBER_OF_OLD_BACKUPS), 1) @classmethod def __postPopulateWithDefaults(cls, configuration): cls.__addOptionIfNotPresent(configuration, Options.USER_CONFIG_DIR, cls.__FACTORY_USER_CONFIG_DIR) cls.__addOptionIfNotPresent( configuration, Options.USER_CONFIG_FILE, os.path.join( configuration[Options.USER_CONFIG_DIR], cls.__CONFIG_FILE_NAME)) cls.__addOptionIfNotPresent( configuration, Options.ARCHIVE_SPECS_DIR, os.path.join( configuration[Options.USER_CONFIG_DIR], "archive_specs")) @staticmethod def __addOptionIfNotPresent(configuration, option, value): if not configuration.isOptionPresent(option): configuration._addOrReplaceOption(str(option), value) @staticmethod def __createUserConfigDirectory(configuration): userConfigDir = configuration[Options.USER_CONFIG_DIR] if not os.path.exists(userConfigDir): try: if not configuration[Options.QUIET]: Utils.printWarning(str.format( "User configuration directory does not exists. Creating \"{}\".", userConfigDir)) os.makedirs(userConfigDir) return True except OSError as ex: Utils.printError(str.format( "Unable to create user configuration directory: \"{}\". The reason is: {}", userConfigDir, ex.strerror)) return False @staticmethod def __createArchiveSpecsDirectory(configuration, userDirectoryCreated): userConfigDir = configuration[Options.USER_CONFIG_DIR] archiveSpecsDirectory = configuration[Options.ARCHIVE_SPECS_DIR] if not os.path.exists(archiveSpecsDirectory): try: # show warning only if we are not quiet and if the user configuration directory was not just created; # or in case it was just created the archive specifications directory is not its subdirectory if not configuration[Options.QUIET] and \ (not userDirectoryCreated or not os.path.commonprefix((userConfigDir, archiveSpecsDirectory)) == userConfigDir): Utils.printWarning(str.format( "Archive specifications directory does not exists. Creating \"{}\".", archiveSpecsDirectory)) os.makedirs(archiveSpecsDirectory) except OSError as ex: Utils.printError(str.format( "Unable to create archive specifications directory: \"{}\". The reason is: {}", archiveSpecsDirectory, ex.strerror))
# }}} helpers # }}} CLASSES