#!/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_Client.py 82 2004-07-11 13:01:44Z henning $

from __version__ import __version__
import debug

class CoCuMa_AbstractClient:
    "Abstract Base Client"
    def __init__(self):
        self.server = None
        self.connected = False
        self.errorstring = ""
    def getErrorString(self):
        return self.errorstring
    def Connect(self, connect_str):
        "Connect to Server and start Session"
        raise NotImplementedError
    def Disconnect(self):
        "Disconnect from Server = Exit Session" 
        raise NotImplementedError
    def ListHandles(self, sortby):
        "Returns List of Handles"
        raise NotImplementedError
    def QueryAttributes(self, handles, attributes):
        "Returns a list of a list of attributes"
        raise NotImplementedError
    def GetContact(self, handle):
        "Returns Contact in vCard Format"
        raise NotImplementedError
    def PutContact(self, handle, data):
        "Store Contact and overwrite previous one"
        raise NotImplementedError
    def NewContact(self, initdict={}):
        "Create new Contact and return Handle"
        raise NotImplementedError
    def DelContact(self, handle):
        "Remove Contact"
        raise NotImplementedError
    def ListJournalHandles(self, sortby):
        "Returns List of Handles"
        raise NotImplementedError
    def QueryJournalAttributes(self, handles, attributes):
        "Returns a list of a list of attributes"
        raise NotImplementedError
    def GetJournal(self, handle):
        "Returns Contact in vEvent Format"
        raise NotImplementedError
    def PutJournal(self, handle, data):
        "Store Journal and overwrite previous one"
        raise NotImplementedError
    def NewJournal(self, initdict={}):
        "Create new Contact and return Handle"
        raise NotImplementedError
    def DelJournal(self, handle):
        "Remove Contact"
        raise NotImplementedError
                
class CoCuMa_XMLRPCClient(CoCuMa_AbstractClient):
    "Client to the XMLRPC Server (CoCuMa_Server.py)"
    def __init__(self):
        CoCuMa_AbstractClient.__init__(self)
        self.cache_handles = {}
        self.cache_contacts = {}
        self.cache_journalhandles = {}
        self.cache_journals = {}
    def Connect(self, connect_str="http://localhost:8810"):
        "Connect to Server and start Session"
        self.errorstring = ""
        import xmlrpclib, socket
        retries = 4 # four connection retries
        for i in range(retries):
            try:
                self.server = xmlrpclib.ServerProxy(connect_str)
                ver = self.server.SessionInit()
                if ver == "CoCuMa_Server "+__version__:
                    self.connected = True
                    return True # Alright, we made it!
                else:
                    self.errorstring = "Version Mismatch"
                    debug.echo("CoCuMa_XMLRPCClient.Connect(): Could not connect to server: %s" % self.errorstring)
                    # We don't need to retry,
                    # because the server's version won't change ;-)
                    return False 
            except socket.error, detail:
                errno, self.errorstring = detail
                if errno != 111: return False
            except Exception, detail:
                self.errorstring = detail
            debug.echo("CoCuMa_XMLRPCClient.Connect(): Retrying connection to server..")
            import time
            # Exponential Backoff (wait up to 1.6 sec):
            time.sleep(0.2*(2**i))
        return False
    def Disconnect(self):
        "Disconnect from Server = Exit Session" 
        self.server.SessionQuit()
        self.server = None
        self.connected = False
    def ListHandles(self, sortby):
        "Returns List of Handles"
        if self.connected:
            if not self.cache_handles.has_key(sortby):
                self.cache_handles[sortby] = self.server.ListHandles(sortby)
            return self.cache_handles[sortby]
        else:
            return None
    def QueryAttributes(self, handles, attributes):
        "Returns a list of a list of attributes"
        if self.connected:
                return self.server.QueryAttributes(handles, attributes)
        else:   return None
    def GetContact(self, handle):
        "Returns Contact in vCard Format"
        try:
            return self.cache_contacts[handle]
        except:
            if self.connected:
                data = self.server.GetContact(handle)
                self.cache_contacts[handle] = data
                return data
            else: 
                return None
    def PutContact(self, handle, data):
        "Store Contact and overwrite previous one"
        if self.connected:
            try:
                self.cache_handles.clear()
                del self.cache_contacts[handle]
            except:
                pass
            return self.server.PutContact(handle, data)
        else:   return None
    def NewContact(self, initdict={}):
        "Create new Contact and return Handle"
        if self.connected:
            self.cache_handles.clear()
            return self.server.NewContact(initdict)
        else:   return None
    def DelContact(self, handle):
        "Remove Contact"
        if self.connected:
            try:
                del self.cache_contacts[handle]
            except:
                pass
            self.cache_handles.clear()
            return self.server.DelContact(handle)
        else:
            return None
    def ListJournalHandles(self, sortby):
        "Returns List of Handles"
        if self.connected:
            if not self.cache_journalhandles.has_key(sortby):
                self.cache_journalhandles[sortby] = self.server.ListJournalHandles(sortby)
            return self.cache_journalhandles[sortby]
        else:
            return None
    def QueryJournalAttributes(self, handles, attributes):
        "Returns a list of a list of attributes"
        if self.connected:
                return self.server.QueryJournalAttributes(handles, attributes)
        else:   return None
    def GetJournal(self, handle):
        "Returns Contact in vEvent Format"
        try:
            return self.cache_journals[handle]
        except:
            if self.connected:
                data = self.server.GetJournal(handle)
                self.cache_journals[handle] = data
                return data
            else: 
                return None
    def PutJournal(self, handle, data):
        "Store Journal and overwrite previous one"
        if self.connected:
            try:
                self.cache_journalhandles.clear()
                del self.cache_journals[handle]
            except:
                pass
            return self.server.PutJournal(handle, data)
        else:   return None
    def NewJournal(self, initdict={}):
        "Create new Journal and return Handle"
        if self.connected:
            self.cache_journalhandles.clear()
            return self.server.NewJournal(initdict)
        else:   return None
    def DelJournal(self, handle):
        "Remove Journal"
        if self.connected:
            try:
                del self.cache_journals[handle]
            except:
                pass
            self.cache_journalhandles.clear()
            return self.server.DelJournal(handle)
        else:
            return None
                                
class CoCuMa_FileClient(CoCuMa_AbstractClient):
    "Ordinary Filesystem Backend"
    def __init__(self):
        CoCuMa_AbstractClient.__init__(self)
    def Connect(self, connect_str):
        "Connect to Server and start Session"
        try:
            try:
                import os.path
                from CoCuMa_Server import CoCuMa_Server
                # XXX: Not very nice to use '<abook-fname>.ics':
                root, ext = os.path.splitext(connect_str)
                self.server = CoCuMa_Server(addressbook_fname = connect_str,
                                            calendar_fname = root+'.ics')
                self.server.SessionInit()
                self.connected = True
            except Exception, detail:
                self.errorstring = detail 
        finally:
            return self.connected
    def Disconnect(self):
        "Disconnect from Server = Exit Session" 
        self.server.SessionQuit()
        self.server = None
        self.connected = False
    def ListHandles(self, sortby):
        "Returns List of Handles"
        if self.connected:
            return self.server.ListHandles(sortby)
        else: return None
    def QueryAttributes(self, handles, attributes):
        "Returns a list of a list of attributes"
        if self.connected:
            return self.server.QueryAttributes(handles, attributes)
        else: return None
    def GetContact(self, handle):
        "Returns Contact in vCard Format"
        if self.connected:
            return self.server.GetContact(handle)
        else: return None
    def PutContact(self, handle, data):
        "Store Contact and overwrite previous one"
        if self.connected:
            return self.server.PutContact(handle, data)
        else: return None
    def NewContact(self, initdict={}):
        "Create new Contact and return Handle"
        if self.connected:
            return self.server.NewContact(initdict)
        else: return None
    def DelContact(self, handle):
        "Remove Contact"
        if self.connected:
            return self.server.DelContact(handle)
        else: return None
    def ListJournalHandles(self, sortby):
        "Returns List of Handles"
        if self.connected:
            return self.server.ListJournalHandles(sortby)
        else: return None
    def QueryJournalAttributes(self, handles, attributes):
        "Returns a list of a list of attributes"
        if self.connected:
            return self.server.QueryJournalAttributes(handles, attributes)
        else: return None
    def GetJournal(self, handle):
        "Returns Journal in vCard Format"
        if self.connected:
            return self.server.GetJournal(handle)
        else: return None
    def PutJournal(self, handle, data):
        "Store Journal and overwrite previous one"
        if self.connected:
            return self.server.PutJournal(handle, data)
        else: return None
    def NewJournal(self, initdict={}):
        "Create new Journal and return Handle"
        if self.connected:
            return self.server.NewJournal(initdict)
        else: return None
    def DelJournal(self, handle):
        "Remove Journal"
        if self.connected:
            return self.server.DelJournal(handle)
        else: return None
                
def Instantiate(con_type):
    "Instantiate Client Object depending on Connection Type"
    if con_type == 'xmlrpc':
        return CoCuMa_XMLRPCClient()
    elif con_type == 'file':
        return CoCuMa_FileClient()
    else:
        return None

