#
# Copyright (C) 2002 Manuel Estrada Sainz <ranty@debian.org>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of version 2.1 of the GNU Lesser General Public
# License as published by the Free Software Foundation.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

from apt_proxy import Backend
from misc import log
import os, sys, re
from types import StringType, NoneType
import urlparse
from ConfigParser import RawConfigParser,DEFAULTSECT
    
class ConfigError(Exception):
    def __init__(self, message):
        self.message = message
    def __str__(self):
        return repr(self.message)

class apConfigParser(RawConfigParser):
    """
    Adds 'gettime' to ConfigParser to interpret the suffixes.
    Interprets 'disabled_keyword' as disabled (None).
    """
    time_multipliers={
        's': 1,    #seconds
        'm': 60,   #minutes
        'h': 3600, #hours
        'd': 86400,#days
        }
    DISABLED_KEYWORD = 'off'
    def isOff(self, section, option):
        value = self.get(section, option)
        return value == self.DISABLED_KEYWORD

    def getint(self, section, option):
        value = self.get(section, option)
        return int(value)
    def gettime(self, section, option):
        mult = 1
        value = self.get(section, option)
        if len(value) == 0:
            raise ConfigError("Configuration parse error: [%s] %s" % (section, option))
        suffix = value[-1].lower()
        if suffix in self.time_multipliers.keys():
            mult = self.time_multipliers[suffix]
            value = value[:-1]
        return int(value)*mult
    def getstring(self, section, option):
        return self.get(section,option)
    def getstringlist(self, section, option):
        return self.get(section,option).split()
    def getproxyspec(self, section, option):
        "Get http proxy info from string"
        p = ProxyConfig(self.get(section,option))
        if p.host is not None:
            return p
        else:
            return None

class apConfig:
    """
    Configuration module for apt-proxy
    holds main configuration values in class and backend
    configs in backends[backend-name]
    """
    
    """
    Configuration items for default section
    [0] - name of config parameter and resulting class variable name
    [1] - default value
    [2] - getXXX method to use to read config.
          A method prefixed with '*' will return None if disabled
    """
    CONFIG_ITEMS = [
        ['address', '', 'string'],
        ['port', 9999, 'int'],
        ['min_refresh_delay', 30, 'time'],
        ['complete_clientless_downloads', False, 'boolean'],
        ['telnet_port', 0, 'int'],
        ['telnet_user', '', 'string'],
        ['telnet_pass', '', 'string'],
        ['timeout', 30, 'time'],
        ['cleanup_freq', 600, '*time'],
        ['cache_dir', '/var/cache/apt-proxy', 'string'],
        ['max_versions', 3, '*int'],
        ['max_age', 10, '*time'],
        ['import_dir', '/var/cache/apt-proxy/import', 'string'],
        ['passive_ftp', 'on', 'boolean'],
        ['dynamic_backends', 'on', 'boolean'],
        ['http_proxy', None , 'proxyspec'],
        ['username', 'aptproxy', 'string'],
        ['bandwidth_limit', None, '*int']
        ]

    """
    Configuration items for backends
    [0] - name of config parameter and resulting class variable name
    [1] - default value, None to use factory default
    [2] - getXXX method to use to read config.
          A method prefixed with '*' will return None if disabled
    """
    BACKEND_CONFIG_ITEMS = [
        ['timeout', None, 'time'],
        ['passive_ftp', None, 'boolean'],
        ['backends', '', 'stringlist'],
        ['http_proxy', None , 'proxyspec'],
        ['bandwidth_limit', None, '*int']
        ]

    DEFAULT_CONFIG_FILE = ['/etc/apt-proxy/apt-proxy-v2.conf',
                            '/etc/apt-proxy/apt-proxy-2.conf',
                            '/etc/apt-proxy/apt-proxy.conf']

    "Backend configurations are held here"
    backends = {}
    parser = None
    debugDomains = {}
    debug = '0'
    
    def __init__(self, config_file = None):
        """
        Read configuration from specified source, or default config
        file location if not specified

        @param config_file Filename to read, or descriptor of already-open file
        """
        self.backends = {}
        if type(config_file) is StringType or type(config_file) is NoneType:
            c = self.readFromFile(config_file)
        else:
            c = self.readFromStream(config_file)

        self.parseConfig(c)

    def readFromFile(self, config_file):
        """
        Read configuration from filename given
        @param config_file filename to read from, or None for default
        """
        conf = apConfigParser()

        if config_file is not None:
            config_files = config_file,
        else:
            config_files = self.DEFAULT_CONFIG_FILE

        for f in config_files:
            if os.path.exists(f):
                conf.read(f)
                break
        else:
            raise ConfigError("%s: Configuration file does not exist" % config_files[0])

        return conf

    def readFromStream(self, filehandle):
        "Read from open file handle, for test suite"
        conf = apConfigParser()
        conf.readfp(filehandle)
        filehandle.close()
        return conf
    
    def setDebug(self):
        "Set logger debug level"
        for domain in self.debug.split():
            #print "domain:",domain
            if domain.find(':') != -1:
                name, level = domain.split(':')
            else:
                name, level = domain, 9
            self.debugDomains[name] = int(level)
        log.setDomains(self.debugDomains)

    def parseConfig(self, config):
        "Read values from apConfigParser config"

        # debug setting
        if config.has_option(DEFAULTSECT, 'debug'):
            self.debug=config.get(DEFAULTSECT, 'debug')
        else:
            self.debug='all:3'
        self.setDebug()

        # read default values
        for name,default,getmethod in self.CONFIG_ITEMS:
            value = self.parseConfigValue(config, DEFAULTSECT, name, default, getmethod)
            setattr(self, name, value)
            if value != default and name != "telnet_pass":
                log.debug("config value %s=%s"%(name, value), 'config')

        self.address = self.address.split(" ")

        if not self.telnet_user or not self.telnet_pass:
            self.telnet_port = 0  # No server if empty username or password

        # Read backend configurations
        for backendName in config.sections():
            self.addBackend(config, backendName)

    def addBackend(self, config, backendName, backendServers=None):
        """
        Add a new backend configuration
        @param config Configuration file parser to get backend values from.  If None, backend is dynamic
        @param backendName Name of backend to create
        @param backendServers List of backend servers to use (if backend is dynamic)
        @ret newly created apBackendConfig
        """
        if backendName.find('/') != -1:
            log.msg("WARNING: backend %s contains '/' (ignored)"%(name))
            return None

        backend = apBackendConfig(backendName)
        for paramName,default,getmethod in self.BACKEND_CONFIG_ITEMS:
            if default is None:
                default = getattr(self, paramName) # Use default section's default value
            value = self.parseConfigValue(config, backendName, paramName, default, getmethod)
            setattr(backend,paramName, value)
            if value != default:
                log.debug("[backend %s] %s=%s"%(backendName, paramName, value), 'config')

        if backendServers is None:
            backend.dynamic = False
            backendServers = backend.backends
        else:
            # Dynamic backend
            backend.dynamic = True

        backend.backends = []
        for server in backendServers:
            if server[-1] == '/':
                log.msg ("Removing unnecessary '/' at the end of %s"%(server))
                server = server[0:-1]
            if urlparse.urlparse(server)[0] in ['http', 'ftp', 'rsync', 'file']:
                backend.backends.append(server)
            else:
                log.msg ("WARNING: Wrong server '%s' found in backend '%s'. It was skipped." % (server, backendName))
                return None
        if len(backend.backends) == 0:
            log.msg("WARNING: [%s] has no backend servers (ignored)"%backendName)
            return None

        self.backends[backendName] = backend
        return backend

    def parseConfigValue(self, config, section, name, default, getmethod):
        "Determine value of given config item"
        if config is None or not config.has_option(section, name):
            return default
        if getmethod[0]=='*':
            if config.isOff(section, name):
                return None
            else:
                return getattr(config, 'get'+getmethod[1:])(section, name)
        else:
                return getattr(config, 'get'+getmethod)(section, name)

class apBackendConfig:
    """
    Configuration information for an apt-proxy backend
    """
    name = "UNKNOWN"
    def __init__(self, name):
        self.name = name

class ProxyConfig:
    """
    Configuration information for backend server proxies
    """
    host = None
    port = None
    user = None
    password = None

    def __init__(self, proxyspec):
        if proxyspec=='':
            return
        m = re.match('^((?P<user>.*):(?P<password>.*)@)?(?P<host>[a-zA-Z0-9_.+=-]+):(?P<port>[a-zA-Z0-9]+)',
                     proxyspec)
        if m:
            self.host = m.group('host')
            self.port = m.group('port')
            try:
                self.port = int(self.port)
            except ValueError:
                pass
            self.user = m.group('user')
            self.password = m.group('password')
