"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/dulcinea/lib/site_util.py $
$Id: site_util.py 22983 2003-11-05 01:53:27Z dbinger $

Provides access to site configuration data.
"""
# Change this if you need a different config file.

SITE_CONF = '/www/conf/site.conf'

import sys, os, re, socket
import ConfigParser

_ptl_enabled = False # delay until needed since it's expensive

class SiteConfig:
    """This is a ConfigParser-like class.  It allows settings for specific
    hosts to be overridden.  For example, if the site.conf contains

        [goodsite]
        mode: devel

        [goodsite darkstar]
        mode: live

    then config.get("goodsite", "mode") will return "devel" on all hosts
    except on "darkstar", where it will return "live".
    """

    def __init__ (self):
        config = ConfigParser.ConfigParser()
        config.readfp(open(SITE_CONF))
        self._defaults = {} # { name : value }
        self._options = {} # { (site, name) : value }
        self._sites = [site for site in config.sections()
                       if site.find(' ') == -1]
        # [DEFAULT]
        for name, value in config.defaults().items():
            for site in self._sites:
                self._defaults[name] = value
        # [site]
        for site in self._sites:
            for name in config.options(site):
                self._options[(site, name)] = config.get(site, name)
        # [site host]
        host = socket.gethostname()
        for site in self._sites:
            section = site + ' ' + host
            if config.has_section(section):
                for name in config.options(section):
                    self._options[(site, name)] = config.get(section, name)

    # XXX should really come up with a nicer interface.  The ConfigParser one
    # is pretty ugly.

    def defaults (self):
        return self._defaults

    def has_option (self, site, name):
        return (site, name) in self._options

    def get (self, site, name, fallback=None):
        if (site, name) in self._options:
            return self._options[(site, name)]
        else:
            return self._defaults.get((site, name), fallback)

    def list_sites (self):
        return self._sites

_config = SiteConfig()

def get_config ():
    return _config

def list_sites ():
    return _config.list_sites()

def get_hostname ():
    return socket.gethostbyname_ex(socket.gethostname())[0]

def is_staging (site):
    return _config.get(site, 'mode') == 'staging'

def is_live (site):
    return _config.get(site, 'mode') == 'live'

def any_live_sites ():
    """Returns True if any sites are live."""
    for site in list_sites():
        if is_live(site):
            return True
    else:
        return False

def _get_local_interfaces():
    """Return a list of local IP addresses for this machine."""
    # Currently a Linux specific hack. :-(
    addrs = []
    p = os.popen("/sbin/ifconfig")
    for line in p.readlines():
        m = re.search(r'inet (addr:)?(\d+\.\d+\.\d+\.\d+)', line)
        if m:
            addrs.append(m.group(2))
    return addrs

local_interfaces = _get_local_interfaces()

def parse_address(s):
    ip, port = s.strip().split(':')
    if not ip[0].isdigit():
        # not an IP address, resolve it
        ip = socket.gethostbyname(ip)
    return ip, port and int(port)

def is_local (address):
    ip, port = parse_address(address)
    return ip in local_interfaces

def get_dbspec (site=None):
    if site is None:
        site = os.environ.get('SITE', None)
    site_config = get_config()
    if site_config.has_option(site, 'zeo-address'):
        zeo_address = site_config.get(site, 'zeo-address')
        return 'client:%s,%s' % parse_address(zeo_address)
    return None

def get_root_exports (site=None):
    if site is None:
        site = os.environ.get('SITE', None)
    return get_config().get(site, 'root-exports').split()
    
def get_cache_size (site=None, default=10000):
    if site is None:
        site = os.environ.get('SITE', None)
    return int(get_config().get(site, 'cache-size', 10000))

_email_pat = re.compile(r'^(.*)\s+<(.*)>$')

def get_administrator_address(site=None):
    if site is None:
        administrator = _config.defaults().get('administrator')
    else:
        administrator = _config.get(site, 'administrator')
    m = _email_pat.match(administrator)
    if not m:
        raise ValueError, '"administrator" in site.conf missing real name'
    realname = m.group(1)
    email = m.group(2)
    return (email, realname)

def get_pid_file_name (daemon, site=None):
    name = '%s.pid' % daemon
    if site:
        name = site + '-' + name
    pid_dir = get_config().defaults().get('var-directory')
    return os.path.join(pid_dir, name)

def write_pid_file (daemon, site=None):
    file = open(get_pid_file_name(daemon, site), 'w')
    file.write(str(os.getpid()))
    file.close()

def change_uid_gid(uid, gid=None):
    "Try to change UID and GID to the provided values"
    # This will only work if this script is run by root.

    # Try to convert uid and gid to integers, in case they're numeric
    import pwd, grp
    try:
        uid = int(uid)
        default_grp = pwd.getpwuid(uid)[3]
    except ValueError:
        uid, default_grp = pwd.getpwnam(uid)[2:4]

    if gid is None:
        gid = default_grp
    else:
        try:
            gid = int(gid)
        except ValueError:
            gid = grp.getgrnam(gid)[2]

    os.setgid(gid)
    os.setuid(uid)


def ensure_uid_gid_not_root ():
    if os.getuid() == 0:
        uid = get_config().defaults().get('daemon-uid')
        change_uid_gid(uid)
    assert os.getuid() != 0

def log_to (log_file_name):
    log = open(log_file_name, "a", 1)
    os.dup2(log.fileno(), 1)
    os.dup2(log.fileno(), 2)
    os.close(0)

def _get_servername (site, mode):
    config = ConfigParser.ConfigParser()
    config.readfp(open(SITE_CONF))
    for section in config.sections():
        if section.split()[0] == site:
            if (config.has_option(section, 'mode') and
                config.get(section, 'mode') == mode):
                if config.has_option(section, 'servername'):
                    return config.get(section, 'servername')

def live_servername (site):
    return _get_servername(site, 'live')

def staging_servername (site):
    return _get_servername(site, 'staging')

def fetch_database (site, destination=None, source_file=None):
    """
    Pull a copy of the live database for site to the local
    machine using scp (using the daemon-uid on
    the remote host).
    If destination is given, it is the name of local file.
    If source file is given, it is a file name
    in the var-directory of the remote host.
    If neither of the keyword arguments are provided, the
    database is pulled from the standard place on the remote machine
    and put in the standard place on the local machine.
    The standard place is <site>.fs in the var-directory.
    """
    config = get_config()
    source_file = os.path.join(config.get(site, 'var-directory'),
                               source_file or ("%s.fs" % site))
    remote = (config.get(site, 'daemon-uid') + '@'
              + live_servername(site) + ':'
              + source_file)
    if not destination:
        destination = os.path.join(config.get(site, 'var-directory'),
                                   "%s.fs" % site)
    cmd = 'scp -C -p %s %s' % (remote, destination)
    print cmd
    os.system(cmd)
    return 'file:' + destination


class LazyModule:
    """A module-like object that is used for the 'local' and 'local_ui'
    modules.  It is necessary because we want to delay importing the site
    specific local modules until they are actually needed.
    """
    def __init__(self, module_name):
        self._loaded = False
        self.__name__ = module_name

    def __getattr__(self, name):
        if not self._loaded and not name.startswith('_'):
            site = os.environ.get('SITE')
            if site is None:
                raise RuntimeError, ("SITE must be set before "
                                     "accessing '%s' module" % self.__name__)
            module_name = site + '.' + self.__name__
            __import__(module_name)
            for n, v in vars(sys.modules[module_name]).items():
                if not n.startswith('_'):
                    setattr(self, n, v)
            self._loaded = True
        try:
            return self.__dict__[name]
        except KeyError:
            raise AttributeError, name

def setup_local_modules():
    """Create 'local' and 'local_ui' module-like objects.  Import of the actual
    site specific modules will be delayed until an attribute is accessed.
    """
    return LazyModule('local'), LazyModule('local_ui')
