#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-


# Glasnost
# By: Odile Bénassy <obenassy@entrouvert.com>
#     Romain Chantereau <rchantereau@entrouvert.com>
#     Nicolas Clapiès <nclapies@easter-eggs.org>
#     Pierre-Antoine Dejace <padejace@entrouvert.be>
#     Thierry Dulieu <tdulieu@easter-eggs.com>
#     Florent Monnier <monnier@codelutin.com>
#     Cédric Musso <cmusso@easter-eggs.org>
#     Frédéric Péters <fpeters@entrouvert.be>
#     Benjamin Poussin <poussin@codelutin.com>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#     Sébastien Régnier <regnier@codelutin.com>
#     Emmanuel Saracco <esaracco@easter-eggs.com>
#
# Copyright (C) 2000, 2001 Easter-eggs & Emmanuel Raviart
# Copyright (C) 2002 Odile Bénassy, Code Lutin, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Frédéric Péters, Benjamin Poussin, Emmanuel Raviart,
#     Emmanuel Saracco & Théridion
# Copyright (C) 2003 Odile Bénassy, Romain Chantereau, Nicolas Clapiès,
#     Code Lutin, Pierre-Antoine Dejace, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Florent Monnier, Cédric Musso, Ouvaton, Frédéric Péters,
#     Benjamin Poussin, Rodolphe Quiédeville, Emmanuel Raviart, Sébastien
#     Régnier, Emmanuel Saracco, Théridion & Vecam
#
# 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


__doc__ = """Glasnost Sessions Server"""

__version__ = '$Revision$'[11:-2]


import cPickle
import sys
import time
import whrandom

glasnostPythonDir = '/usr/local/lib/glasnost-devel' # changed on make install
sys.path.insert(0, glasnostPythonDir)

import glasnost

import glasnost.common.faults as faults
import glasnost.common.tools_new as commonTools

from glasnost.server.ObjectsServer import Server, VirtualServer
from glasnost.server.tools import *


applicationName = 'SessionsServer'
applicationRole = 'sessions'
dispatcher = None

# Don't forget to also change the value in IdentitiesServer.py.
expirationTime = 120 * 60


class SessionsVirtualServer(VirtualServer):
    objects = None

    def init(self):
        VirtualServer.init(self)
        self.objects = {None: []}

    def initFromOldData(self, data):
        VirtualServer.initFromOldData(self, data)
        self.objects = data


class SessionsServer(Server):
    VirtualServer = SessionsVirtualServer
    temporaryData = None

    def addTemporaryData(self, sessionToken, data):
        if not self.temporaryData:
            self.temporaryData = {}
        while 1:
            dataToken = str(self.randomGenerator.uniform(0.1, 1))
            if not self.temporaryData.has_key(dataToken):
                break
        self.temporaryData[sessionToken+dataToken] = data
        return dataToken

    def getTemporaryData(self, sessionToken, dataToken):
        if not self.temporaryData or \
                not self.temporaryData.has_key(sessionToken+dataToken):
            return None
        return self.temporaryData[sessionToken+dataToken]

    def delTemporaryData(self, sessionToken, dataToken):
        if not self.temporaryData or \
                not self.temporaryData.has_key(sessionToken+dataToken):
            return None
        del self.temporaryData[sessionToken+dataToken]

    def convertVirtualServersIds(self, sourceDispatcherId,
                                 destinationDispatcherId):
        # Erase the sessions when converting ids.
        return 0

    def deleteObject(self, sessionToken):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        virtualServer.lock.acquire()
        if not virtualServer.objects.has_key(sessionToken):
            virtualServer.lock.release()
            raise faults.UnknownSessionToken(sessionToken)
        self.rememberOldSession(virtualServer,
                virtualServer.objects[sessionToken])
        del virtualServer.objects[sessionToken]
        if self.temporaryData:
            for k in self.temporaryData.keys():
                if k.startswith(sessionToken):
                    del self.temporaryData[k]
        virtualServer.lock.release()
        virtualServer.markCoreAsDirty()
        #invalidateValue(no id!)

    def getObject(self, sessionToken, ipAddress):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        try:
            int(sessionToken)
        except (ValueError, TypeError):
            raise faults.UnknownSessionToken(repr(sessionToken))
        if not virtualServer.objects.has_key(sessionToken):
            raise faults.UnknownSessionToken(sessionToken)
        session = virtualServer.objects[sessionToken]
        if time.time() > session['expirationTime']:
            self.deleteObject(sessionToken)
            raise faults.UnknownSessionToken(sessionToken)
        if session['ipAddress'] != ipAddress:
            raise faults.InvalidSessionToken(sessionToken)
        session['endTime'] = time.time()
        session['expirationTime'] = time.time() + expirationTime
        virtualServer.markCoreAsDirty()
        return session

    def getHistory(self):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        identitiesProxy = getProxyForServerRole('identities')
        if not identitiesProxy.isAdmin(
                serverId = commonTools.extractServerId(virtualServerId)):
            raise faults.UserAccessDenied()

        now = time.time()
        for sessionToken, session in virtualServer.objects.items():
            if sessionToken is None:
                continue
            if not session.has_key('userId'):
                continue
            if session['expirationTime'] < now:
                self.rememberOldSession(virtualServer,
                        virtualServer.objects[sessionToken])
                del virtualServer.objects[sessionToken]

        result = self.loadOldSessions(virtualServer)
        
        for sessionToken, session in virtualServer.objects.items():
            if sessionToken is None:
                continue
            if not session.has_key('userId'):
                continue
            activeSession = {}
            for k in ('startTime', 'expirationTime', 'ipAddress', 'userId'):
                activeSession[k] = session[k]
            result.append(activeSession)
        return result

    def init(self):
        self.randomGenerator = whrandom.whrandom()
        Server.init(self)

    def loadConfigOptions(self):
        Server.loadConfigOptions(self)

        # Default expiration time is 120 minutes.
        global expirationTime
        expirationTime = int(
                commonTools.getConfig('Misc', 'ExpirationTime', '120')) * 60

    def loadOldSessions(self, virtualServer):
        rcFilePath = os.path.join(
                virtualServer.dataDirectoryPath,
                self.applicationName + '.oldSessions.pickle')
        try:
            fd = open(rcFilePath)
        except IOError:
            return []
        oldSessions = []
        while 1:
            try:
                oldSession = cPickle.load(fd)
            except EOFError:
                break
            oldSessions.append(oldSession)
        fd.close()
        return oldSessions

    def newObject(self, ipAddress):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        virtualServer.lock.acquire()
        while 1:
            sessionToken = str(self.randomGenerator.uniform(0.1, 1))[2:]
            if not virtualServer.objects.has_key(sessionToken):
                break
        session = {
            'sessionToken': sessionToken,
            'version': 0,
            'startTime': time.time(),
            'endTime': time.time(),
            'expirationTime': time.time() + expirationTime,
            'ipAddress': ipAddress,
            }
        virtualServer.objects[sessionToken] = session
        virtualServer.lock.release()
        virtualServer.markCoreAsDirty()
        return session

    def registerPublicMethods(self):
        Server.registerPublicMethods(self)
        self.registerPublicMethod('deleteObject')
        self.registerPublicMethod('getHistory')
        self.registerPublicMethod('getObject')
        self.registerPublicMethod('newObject')
        self.registerPublicMethod('setObject')
        self.registerPublicMethod('addTemporaryData')
        self.registerPublicMethod('getTemporaryData')
        self.registerPublicMethod('delTemporaryData')

    def rememberOldSession(self, virtualServer, session):
        if not session.has_key('userId'):
            return
        oldSession = {}
        for k in ('startTime', 'endTime', 'ipAddress', 'userId'):
            oldSession[k] = session[k]

        rcFilePath = os.path.join(
                virtualServer.dataDirectoryPath,
                self.applicationName + '.oldSessions.pickle')
        virtualServer.lock.acquire()
        rcFile = open(rcFilePath, 'a')
        cPickle.dump(oldSession, rcFile)
        rcFile.close()
        os.chmod(rcFilePath, 0640)
        virtualServer.lock.release()

    def upgradeVirtualServer(self, virtualServer, fileVersionNumber):
        Server.upgradeVirtualServer(self, virtualServer, fileVersionNumber)

        changed = 0
        now = time.time()

        if virtualServer.objects.has_key(None):
            for oldSession in virtualServer.objects[None]:
                self.rememberOldSession(virtualServer, oldSession)
            del virtualServer.objects[None]
            changed = 1

        for sessionToken, session in virtualServer.objects.items():
            if sessionToken is None:
                continue
            self.rememberOldSession(virtualServer, session)
            del virtualServer.objects[sessionToken]
            changed = 1
        
        if changed:
            virtualServer.markAllAsDirtyFIXME()
            self.saveVirtualServer(virtualServer)

    def setObject(self, sessionChanges):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        if not sessionChanges.has_key('sessionToken'):
            raise faults.UnknownSessionToken(None)
        sessionToken = sessionChanges['sessionToken']
        virtualServer.lock.acquire()
        if not virtualServer.objects.has_key(sessionToken):
            virtualServer.lock.release()
            raise faults.UnknownSessionToken(sessionToken)
        session = virtualServer.objects[sessionToken]
        if session.has_key('userId') and \
                not sessionChanges.has_key('userId'):
            # the user logged out
            self.rememberOldSession(virtualServer, session)
        version = session['version']
        if version != sessionChanges['version']:
            virtualServer.lock.release()
            raise faults.WrongVersion()
        session = sessionChanges
        version += 1
        session['version'] = version
        virtualServer.objects[sessionToken] = session
        virtualServer.lock.release()
        virtualServer.markCoreAsDirty()
        #invalidateValue(no id!)
        return version


sessionsServer = SessionsServer()


if __name__ == "__main__":
    sessionsServer.launch(applicationName, applicationRole)

