#
#    Backupninja python reimplementation, based on original backupninja program
#    by riseup.net.
#    Copyright (C) 2010  Matthijs Kooijman <matthijs@stdin.nl>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License along
#    with this program; if not, write to the Free Software Foundation, Inc.,
#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

""" Action superclass with common functionality """

import sys, ConfigParser
import logging as log

from backupninja import config

def fail_on_exception(f):
    """
    This is a decorator meant for methods on the Action class. It
    catches any exceptions thrown, sets the failed attribute to True and
    rethrows the exception.
    """
    def inner(self, *args, **kwargs):
        try:
            f(self, *args, **kwargs)
        except:
            self.failed = True
            raise
    return inner

class Action(object):
    """
    Subclasses of Action represent handlers for various action types.
    This class is called Action instead of Handler, since even though the
    classes could be referred to as handlers, the instances of this
    class are really actions (i.e., it represents a specific action,
    which is a combination of a action type and a specific action
    configuration).
    """
    def __init__(self, logger):
        # Subclasses should overwrite this with their default config
        # See backupninja.config.load_config for the structure of this
        # value.
        self.default_config = {}
        # Assume we'll run succesfully. If anything fails in the
        # meanwhile, set this to True.
        self.failed = False
        # A logger object for this action. In the future, this might
        # become a specific logger, that includes the action name and
        # type.
        self.log = logger

    def run(self, **kwargs):
        """
        Run this action for a single target. Override this method
        in a subclass
        """
        pass

    def finish(self, **kwargs):
        """
        Called when all targets have been processed. Can be overridden
        in a subclass.
        """
        pass

    def load_config(self, filename):
        """
        Load the configuration for this action from the given filename.
        """
        self.conf = config.load_config(filename, self.default_config)

    def get_config_optional(self, section, option):
        """
        Returns the value of the given option. If the option was not set
        (and no default was set in self.default_config), return None.

        This is a convenience wrapper for ConfigParser.get(), since that
        throws an exception on unset options.
        """
        try:
            return self.conf.get(section, option)
        except ConfigParser.NoOptionError:
            return None

    def get_config_mandatory(self, section, option):
        """
        Returns the value of the given option. If the option was not set
        (and no default was set in self.default_config), raises a
        backupninja.config.ConfigError.

        This is a convenience wrapper for ConfigParser.get(), since that
        has a very generic exception message on unknown options.
        """
        try:
            return self.conf.get(section, option)
        except ConfigParser.NoOptionError:
            raise config.ConfigError("Option '%s' in section '%s' is mandatory, please configure it" % (option, section))

def create_action(ty, **kwargs):
    """
    Create a new (subclass of) Action object for an action with the
    given type. Any extra keyword arguments are passed to the
    constructor.

    If the handler class for this type cannot be loaded, an exception is
    thrown.
    """
    modname = 'backupninja.handlers.%s' % ty
    # Load the handler if it is not loaded yet
    if not modname in sys.modules:
        log.debug('Loading handler for type "%s"', ty)
        try:
            __import__(modname, globals(), locals(), [])
        except ImportError, e:
            # Add some extra info, since the default exception does not
            # show the full module name.
            raise ImportError('Cannot load module %s: %s' % (modname, e))
        log.debug('Loaded handler for type "%s" from "%s"', ty, sys.modules[modname].__file__)
    # Get the module from the module table
    module = sys.modules[modname]

    # Check that the module has a "handler" top level function, which
    # should create a new Action object.
    if not hasattr(module, 'handler'):
        raise ImportError('%s is not valid: it '
                          'does not have a "handler" top level function.' 
                          % (module.__file__))

    # Call the "handler" function to create the actual action
    action = module.handler(**kwargs)
   
    # Check if the handler returned is really a subclass of Action
    if not isinstance(action, Action):
        raise TypeError('%s is not valid, %s.handler did not return a '
                        'subclass of backupninja.handlers.Handler.'
                        % (module.__file__, modname))
    return action
