########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Server/Server/GlobalConfig.py,v 1.9 2005/02/27 04:17:31 jkloth Exp $
"""
Collected stste from the global 4Suite repository config file, parsed in Ft.Server.Server.Lib.ConfigFile

Copyright 2003 Fourthought, Inc. (USA).
Detailed license and copyright information: http://4suite.org/COPYRIGHT
Project home, documentation, distributions: http://4suite.org/
"""

import sys, os
import distutils.util

import Ft
from Ft.Server.Server.FtRpc import FtRpcHandler
from Ft.Server.Server.Lib import LogUtil
from Ft.Server.Server.SCore import GetRepository

DEFAULT_LOG_FILE = 'Logs/error.log'
DEFAULT_PID_FILE = 'Logs/4ssd.pid'

# Number of servers to spawn off by default --- also, if fewer than
# this free when the caretaker checks, it will spawn more.
# Configuration entry: StartServers
DEFAULT_START_DAEMON = 5

# Maximum number of *free* server processes --- more than this, and
# they will die off.
# Configuration entry: MaxSpareServers
DEFAULT_MAX_FREE_DAEMON = 10

# Minimum --- fewer than this, and more will be created
# Configuration entry: MinSpareServers
DEFAULT_MIN_FREE_DAEMON = 5

# Limit on total number of servers running, i.e., limit on the number
# of clients who can simultaneously connect --- if this limit is ever
# reached, clients will be LOCKED OUT, so it should NOT BE SET TOO LOW.
# It is intended mainly as a brake to keep a runaway server from taking
# the system with it as it spirals down...
# Configuration entry: MaxServers
DEFAULT_MAX_DAEMONS = 150

# We keep a hard maximum number of servers --- in case something goes
# seriously wrong, we want to stop the fork bomb short of actually
# crashing the machine we're running on by filling some kernel table.
MAX_DAEMONS = 256

class GlobalConfig:
    """
    The configuration shared between the controller and the
    individual servers.
    """

    ident = 'Controller'

    def __init__(self, username, password, core, filename, debug=0):
        self.username = username
        self.password = password
        self.core = core
        self.filename = filename
        self.debug = debug

        self.properties = None

        self.savedPid = 0
        self.pidFile = None
        self.addresses = []

        self.serverRoot = Ft.GetConfigVar('DATADIR')

        # Default logging facility (allows logging before config is known)
        self.logFilename = sys.stderr.name
        self.logLevel = LogUtil.LOG_WARNING
        self.errorLog = LogUtil.Logger(self.ident, sys.stderr, self.logLevel)

        # Concurrency support
        self.daemons_to_start = DEFAULT_START_DAEMON      # StartServers
        self.daemons_min_free = DEFAULT_MIN_FREE_DAEMON   # MinSpareServers
        self.daemons_max_free = DEFAULT_MAX_FREE_DAEMON   # MaxSpareServers
        self.max_daemons = DEFAULT_MAX_DAEMONS            # MaxServers

        #XSLT extensions
        self.xslt_extension_modules = []
        return

    def readConfig(self):
        from Ft.Server.Server.Lib import ConfigFile
        config_dict = ConfigFile.Read(self.filename)
        if not self.core in config_dict:
            sys.stdout.write('The core "%s" (case sensitive) is not defined (%s).\n' % (self.core, config_dict.keys()))
            return
        self.properties = config_dict[self.core]

        # Break out a few of the values for easy access
        #pidfile = self.properties.get('PidFile', DEFAULT_PID_FILE)
        #pidfile = distutils.util.convert_path(pidfile)
        #self.pidFile = os.path.join(self.serverRoot, pidfile)
        self.pidFile = self.properties['PidFile']

        # This will be an empty list if nothing was in the configuration file.
        self.addresses = self.properties['ListenAddress']
        if not self.addresses:
            # Defaults to INADDR_ANY
            self.addresses.append('*')

        #logfile = self.properties.get('LogFile', DEFAULT_LOG_FILE)
        #logfile = distutils.util.convert_path(logfile)
        #self.logFilename = os.path.join(self.serverRoot, logfile)
        self.logFilename = self.properties['LogFile']

        if self.debug:
            self.logLevel = LogUtil.LOG_DEBUG
        elif self.properties.has_key('LogLevel'):
            self.logLevel = self.properties['LogLevel']

        if self.properties.has_key('StartServers'):
            self.daemons_to_start = self.properties['StartServers']

        if self.properties.has_key('MinSpareServers'):
            self.daemons_min_free = self.properties['MinSpareServers']

        if self.properties.has_key('MaxSpareServers'):
            self.daemons_max_free = self.properties['MaxSpareServers']

        if self.properties.has_key('MaxServers'):
            self.max_daemons = self.properties['MaxServers']

        # Don't thrash; make sure at least one spare daemon
        if self.daemons_max_free < self.daemons_min_free + 1:
            self.daemons_max_free = self.daemons_min_free + 1

        if self.max_daemons > MAX_DAEMONS:
            self.errorLog.warning('MaxServers of %d exceeds hard limit of %d '
                                  'servers,\nlowering MaxServers to %d.' %
                                  (self.max_daemons, MAX_DAEMONS))
            self.max_daemons = MAX_DAEMONS
        elif self.max_daemons < 1:
            self.errorLog.warning('Require MaxServers > 0, setting to 1')
            self.max_daemons = 1

        if self.daemons_to_start > self.max_daemons:
            self.daemons_to_start = self.max_daemons

        self.xslt_extension_modules = self.properties['XsltExtensionModule']
        return

    def openLogs(self):
        if self.debug:
            # Redirect our logging to stderr and force debug maxlevel
            self.errorLog = LogUtil.Logger(self.ident, sys.stderr,
                                           self.logLevel)
        else:
            self.errorLog = LogUtil.Logger(self.ident, self.logFilename,
                                           self.logLevel, showPid=1)
            # Redirect our standard streams to the log file
            sys.stdout = LogUtil.StreamLogger(self.errorLog, LogUtil.LOG_DEBUG)
            sys.stderr = LogUtil.StreamLogger(self.errorLog, LogUtil.LOG_ERROR)
        return

    def getRepository(self):
        return GetRepository(self.username, self.password, self.errorLog,
                             self.properties)


    # -- process control ---------------------------------------------

    def savePid(self):
        mypid = os.getpid()
        if os.path.exists(self.pidFile) and self.savedPid != mypid:
            self.errorLog.warning('PID file %s overwritten -- unclean '
                                  'shutdown of previous run?' % self.pidFile)
        try:
            fd = open(self.pidFile, 'w')
        except Exception, error:
            self.errorLog.critical("Unable to open pid file '%s': %s\n" %
                                   (self.pidFile, str(error)))
            sys.exit(1)

        try:
            fd.write(str(mypid))
        except Exception, error:
            self.errorLog.critical("Unable to write to pid file '%s': %s\n" %
                                 (self.pidFile, str(error)))
            try:
                fd.close()
            except:
                self.errorLog.error("Error closing file descriptor\n")
            sys.exit(1)

        try:
            fd.close()
        except:
            self.errorLog.error("Error closing file descriptor\n")
            sys.exit(1)

        self.savedPid = mypid
        return

    def removePid(self):
        try:
            if os.path.exists(self.pidFile):
                os.remove(self.pidFile)
                self.errorLog.info('removed PID file %s' % self.pidFile)
        except:
            self.errorLog.debug('Unable to remove file: %s' % self.pidFile)
        return

