Source code for AutoArchive.starter

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



"""Initializes the application and starts it."""



__all__ = ["Starter"]



# {{{ INCLUDES

from abc import ABCMeta, abstractmethod
import os
import sys
from argparse import ArgumentParser, RawDescriptionHelpFormatter, SUPPRESS

from ._meta import _Meta
from AutoArchive._infrastructure.utils import Constants, Utils
from AutoArchive._infrastructure._app_environment import AppEnvironment
from AutoArchive._infrastructure.service._service_accessor import ServiceAccessor
from AutoArchive._infrastructure._application_context import ApplicationContext
from AutoArchive._infrastructure.configuration._configuration_factory import ConfigurationFactory
from AutoArchive._infrastructure.configuration import Options, OptionsUtils, ArchiverTypes
from AutoArchive._infrastructure.storage._file_storage import FileStorage
from AutoArchive._services.archiver._archiver_service_component import ArchiverServiceComponent
from AutoArchive._services.external_command_executor._external_command_executor_service_component import \
    ExternalCommandExecutorServiceComponent
from AutoArchive._application.archiving import ArchivingApplication
from AutoArchive._ui.cmdline._user_action_executor import UserActionExecutor
from AutoArchive._ui.cmdline._cmdline_ui import CmdlineUi
from AutoArchive._ui.cmdline._cmdline_commands import CmdlineCommands

# }}} INCLUDES



# {{{ CONSTANTS

#: Tuple of service components
_SERVICE_COMPONENTS = (ExternalCommandExecutorServiceComponent, ArchiverServiceComponent)

# }}} CONSTANTS



# {{{ CLASSES

[docs]class Starter(metaclass = ABCMeta): "Fires up the show." @abstractmethod def __init__(self): pass
[docs] @classmethod def start(cls, programArgs = sys.argv): "Initializes and starts the program." try: options, arguments = cls.__parseArguments(programArgs[1:]) appEnvironment = AppEnvironment(os.path.basename(programArgs[0]), options, arguments) configuration = ConfigurationFactory.makeConfiguration(appEnvironment) storage = FileStorage(configuration) applicationContext = ApplicationContext(appEnvironment, configuration, storage) serviceAccessor = ServiceAccessor() serviceComponents = cls.__createServiceComponents(applicationContext, serviceAccessor) cmdlineUi = CmdlineUi(appEnvironment, configuration) archivingApplication = ArchivingApplication(cmdlineUi, applicationContext, serviceAccessor) return 0 if UserActionExecutor(cmdlineUi, applicationContext, archivingApplication).execute() else 1 except KeyboardInterrupt: print("\nAborted by user.") return 1 except Exception as ex: import traceback if Constants.DEBUG: print(traceback.print_exc()) else: Utils.printError(str.format("Exception occurred: {}.", traceback.format_exception_only(type(ex), ex))) return 1
@staticmethod def __createServiceComponents(applicationContext, serviceAccessor): serviceComponents = [] for serviceComponentClass in _SERVICE_COMPONENTS: serviceComponents.append(serviceComponentClass(applicationContext, serviceAccessor)) return serviceComponents @classmethod def __parseArguments(cls, programArgs): "Parses command line arguments." # define usage and version strings usage = "%(prog)s [options] [command] [AA_SPEC ...]" description = _Meta.DESCRIPTION version = """\ %(prog)s version {version} {copyright} {license} """.format(version = _Meta.VERSION, copyright = _Meta.COPYRIGHT, license = _Meta.LICENSE) # create parser and add the options # we use RawDescriptionHelpFormatter to preserve format of 'version' text parser = ArgumentParser(usage = usage, description = description, add_help = False, formatter_class = RawDescriptionHelpFormatter, argument_default = SUPPRESS) parser.add_argument("aaSpecs", metavar = "AA_SPEC", nargs = "*", help = str.format( "Archive specification. It determines the archive specification file that shall be " + "processed. If AA_SPEC contains the \".aa\" extension then it is taken as the path " + "to an archive specification file. Otherwise, if specified without the extension, " + "the corresponding .aa file is searched in the archive specifications directory " + "(see option --{}).)", Options.ARCHIVE_SPECS_DIR)) # {{{ commands commandsGroup = parser.add_argument_group( "Commands", "Commands for program's operations. The default operation is the backup " + "creation if no command is specified.") commandsGroup.add_argument(cls.__makeCmdlineOption(CmdlineCommands.LIST), action = "store_true", help = "Show all configured or orphaned archives.") commandsGroup.add_argument(cls.__makeCmdlineOption(CmdlineCommands.PURGE), action = "store_true", help = "Purge stored data for an orphaned archive.") commandsGroup.add_argument("--version", action = "version", version = version, help = "Show program's version number and exit.") commandsGroup.add_argument("-h", "--help", action = "help", help = "Show this help message and exit.") # }}} commands # {{{ archiving related options archivingGroup = parser.add_argument_group("Archiving options") archiverChoices = [OptionsUtils.archiverTypeToStr(arch) for arch in ArchiverTypes] archivingGroup.add_argument("-a", cls.__makeCmdlineOption(Options.ARCHIVER), metavar = "ARCHIVER", choices = archiverChoices, help = str.format("Specify archiver type. Supported types are: {choices} " + "(default: targz).", choices = archiverChoices)) compressionLevelChoices = [str(level) for level in range(0, 10)] archivingGroup.add_argument("-c", cls.__makeCmdlineOption(Options.COMPRESSION_LEVEL), choices = compressionLevelChoices, metavar = "NUM", help = str.format( "Compression strength level. If not specified, default " + "behaviour of underlying compression program will be used. " + "Valid range is from {rangeFrom} to {rangeTo}.", rangeFrom = compressionLevelChoices[0], rangeTo = compressionLevelChoices[-1])) archivingGroup.add_argument("-d", cls.__makeCmdlineOption(Options.DEST_DIR), metavar = "DIR_PATH", help = "Directory where the backup will be created (default: <current directory>).") archivingGroup.add_argument(cls.__makeCmdlineOption(Options.OVERWRITE_AT_START), action = "store_true", help = "If enabled, backups are overwritten at the start of creation. If " + "disabled (default), backups are overwritten at the end of creation. " + "Enabling this option can be useful with big backups and low free space " + "on the backup volume.") # }}} archiving related options # {{{ incremental archiving related options incrementalGroup = parser.add_argument_group("Incremental archiving options") incrementalGroup.add_argument("-i", cls.__makeCmdlineOption(Options.INCREMENTAL), action = "store_true", help = "Perform incremental backup.") incrementalGroup.add_argument("-l", cls.__makeCmdlineOption(Options.LEVEL), type = int, help = "Specify the backup level which should be created. All information " + "about higher levels---if any exists---will be erased. If not " + "present, the next level in a row will be created.") incrementalGroup.add_argument(cls.__makeCmdlineOption(Options.RESTARTING), action = "store_true", help = "Turns on backup level restarting. See other '*restart-*' options to " + "configure the restarting behaviour.") incrementalGroup.add_argument(cls.__makeCmdlineOption(Options.RESTART_AFTER_LEVEL), type = int, metavar = "LEVEL", help = str.format("Maximal backup level. If reached, it will be restarted " + "back to a lower level (which is typically level 1 but it " + "depends on '--{}') (default: 10).", Options.MAX_RESTART_LEVEL_SIZE)) incrementalGroup.add_argument(cls.__makeCmdlineOption(Options.RESTART_AFTER_AGE), type = int, metavar = "DAYS", help = str.format("Number of days after which the backup level is restarted. " + "Similarly to '--{}' it will be restarted to level 1 or " + "higher.", Options.RESTART_AFTER_LEVEL)) incrementalGroup.add_argument(cls.__makeCmdlineOption(Options.FULL_RESTART_AFTER_COUNT), type = int, metavar = "COUNT", help = "Number of backup level restarts after which the level is restarted to 0.") incrementalGroup.add_argument(cls.__makeCmdlineOption(Options.FULL_RESTART_AFTER_AGE), type = int, metavar = "DAYS", help = "Number of days after which the backup level is restarted to 0.") incrementalGroup.add_argument(cls.__makeCmdlineOption(Options.MAX_RESTART_LEVEL_SIZE), type = int, metavar = "PERCENTAGE", help = "Maximal percentage size of a backup (of level > 0) to which level is " + "allowed restart to. The size is percentage of size of the level 0 " + "backup file. If a backup of particular level has its size bigger " + "than defined percentage, restart to that level will not be allowed.") incrementalGroup.add_argument(cls.__makeCmdlineOption(Options.REMOVE_OBSOLETE_BACKUPS), action = "store_true", help = "Turn on removing backups of levels that are no longer valid due to " + "the backup level restart. All backups of the backup level higher " + "than the one currently being created will be removed.") # }}} incremental archiving related options # {{{ options related to old backups keeping keepingGroup = parser.add_argument_group("Options for keeping old backups") keepingGroup.add_argument("-k", cls.__makeCmdlineOption(Options.KEEP_OLD_BACKUPS), action = "store_true", help = str.format("Turn on backup keeping. When a backup is about to be " + "overwritten, it is renamed instead. If '--{}' is enabled it " + "applies to all corresponding increments. The new name is " + "created by inserting a keeping ID in front of backup file(s) " + "extension. The keeping ID is a string from interval 'aa', " + "'ab', ..., 'zy', 'zz' where 'aa' represents most recent kept " + "backup.", Options.INCREMENTAL)) keepingGroup.add_argument(cls.__makeCmdlineOption(Options.NUMBER_OF_OLD_BACKUPS), type = int, metavar = "NUM", help = str.format("Number of old backups to keep when '--{}' is enabled " + "(default: 1).", Options.KEEP_OLD_BACKUPS)) # }}} options related to old backups keeping # {{{ command execution options commandExecutionGroup = parser.add_argument_group("Command execution options") commandExecutionGroup.add_argument(cls.__makeCmdlineOption(Options.COMMAND_BEFORE_ALL_BACKUPS), type = str, metavar = "COMMAND_BEFORE_ALL", help = "Arbitrary command that will be executed before backup creation " + "for the set of selected archives.") commandExecutionGroup.add_argument(cls.__makeCmdlineOption(Options.COMMAND_AFTER_ALL_BACKUPS), metavar = "COMMAND_AFTER_ALL", help = "Arbitrary command that will be executed after backup creation " + "for the set of selected archives.") commandExecutionGroup.add_argument(cls.__makeCmdlineOption(Options.COMMAND_BEFORE_BACKUP), metavar = "COMMAND_BEFORE", help = "Arbitrary command to execute prior to each backup creation.") commandExecutionGroup.add_argument(cls.__makeCmdlineOption(Options.COMMAND_AFTER_BACKUP), metavar = "COMMAND_AFTER", help = "Arbitrary command to execute after each backup creation.") # }}} command execution options # {{{ general options generalGroup = parser.add_argument_group("General options") generalGroup.add_argument("-v", cls.__makeCmdlineOption(Options.VERBOSE), action = "count", help = "Turn on verbose output.") generalGroup.add_argument("-q", cls.__makeCmdlineOption(Options.QUIET), action = "store_true", help = str.format("Turn on quiet output. Only errors will be shown. If --{quiet} " + "is turned on at the same level as --{verbose} (e. g. both are " + "specified on the command line) then --{quiet} has higher " + "priority than --{verbose}.", quiet = Options.QUIET, verbose = Options.VERBOSE)) generalGroup.add_argument(cls.__makeCmdlineOption(Options.ALL), action = "store_true", help = str.format("Operate on all configured archives. See also --{}.", Options.ARCHIVE_SPECS_DIR)) generalGroup.add_argument(cls.__makeCmdlineOption(Options.ARCHIVE_SPECS_DIR), metavar = "DIR_PATH", help = "Directory where archive specification files will be searched for (default: " + "~/.config/aa/archive_specs).") generalGroup.add_argument(cls.__makeCmdlineOption(Options.USER_CONFIG_FILE), metavar = "FILE_PATH", help = "Alternate user configuration file (default: ~/.config/aa/aa.conf).") generalGroup.add_argument(cls.__makeCmdlineOption(Options.USER_CONFIG_DIR), metavar = "DIR_PATH", help = "Alternate user configuration directory (default: ~/.config/aa).") # }}} general options # {{{ force options forceGroup = parser.add_argument_group("Force options", "Options to override standard options defined in " + "archive specification files.") forceGroup.add_argument(cls.__makeCmdlineOption(Options.FORCE_ARCHIVER), choices = archiverChoices, metavar = "ARCHIVER", help = str.format( "Force archiver type. See --{archiver} option for supported types.", archiver = Options.ARCHIVER)) forceGroup.add_argument(cls.__makeCmdlineOption(Options.FORCE_INCREMENTAL), action = "store_true", help = "Force incremental backup.") forceGroup.add_argument(cls.__makeCmdlineOption(Options.FORCE_RESTARTING), action = "store_true", help = "Force backup level restarting.") forceGroup.add_argument(cls.__makeCmdlineOption(Options.FORCE_COMPRESSION_LEVEL), choices = compressionLevelChoices, metavar = "NUM", help = "Force compression strength level.") forceGroup.add_argument(cls.__makeCmdlineOption(Options.FORCE_DEST_DIR), metavar = "DIR_PATH", help = "Force the directory where the backup will be created.") forceGroup.add_argument(cls.__makeCmdlineOption(Options.FORCE_COMMAND_BEFORE_BACKUP), metavar = "COMMAND_BEFORE", help = "Force configuration of the command to execute prior to each backup creation.") forceGroup.add_argument(cls.__makeCmdlineOption(Options.FORCE_COMMAND_AFTER_BACKUP), metavar = "COMMAND_AFTER", help = "Force configuration of the command to execute after each backup creation.") forceGroup.add_argument(cls.__makeCmdlineOption(Options.FORCE_OVERWRITE_AT_START), action = "store_true", help = "Force backup overwriting behavior.") # }}} force options # {{{ negation options negationGroup = parser.add_argument_group("Negation options", "Negative variants of standard boolean options.") negationGroup.add_argument(cls.__makeCmdlineOption(Options.NO_INCREMENTAL), action = "store_true", help = "Disable incremental backup.") negationGroup.add_argument(cls.__makeCmdlineOption(Options.NO_RESTARTING), action = "store_true", help = "Turn off backup level restarting.") negationGroup.add_argument(cls.__makeCmdlineOption(Options.NO_REMOVE_OBSOLETE_BACKUPS), action = "store_true", help = "Turn off obsolete backups removing.") negationGroup.add_argument(cls.__makeCmdlineOption(Options.NO_KEEP_OLD_BACKUPS), action = "store_true", help = "Turn off backup keeping.") negationGroup.add_argument(cls.__makeCmdlineOption(Options.NO_ALL), action = "store_true", help = "Do not operate on all configured archive specification files.") negationGroup.add_argument(cls.__makeCmdlineOption(Options.NO_OVERWRITE_AT_START), action = "store_true", help = "Do not overwrite backup at the start of creation. Overwrite after the new " + "backup is created.") # }}} negation options args = parser.parse_args(programArgs) options = vars(args) arguments = options["aaSpecs"] if "aaSpecs" in options else [] return options, arguments @staticmethod def __makeCmdlineOption(option): return "--" + str(option)
# }}} CLASSES