#!/usr/bin/python
#  Copyright (C) 2004  Henning Jacobs <henning@srcco.de>
#
#  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.
#
#  $Id: CoCuMa_Server.py 92 2004-11-28 15:34:44Z henning $

import SimpleXMLRPCServer, SocketServer
from types import *
import time
import os
import vcard
import vcalendar
from __version__ import __version__
import sys, signal
import Preferences
import debug
import broadcaster

class XMLRPCServer(SocketServer.TCPServer, SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
    """Overridden SimpleXMLRPCServer

    We want allow_reuse_address==True"""
    def __init__(self, addr, requestHandler=SimpleXMLRPCServer.SimpleXMLRPCRequestHandler,
                 logRequests=1):
        self.logRequests = logRequests
        self.allow_reuse_address = 1

        SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self)
        SocketServer.TCPServer.__init__(self, addr, requestHandler)

class CoCuMa_Server:
    def __init__(self, addressbook_fname, calendar_fname):
        self._cards_modified = False
        self._cal_modified = False
        self._addressbook_filename = addressbook_fname 
        self._calendar_filename = calendar_fname 
        broadcaster.Broadcast('Notification', 'Status', 
            {'message':"Loading vCards from file '%s'..." % (self._addressbook_filename,)})
        self._vcards = vcard.vCardList()
        self._vcards.LoadFromFile(self._addressbook_filename)
        broadcaster.Broadcast('Notification', 'Status', 
            {'message':"Loading iCalendar from file '%s'..." % (self._calendar_filename,)})
        self._vcalendar = vcalendar.vCalendar()
        self._vcalendar.LoadFromFile(self._calendar_filename)
    def _writeToDisk(self):
        "save our vcards and our vcalendar on disk"
        if self._cards_modified:
            self._vcards.SaveToFile(self._addressbook_filename)
        if self._cal_modified:
            self._vcalendar.SaveToFile(self._calendar_filename)
    def _shutdown(self):
        "Server Stop"
        # saving is now done on session exit:
        # Redundant: self._writeToDisk()
        pass
    def _signal_handler(self, signalnum, stackframe):
        "Trap Unix Signals"
        if signalnum == signal.SIGTERM:
            self._shutdown()
            sys.exit(0)
    def SessionInit(self):
        "Send My Identification to Client" 
        return "CoCuMa_Server "+__version__
    def SessionQuit(self):
        "Exit Session and save to disk"
        self._writeToDisk()
        return True
    def ListHandles(self, sortby=None):
        """Returns list of vCard Handles
        sortby is the fieldname to order by"""
        return self._vcards.sortedlist(sortby)
    def ListJournalHandles(self, sortby):
        "Returns list of vCalendar Handles"
        return self._vcalendar.sortedlist(sortby)
          
    def QueryAttributes(self, handles, attributes):
        """Returns a list of tuples of attribute values 
        or a single list of strings if attributes is not a list or tuple"""
        return self._queryAttributes(self._vcards, handles, attributes)
    def QueryJournalAttributes(self, handles, attributes):
        """Returns a list of tuples of attribute values 
        or a single list of strings if attributes is not a list or tuple"""
        return self._queryAttributes(self._vcalendar, handles, attributes)
    def _queryAttributes(self, vobj, handles, attributes):      
        """Returns a list of tuples of attribute values 
        or a single list of strings if attributes is not a list or tuple"""
        ret = []
        try:
            if type(attributes)==ListType or type(attributes)==TupleType:
                for handle in handles:
                    attrvals = []
                    for attr in attributes:
                        attrvals.append(vobj[handle].getFieldValueStr(attr))
                    ret.append(tuple(attrvals))
            else:
                for handle in handles:
                    ret.append(vobj[handle].getFieldValueStr(attributes))
            vobj.forgetLastReturnedField()
        finally:
            return ret
    def GetContact(self, handle):
        "Returns Contact as vCard"
        return self._vcards[handle].VCF_repr()
    def PutContact(self, handle, data):
        "Store Contact (overwrite)"
        self._vcards[handle] = vcard.vCard(data)
        # Our modified Contact lost its Handle:
        self._vcards[handle].sethandle(handle)
        # verify that our storing was correct:
        verify = self._vcards[handle].VCF_repr() == data
        if verify:
                # Update our Revision Date:
                self._vcards[handle].rev.set(time.gmtime())
                self._cards_modified = True
        return verify
    def NewContact(self, initdict={}):
        "Create new vCard and return it's handle"
        card = vcard.vCard()
        # Set initial dictionary:
        for key, value in zip(initdict.keys(), initdict.values()):
            getattr(card, key).set(value)
        self._cards_modified = True
        return self._vcards.add(card)
    def DelContact(self, handle):
        "Delete Contact"
        self._cards_modified = True
        return self._vcards.delete(handle)
        
    def GetJournal(self, handle):
        "Returns Calendar Entry as vEvent"
        # Update our TimeStamp:
        self._vcalendar[handle].dtstamp.set(time.gmtime())
        return self._vcalendar[handle].VCF_repr()
    def PutJournal(self, handle, data):
        "Store Calendar Entry (overwrite)"
        self._vcalendar[handle] = vcalendar.vEvent(data)
        # Our modified Journal lost its Handle:
        self._vcalendar[handle].sethandle(handle)
        # verify that our storing was correct:
        verify = self._vcalendar[handle].VCF_repr() == data
        if verify:
            # Update our Revision Date:
            self._vcalendar[handle].last_mod.set(time.gmtime())
            self._cal_modified = True
        else:
            debug.echo("WARNING: CoCuMa_Server.PutJournal(): Verify Failed for #%d:" % handle)
            debug.echo(self._vcalendar[handle].VCF_repr())
        return verify
    def NewJournal(self, initdict={}):
        "Create new vEvent and return it's handle"
        jour = vcalendar.vEvent()
        # Set initial dictionary:
        for key, value in zip(initdict.keys(), initdict.values()):
            getattr(jour, key).set(value)
        self._cal_modified = True
        return self._vcalendar.add(jour)
    def DelJournal(self, handle):
        "Delete Calendar Entry"
        self._cards_modified = True
        return self._vcalendar.delete(handle)
        
class LogFile:
    "file-like class, appends to a file, or does nothing"
    def __init__(self, filename):
        self.fd = None
        if filename:
            self.fd = file(filename, 'ab')
    def write(self, obj):
        if self.fd:
            self.fd.write(obj)
            self.fd.flush()
                
def run():
    Preferences.Load()
    import getopt
    optlist, args = getopt.getopt(sys.argv[1:], "h:p:f:j:")
    for key, val in optlist:
        # Command Line Arguments override Preferences:
        if key == "-p": Preferences.set("server.listen_port",val)
        if key == "-h": Preferences.set("server.listen_host",val)
        if key == "-f": Preferences.set("server.addressbook_filename",val)
        if key == "-j": Preferences.set("server.calendar_filename",val)

    import socket
    try:
        xmlsrv = XMLRPCServer((Preferences.get("server.listen_host"),
                           int(Preferences.get("server.listen_port"))))
    except socket.error:
        # Try to connect to ourself (check if we are already running):
        import CoCuMa_Client
        conn_str = "http://%s:%d" % (Preferences.get("server.listen_host"),
                                 int(Preferences.get("server.listen_port")))
        client = CoCuMa_Client.CoCuMa_XMLRPCClient()
        if client.Connect(conn_str):
            sys.exit("CoCuMa_Server.run(): I'm already running. Terminating!")
        else:
            raise # Unhandled/unknown Error

    # Create our Server-Object:
    server = CoCuMa_Server(addressbook_fname = os.path.expanduser(
                             Preferences.get("server.addressbook_filename")),
                           calendar_fname = os.path.expanduser(
                             Preferences.get("server.calendar_filename")))
                             
    # Register our public (XML) accessible Methods:
    xmlsrv.register_instance(server)

    # Trap UNIX-Signals:
    signal.signal(signal.SIGTERM, server._signal_handler)
    
    # Redirect Stderr and Stdout:
    log = LogFile(Preferences.get("server.log_filename"))
    sys.stderr = log
    sys.stdout = log
    
    xmlsrv.serve_forever()
        
if __name__=='__main__':
    run()
