]> code.delx.au - pymsnt/blobdiff - src/legacy/glue.py
Updated rev and url
[pymsnt] / src / legacy / glue.py
index 17474924cabe93dd0981c76bfa6192498997a535..cc3b30c36d340340f70c76cdcf94d2125a1c3f63 100644 (file)
@@ -1,29 +1,44 @@
-# Copyright 2004 James Bunton <james@delx.cjb.net>
+# Copyright 2004-2005 James Bunton <james@delx.cjb.net>
 # Licensed for distribution under the GPL version 2, check COPYING for details
 
-import utils
-from twisted.internet import task
-if(utils.checkTwisted()):
-       from twisted.xish.domish import Element
-else:
-       from tlib.domish import Element
-from tlib import msn
+import os.path
+from twisted.internet import task, error
+from twisted.words.xish.domish import Element
+
+from debug import LogEvent, INFO, WARN, ERROR
+from legacy import msn
+import disco
 import groupchat
-import msnw
+import ft
+import avatar
 import config
-import debug
 import lang
 
 
 
 
-name = "MSN Transport" # The name of the transport
-version = "0.9.5"      # The transport version
-mangle = True          # XDB '@' -> '%' mangling
-id = "msn"             # The transport identifier
+url = "http://delx.net.au/projects/pymsnt"
+version = "0.11.3"       # The transport version
+mangle = True            # XDB '@' -> '%' mangling
+id = "msn"               # The transport identifier
+
+
+# Load the default avatars
+f = open(os.path.join("data", "defaultJabberAvatar.png"), "rb")
+defaultJabberAvatarData = f.read()
+f.close()
 
+f = open(os.path.join("data", "defaultMSNAvatar.png"), "rb")
+defaultAvatarData = f.read()
+f.close()
+defaultAvatar = avatar.AvatarCache().setAvatar(defaultAvatarData)
 
 
+def reloadConfig():
+       msn.MSNConnection.GETALLAVATARS = config.getAllAvatars
+       msn.MSNConnection.BINDADDRESS = config.host
+       msn.setDebug(config._debugLevel >= 4)
+
 def isGroupJID(jid):
        """ Returns True if the JID passed is a valid groupchat JID (for MSN, does not contain '%') """
        return (jid.find('%') == -1)
@@ -34,7 +49,7 @@ def isGroupJID(jid):
 namespace = "jabber:iq:register"
 
 
-def formRegEntry(username, password, nickname):
+def formRegEntry(username, password):
        """ Returns a domish.Element representation of the data passed. This element will be written to the XDB spool file """
        reginfo = Element((None, "query"))
        reginfo.attributes["xmlns"] = "jabber:iq:register"
@@ -45,9 +60,6 @@ def formRegEntry(username, password, nickname):
        passEl = reginfo.addElement("password")
        passEl.addContent(password)
 
-       nickEl = reginfo.addElement("nick")
-       if(nickname): nickEl.addContent(nickname)
-       
        return reginfo
 
 
@@ -55,123 +67,202 @@ def formRegEntry(username, password, nickname):
 
 def getAttributes(base):
        """ This function should, given a spool domish.Element, pull the username, password,
-       and nickname out of it and return them """
+       and out of it and return them """
        username = ""
        password = ""
-       nickname = ""
        for child in base.elements():
                try:
-                       if(child.name == "username"):
+                       if child.name == "username":
                                username = child.__str__()
-                       elif(child.name == "password"):
+                       elif child.name == "password":
                                password = child.__str__()
-                       elif(child.name == "nick"):
-                               nickname = child.__str__()
                except AttributeError:
                        continue
        
-       return username, password, nickname
+       return username, password
 
 
+def startStats(statistics):
+       stats = statistics.stats
+       stats["MessageCount"] = 0
+       stats["FailedMessageCount"] = 0
+       stats["AvatarCount"] = 0
+       stats["FailedAvatarCount"] = 0
 
+def updateStats(statistics):
+       stats = statistics.stats
+       # FIXME
+       #stats["AvatarCount"] = msnp2p.MSNP2P_Avatar.TRANSFER_COUNT
+       #stats["FailedAvatarCount"] = msnp2p.MSNP2P_Avatar.ERROR_COUNT
 
 
-def msn2jid(msnid):
+msn2jid_cache = {}
+def msn2jid(msnid, withResource):
        """ Converts a MSN passport into a JID representation to be used with the transport """
-       return msnid.replace('@', '%') + "@" + config.jid
-
-translateAccount = msn2jid # Marks this as the function to be used in jabber:iq:gateway (Service ID Translation)
-
+       global msn2jid_cache
+       global jid2msn_cache
+
+       if msn2jid_cache.has_key(msnid):
+               jid = msn2jid_cache[msnid]
+               if withResource:
+                       jid += "/msn"
+               return jid
+       else:
+               if msnid.startswith("tel:+"):
+                       msnid = msnid.replace("tel:+", "") + "%tel"
+               jid = msnid.replace('@', '%') + "@" + config.jid
+               msn2jid_cache[msnid] = jid
+               jid2msn_cache[jid] = msnid
+               return msn2jid(msnid, withResource)
+
+# Marks this as the function to be used in jabber:iq:gateway (Service ID Translation)
+def translateAccount(msnid):
+       return msn2jid(msnid, False)
+
+jid2msn_cache = {}
 def jid2msn(jid):
        """ Converts a JID representation of a MSN passport into the original MSN passport """
-       return unicode(jid[:jid.find('@')].replace('%', '@'))
+       global jid2msn_cache
+       global msn2jid_cache
+
+       if jid2msn_cache.has_key(jid):
+               msnid = jid2msn_cache[jid]
+               return msnid
+       else:
+               if jid.find("%tel@") > 0:
+                       jid = "tel:+" + jid.replace("%tel@", "@")
+               msnid = unicode(jid[:jid.find('@')].replace('%', '@')).split("/")[0]
+               jid2msn_cache[jid] = msnid
+               msn2jid_cache[msnid] = jid
+               return msnid
 
 
 def presence2state(show, ptype): 
        """ Converts a Jabber presence into an MSN status code """
-       if(ptype == "unavailable"):
+       if ptype == "unavailable":
                return msn.STATUS_OFFLINE
-       elif(show in [None, "online", "chat"]):
+       elif not show or show == "online" or show == "chat":
                return msn.STATUS_ONLINE
-       elif(show in ["dnd"]):
+       elif show == "dnd":
                return msn.STATUS_BUSY
-       elif(show in ["away", "xa"]):
+       elif show == "away" or show == "xa":
                return msn.STATUS_AWAY
+       return msn.STATUS_ONLINE
 
 
 def state2presence(state):
        """ Converts a MSN status code into a Jabber presence """
-       if(state == msn.STATUS_ONLINE):
+       if state == msn.STATUS_ONLINE:
                return (None, None)
-       elif(state == msn.STATUS_BUSY):
+       elif state == msn.STATUS_BUSY:
                return ("dnd", None)
-       elif(state == msn.STATUS_AWAY):
+       elif state == msn.STATUS_AWAY:
                return ("away", None)
-       elif(state == msn.STATUS_IDLE):
+       elif state == msn.STATUS_IDLE:
                return ("away", None)
-       elif(state == msn.STATUS_BRB):
+       elif state == msn.STATUS_BRB:
                return ("away", None)
-       elif(state == msn.STATUS_PHONE):
+       elif state == msn.STATUS_PHONE:
                return ("dnd", None)
-       elif(state == msn.STATUS_LUNCH):
+       elif state == msn.STATUS_LUNCH:
                return ("away", None)
        else:
                return (None, "unavailable")
 
 
+def getGroupNames(msnContact, msnContactList):
+       """ Gets a list of groups that this contact is in """
+       groups = []
+       for groupGUID in msnContact.groups:
+               try:
+                       groups.append(msnContactList.groups[groupGUID])
+               except KeyError:
+                       pass
+       return groups
+
+def msnlist2jabsub(lists):
+       """ Converts MSN contact lists ORed together into the corresponding Jabber subscription state """
+       if lists & msn.FORWARD_LIST and lists & msn.REVERSE_LIST:
+               return "both"
+       elif lists & msn.REVERSE_LIST:
+               return "from"
+       elif lists & msn.FORWARD_LIST:
+               return "to"
+       else:
+               return "none"
+
+
+def jabsub2msnlist(sub):
+       """ Converts a Jabber subscription state into the corresponding MSN contact lists ORed together """
+       if sub == "to":
+               return msn.FORWARD_LIST
+       elif sub == "from":
+               return msn.REVERSE_LIST
+       elif sub == "both":
+               return (msn.FORWARD_LIST | msn.REVERSE_LIST)
+       else:
+               return 0
+
+
+
 
 
 # This class handles groupchats with the legacy protocol
 class LegacyGroupchat(groupchat.BaseGroupchat):
-       def __init__(self, session, resource, ID=None, existing=False, switchboardSession=None):
+       def __init__(self, session, resource=None, ID=None, switchboardSession=None):
                """ Possible entry points for groupchat
                        - User starts an empty switchboard session by sending presence to a blank room
                        - An existing switchboard session is joined by another MSN user
                        - User invited to an existing switchboard session with more than one user
                """
                groupchat.BaseGroupchat.__init__(self, session, resource, ID)
-               if(not existing):
-                       self.switchboardSession = msnw.GroupchatSwitchboardSession(self, makeSwitchboard=True)
-               else:
+               if switchboardSession:
                        self.switchboardSession = switchboardSession
+               else:
+                       self.switchboardSession = msn.MultiSwitchboardSession(self.session.legacycon)
+                       self.switchboardSession.groupchat = self
                        
-               assert(self.switchboardSession != None)
-               
-               debug.log("LegacyGroupchat: \"%s\" created" % (self.roomJID()))
+               LogEvent(INFO, self.roomJID())
        
        def removeMe(self):
-               self.switchboardSession.removeMe()
-               self.switchboardSession = None
+               if self.switchboardSession.transport:
+                       self.switchboardSession.transport.loseConnection()
+               self.switchboardSession.groupchat = None
+               del self.switchboardSession
                groupchat.BaseGroupchat.removeMe(self)
-               debug.log("LegacyGroupchat: \"%s\" destroyed" % (self.roomJID()))
-               utils.mutilateMe(self)
+               LogEvent(INFO, self.roomJID())
        
        def sendLegacyMessage(self, message, noerror):
-               debug.log("LegacyGroupchat: \"%s\" sendLegacyMessage(\"%s\")" % (self.roomJID(), message))
-               self.switchboardSession.sendMessage(message, noerror)
+               LogEvent(INFO, self.roomJID())
+               self.switchboardSession.sendMessage(message.replace("\n", "\r\n"), noerror)
        
        def sendContactInvite(self, contactJID):
-               debug.log("LegacyGroupchat: \"%s\" sendContactInvite(\"%s\")" % (self.roomJID(), contactJID))
+               LogEvent(INFO, self.roomJID())
                userHandle = jid2msn(contactJID)
                self.switchboardSession.inviteUser(userHandle)
+       
+       def gotMessage(self, userHandle, text):
+               LogEvent(INFO, self.roomJID())
+               self.messageReceived(userHandle, text)
 
 
 
 # This class handles most interaction with the legacy protocol
-class LegacyConnection(msnw.MSNConnection):
+class LegacyConnection(msn.MSNConnection):
        """ A glue class that connects to the legacy network """
        def __init__(self, username, password, session):
+               self.jabberID = session.jabberID
+
                self.session = session
                self.listSynced = False
                self.initialListVersion = 0
 
-               # Get the latest listVersion to pass to MSNConnection
-               result = self.session.pytrans.xdb.request(self.session.jabberID, "msn:listVersion")
-               if(result):
-                       self.initialListVersion = int(str(result))
+               self.remoteShow = ""
+               self.remoteStatus = ""
+               self.remoteNick = ""
 
                # Init the MSN bits
-               msnw.MSNConnection.__init__(self, username, password)
+               msn.MSNConnection.__init__(self, username, password, self.jabberID)
 
                # User typing notification stuff
                self.userTyping = dict() # Indexed by contact MSN ID, stores whether the user is typing to this contact
@@ -181,235 +272,419 @@ class LegacyConnection(msnw.MSNConnection):
                self.userTypingSend = task.LoopingCall(self.sendTypingNotifications)
                self.userTypingSend.start(5.0)
                
-               import subscription # Is in here to prevent an ImportError loop
-               self.subscriptions = subscription.SubscriptionManager(self.session)
+               self.legacyList = LegacyList(self.session)
        
-               debug.log("LegacyConnection: \"%s\" - created" % (self.session.jabberID))
+               LogEvent(INFO, self.jabberID)
        
        def removeMe(self):
-               debug.log("LegacyConnection: \"%s\" - being deleted" % (self.session.jabberID))
+               LogEvent(INFO, self.jabberID)
        
                self.userTypingSend.stop()
        
-               if(self.getContacts()):
-                       for userHandle in self.getContacts().getContacts():
-                               msnContact = self.getContacts().getContact(userHandle)
-                               if(msnContact.status and msnContact.status != msn.STATUS_OFFLINE):
-                                       msnContact.status = msn.STATUS_OFFLINE
-                                       self.sendMSNContactPresence(msnContact)
-               
-#              msnw.MSNConnection.changeStatus(self, msn.STATUS_OFFLINE, self.session.nickname) # Change our nickname on the MSN network (stops users from appearing offline with a nickname "james - Online")
-               
-               # Save to XDB the current list version (for fast logins)
-               if(self.notificationFactory and self.notificationFactory.contacts):
-                       listVersion = self.notificationFactory.contacts.version
-                       el = Element((None, "query"))
-                       el.addContent(str(listVersion))
-                       self.session.pytrans.xdb.set(self.session.jabberID, "msn:listVersion", el)
-               
-               msnw.MSNConnection.removeMe(self)
-               self.subscriptions.removeMe()
-               self.subscriptions = None
+               self.legacyList.removeMe()
+               self.legacyList = None
                self.session = None
-
-               utils.mutilateMe(self)
+               self.logOut()
        
-       def jidRes(self, resource):
-               to = self.session.jabberID
-               if(resource):
-                       to += "/" + resource
 
-               return to
+       # Implemented from baseproto
+       def sendShowStatus(self, jid=None):
+               if not self.session: return
+               source = config.jid
+               if not jid:
+                       jid = self.jabberID
+               self.session.sendPresence(to=jid, fro=source, show=self.remoteShow, status=self.remoteStatus, nickname=self.remoteNick)
        
+       def resourceOffline(self, resource):
+               pass
+
        def highestResource(self):
                """ Returns highest priority resource """
                return self.session.highestResource()
        
        def sendMessage(self, dest, resource, body, noerror):
                dest = jid2msn(dest)
-               if(self.userTyping.has_key(dest)):
+               if self.userTyping.has_key(dest):
                        del self.userTyping[dest]
-               msnw.MSNConnection.sendMessage(self, dest, resource, body, noerror)
+               try:
+                       msn.MSNConnection.sendMessage(self, dest, body, noerror)
+                       self.session.pytrans.statistics.stats["MessageCount"] += 1
+               except:
+                       self.failedMessage(dest, body)
+                       raise
        
-       def buildFriendly(self, status):
-               """ Constructs a friendly name from the user's registered nick, and their status message """
-               
-               if(not config.fancyFriendly):
-                       if(self.session.nickname and len(self.session.nickname) > 0):
-                               return self.session.nickname
-                       else:
-                               return self.session.jabberID[:self.session.jabberID.find('@')]
-               
-               if(self.session.nickname and len(self.session.nickname) > 0):
-                       friendly = self.session.nickname
-               else:
-                       friendly = self.session.jabberID[:self.session.jabberID.find('@')]
-               if(status and len(status) > 0):
-                       friendly += " - "
-                       friendly += status
-               if(len(friendly) > 127):
-                       friendly = friendly[:124] + "..."
-               debug.log("LegacyConnection: buildFriendly(%s) returning \"%s\"" % (self.session.jabberID, friendly))
-               return friendly
-       
-       def msnAlert(self, text, actionurl, subscrurl):
-               el = Element((None, "message"))
-               el.attributes["to"] = self.session.jabberID
-               el.attributes["from"] = config.jid
-               el.attributes["type"] = "headline"
-               body = el.addElement("body")
-               body.addContent(text)
-               
-               x = el.addElement("x")
-               x.attributes["xmlns"] = "jabber:x:oob"
-               x.addElement("desc").addContent("More information on this notice.")
-               x.addElement("url").addContent(actionurl)
-
-               x = el.addElement("x")
-               x.attributes["xmlns"] = "jabber:x:oob"
-               x.addElement("desc").addContent("Manage subscriptions to alerts.")
-               x.addElement("url").addContent(subscrurl)
+       def sendFile(self, dest, ftSend):
+               dest = jid2msn(dest)
+               def continueSendFile1((msnFileSend, d)):
+                       def continueSendFile2((success, )):
+                               if success:
+                                       ftSend.accept(msnFileSend)
+                               else:
+                                       sendFileFail()
+                       d.addCallbacks(continueSendFile2, sendFileFail)
+       
+               def sendFileFail():
+                       ftSend.reject()
 
-               self.session.pytrans.send(el)
+               d = msn.MSNConnection.sendFile(self, dest, ftSend.filename, ftSend.filesize)
+               d.addCallbacks(continueSendFile1, sendFileFail)
        
-       def setStatus(self, show, status):
+       def setStatus(self, nickname, show, status):
                statusCode = presence2state(show, None)
-               msnw.MSNConnection.changeStatus(self, statusCode, self.buildFriendly(status))
+               msn.MSNConnection.changeStatus(self, statusCode, nickname, status)
        
-       def newResourceOnline(self, resource):
-               self.sendLists(resource)
-       
-       def jabberSubscriptionReceived(self, source, subtype):
-               self.subscriptions.jabberSubscriptionReceived(source, subtype)
+       def updateAvatar(self, av=None):
+               global defaultJabberAvatarData
+
+               if av:
+                       msn.MSNConnection.changeAvatar(self, av.getImageData)
+               else:
+                       msn.MSNConnection.changeAvatar(self, lambda: defaultJabberAvatarData)
        
        def sendTypingNotifications(self):
+               if not self.session: return
+       
                # Send any typing notification messages to the user's contacts
                for contact in self.userTyping.keys():
-                       if(self.userTyping[contact]):
+                       if self.userTyping[contact]:
                                self.sendTypingToContact(contact)
 
                # Send any typing notification messages from contacts to the user
-               for contact, resource in self.contactTyping.keys():
-                       self.contactTyping[(contact, resource)] += 1
-                       if(self.contactTyping[(contact, resource)] >= 3):
-                               self.session.sendTypingNotification(self.jidRes(resource), msn2jid(contact), False)
-                               del self.contactTyping[(contact, resource)]
-       
-       def gotContactTyping(self, contact, resource):
-               # Check if the contact has only just started typing
-               if(not self.contactTyping.has_key((contact, resource))):
-                       self.session.sendTypingNotification(self.jidRes(resource), msn2jid(contact), True)
-
-               # Reset the counter
-               self.contactTyping[(contact, resource)] = 0
+               for contact in self.contactTyping.keys():
+                       self.contactTyping[contact] += 1
+                       if self.contactTyping[contact] >= 3:
+                               self.session.sendTypingNotification(self.jabberID, msn2jid(contact, True), False)
+                               del self.contactTyping[contact]
        
        def userTypingNotification(self, dest, resource, composing):
+               if not self.session: return
                dest = jid2msn(dest)
                self.userTyping[dest] = composing
-               if(composing): # Make it instant
+               if composing: # Make it instant
                        self.sendTypingToContact(dest)
        
+
+
+       # Implement callbacks from msn.MSNConnection
+       def connectionFailed(self, reason):
+               LogEvent(INFO, self.jabberID)
+               text = lang.get(self.session.lang).msnConnectFailed % reason
+               self.session.sendMessage(to=self.jabberID, fro=config.jid, body=text)
+               self.session.removeMe()
+
+       def loginFailed(self, reason):
+               LogEvent(INFO, self.jabberID)
+               text = lang.get(self.session.lang).msnLoginFailure % (self.session.username)
+               self.session.sendErrorMessage(to=self.jabberID, fro=config.jid, etype="auth", condition="not-authorized", explanation=text, body="Login Failure")
+               self.session.removeMe()
        
-       def sendMSNContactPresence(self, msnContact, to=None):
-               if(not to):
-                       to = self.session.jabberID
-               source = msn2jid(msnContact.userHandle)
-               show, ptype = state2presence(msnContact.status)
-               status = msnContact.screenName.decode("utf-8")
-               self.session.sendPresence(to=to, fro=source, show=show, status=status, ptype=ptype)
-       
-       def sendMSNUserPresence(self, userHandle, to=None):
-               msnContact = self.getContacts().getContact(userHandle)
-               if(msnContact):
-                       self.sendMSNContactPresence(msnContact, to)
-       
-       def sendLists(self, resource):
-               """ Sends a copy of the MSN contact presences to this resource """
-               debug.log("LegacyConnection: \"%s\" - sendLists(\"%s\")" % (self.session.jabberID, resource))
-               fulljid = self.session.jabberID
-               if(resource):
-                       fulljid += "/" + resource
-               if(self.getContacts()):
-                       for userHandle in self.getContacts().getContacts():
-                               self.sendMSNUserPresence(userHandle, fulljid)
-       
-       def listSynchronized(self):
-               if(self.session):
-                       self.session.sendPresence(to=self.session.jabberID, fro=config.jid)
-                       self.subscriptions.syncJabberLegacyLists()
-                       self.listSynced = True
-                       self.subscriptions.flushSubscriptionBuffer()
-       
-       def gotMessage(self, remoteUser, resource, text):
-               source = msn2jid(remoteUser)
-               self.session.sendMessage(self.jidRes(resource), fro=source, body=text, mtype="chat")
+       def connectionLost(self, reason):
+               LogEvent(INFO, self.jabberID)
+               if reason.type != error.ConnectionDone:
+                       text = lang.get(self.session.lang).msnDisconnected % reason
+                       self.session.sendMessage(to=self.jabberID, fro=config.jid, body=text)
+               self.session.removeMe() # Tear down the session
+
+       def multipleLogin(self):
+               LogEvent(INFO, self.jabberID)
+               self.session.sendMessage(to=self.jabberID, fro=config.jid, body=lang.get(self.session.lang).msnMultipleLogin)
+               self.session.removeMe()
        
-       def loggedIn(self):
-               if(self.session):
-                       debug.log("LegacyConnection: \"%s\" - loggedIn()" % (self.session.jabberID))
-                       self.session.ready = True
+       def serverGoingDown(self):
+               LogEvent(INFO, self.jabberID)
+               self.session.sendMessage(to=self.jabberID, fro=config.jid, body=lang.get(self.session.lang).msnMaintenance)
        
-       def contactStatusChanged(self, remoteUser):
-               if(self.session): # Make sure the transport isn't shutting down
-                       debug.log("LegacyConnection: \"%s\" - contactStatusChanged(\"%s\")" % (self.session.jabberID, remoteUser))
-                       self.sendMSNUserPresence(remoteUser)
-       
-       def ourStatusChanged(self, statusCode):
-               # Send out a new presence packet to the Jabber user so that the MSN-t icon changes
-               if(self.session):
-                       source = config.jid
-                       to = self.session.jabberID
-                       show, ptype = state2presence(statusCode)
-                       debug.log("LegacyConnection: \"%s\" - ourStatusChanged(\"%s\")" % (self.session.jabberID, statusCode))
-                       self.session.sendPresence(to=to, fro=source, show=show)
+       def accountNotVerified(self):
+               LogEvent(INFO, self.jabberID)
+               text = lang.get(self.session.lang).msnNotVerified % (self.session.username)
+               self.session.sendMessage(to=self.jabberID, fro=config.jid, body=text)
        
        def userMapping(self, passport, jid):
+               LogEvent(INFO, self.jabberID)
                text = lang.get(self.session.lang).userMapping % (passport, jid)
-               self.session.sendMessage(to=self.session.jabberID, fro=msn2jid(passport), body=text)
+               self.session.sendMessage(to=self.jabberID, fro=msn2jid(passport, True), body=text)
        
-       def userAddedMe(self, userHandle):
-               self.subscriptions.msnContactAddedMe(userHandle)
-       
-       def userRemovedMe(self, userHandle):
-               self.subscriptions.msnContactRemovedMe(userHandle)
+       def loggedIn(self):
+               LogEvent(INFO, self.jabberID)
        
-       def serverGoingDown(self):
-               if(self.session):
-                       self.session.sendMessage(to=self.session.jabberID, fro=config.jid, body=lang.get(self.session.lang).msnMaintenance)
+       def listSynchronized(self):
+               LogEvent(INFO, self.jabberID)
+               self.session.sendPresence(to=self.jabberID, fro=config.jid)
+               self.legacyList.syncJabberLegacyLists()
+               self.listSynced = True
+               self.session.ready = True
+               #self.legacyList.flushSubscriptionBuffer()
+       
+       def ourStatusChanged(self, statusCode, screenName, personal):
+               # Send out a new presence packet to the Jabber user so that the transport icon changes
+               LogEvent(INFO, self.jabberID)
+               self.remoteShow, ptype = state2presence(statusCode)
+               self.remoteStatus = personal
+               self.remoteNick = screenName
+               self.sendShowStatus()
+
+       def gotMessage(self, remoteUser, text):
+               LogEvent(INFO, self.jabberID)
+               source = msn2jid(remoteUser, True)
+               if self.contactTyping.has_key(remoteUser):
+                       del self.contactTyping[remoteUser]
+               self.session.sendMessage(self.jabberID, fro=source, body=text, mtype="chat")
+               self.session.pytrans.statistics.stats["MessageCount"] += 1
+       
+       def gotGroupchat(self, msnGroupchat, userHandle):
+               LogEvent(INFO, self.jabberID)
+               msnGroupchat.groupchat = LegacyGroupchat(self.session, switchboardSession=msnGroupchat)
+               msnGroupchat.groupchat.sendUserInvite(msn2jid(userHandle, True))
+       
+       def gotContactTyping(self, contact):
+               LogEvent(INFO, self.jabberID)
+               # Check if the contact has only just started typing
+               if not self.contactTyping.has_key(contact):
+                       self.session.sendTypingNotification(self.jabberID, msn2jid(contact, True), True)
+
+               # Reset the counter
+               self.contactTyping[contact] = 0
        
-       def multipleLogin(self):
-               if(self.session):
-                       self.session.sendMessage(to=self.session.jabberID, fro=config.jid, body=lang.get(self.session.lang).msnMultipleLogin)
-                       self.session.removeMe()
+       def failedMessage(self, remoteUser, message):
+               LogEvent(INFO, self.jabberID)
+               self.session.pytrans.statistics.stats["FailedMessageCount"] += 1
+               fro = msn2jid(remoteUser, True)
+               self.session.sendErrorMessage(to=self.jabberID, fro=fro, etype="wait", condition="recipient-unavailable", explanation=lang.get(self.session.lang).msnFailedMessage, body=message)
+       
+       def contactAvatarChanged(self, userHandle, hash):
+               LogEvent(INFO, self.jabberID)
+               jid = msn2jid(userHandle, False)
+               c = self.session.contactList.findContact(jid)
+               if not c: return
+
+               if hash:
+                       # New avatar
+                       av = self.session.pytrans.avatarCache.getAvatar(hash)
+                       if av:
+                               msnContact = self.getContacts().getContact(userHandle)
+                               msnContact.msnobjGot = True
+                               c.updateAvatar(av)
+                       else:
+                               def updateAvatarCB((imageData,)):
+                                       av = self.session.pytrans.avatarCache.setAvatar(imageData)
+                                       c.updateAvatar(av)
+                               d = self.sendAvatarRequest(userHandle)
+                               if d:
+                                       d.addCallback(updateAvatarCB)
+               else:
+                       # They've turned off their avatar
+                       global defaultAvatar
+                       c.updateAvatar(defaultAvatar)
        
-       def accountNotVerified(self):
-               if(self.session):
-                       text = lang.get(self.session.lang).msnNotVerified % (self.session.username)
-                       self.session.sendMessage(to=self.session.jabberID, fro=config.jid, body=text)
+       def contactStatusChanged(self, remoteUser):
+               LogEvent(INFO, self.jabberID)
+               
+               msnContact = self.getContacts().getContact(remoteUser)
+               c = self.session.contactList.findContact(msn2jid(remoteUser, False))
+               if not (c and msnContact): return
+
+               show, ptype = state2presence(msnContact.status)
+               status = msnContact.personal.decode("utf-8", "replace")
+               screenName = msnContact.screenName.decode("utf-8", "replace")
+
+               c.updateNickname(screenName, push=False)
+               c.updatePresence(show, status, ptype, force=True)
+       
+       def gotFileReceive(self, fileReceive):
+               LogEvent(INFO, self.jabberID)
+               # FIXME
+               ft.FTReceive(self.session, msn2jid(fileReceive.userHandle, True), fileReceive)
+       
+       def contactAddedMe(self, userHandle):
+               LogEvent(INFO, self.jabberID)
+               self.session.contactList.getContact(msn2jid(userHandle, False)).contactRequestsAuth()
+       
+       def contactRemovedMe(self, userHandle):
+               LogEvent(INFO, self.jabberID)
+               c = self.session.contactList.getContact(msn2jid(userHandle, True))
+               c.contactDerequestsAuth()
+               c.contactRemovesAuth()
+       
+       def gotInitialEmailNotification(self, inboxunread, foldersunread):
+               if config.mailNotifications:
+                       LogEvent(INFO, self.jabberID)
+                       text = lang.get(self.session.lang).msnInitialMail % (inboxunread, foldersunread)
+                       self.session.sendMessage(to=self.jabberID, fro=config.jid, body=text, mtype="headline")
+       
+       def gotRealtimeEmailNotification(self, mailfrom, fromaddr, subject):
+               if config.mailNotifications:
+                       LogEvent(INFO, self.jabberID)
+                       text = lang.get(self.session.lang).msnRealtimeMail % (mailfrom, fromaddr, subject)
+                       self.session.sendMessage(to=self.jabberID, fro=config.jid, body=text, mtype="headline")
+               
+       def gotMSNAlert(self, text, actionurl, subscrurl):
+               LogEvent(INFO, self.jabberID)
+
+               el = Element((None, "message"))
+               el.attributes["to"] = self.jabberID
+               el.attributes["from"] = config.jid
+               el.attributes["type"] = "headline"
+               body = el.addElement("body")
+               body.addContent(text)
+               
+               x = el.addElement("x")
+               x.attributes["xmlns"] = "jabber:x:oob"
+               x.addElement("desc").addContent("More information on this notice.")
+               x.addElement("url").addContent(actionurl)
+
+               x = el.addElement("x")
+               x.attributes["xmlns"] = "jabber:x:oob"
+               x.addElement("desc").addContent("Manage subscriptions to alerts.")
+               x.addElement("url").addContent(subscrurl)
+
+               self.session.pytrans.send(el)
        
-       def loginFailure(self, message):
-               if(self.session):
-                       text = lang.get(self.session.lang).msnLoginFailure % (self.session.username)
-                       self.session.sendErrorMessage(to=self.session.jabberID, fro=config.jid, etype="auth", condition="not-authorized", explanation=text, body="Login Failure")
+       def gotAvatarImageData(self, userHandle, imageData):
+               LogEvent(INFO, self.jabberID)
+               av = self.session.pytrans.avatarCache.setAvatar(imageData)
+               jid = msn2jid(userHandle, False)
+               c = self.session.contactList.findContact(jid)
+               c.updateAvatar(av)
        
-       def failedMessage(self, remoteUser, message):
-               if(self.session):
-                       fro = msn2jid(remoteUser)
-                       self.session.sendErrorMessage(to=self.session.jabberID, fro=fro, etype="wait", condition="recipient-unavailable", explanation=lang.get(self.session.lang).msnFailedMessage, body=message)
        
-       def initialEmailNotification(self, inboxunread, foldersunread):
-               text = lang.get(self.session.lang).msnInitialMail % (inboxunread, foldersunread)
-               self.session.sendMessage(to=self.session.jabberID, fro=config.jid, body=text, mtype="headline")
+
+
+class LegacyList:
+       def __init__(self, session):
+               self.jabberID = session.jabberID
+               self.session = session
        
-       def realtimeEmailNotification(self, mailfrom, fromaddr, subject):
-               text = lang.get(self.session.lang).msnRealtimeMail % (mailfrom, fromaddr, subject)
-               self.session.sendMessage(to=self.session.jabberID, fro=config.jid, body=text, mtype="headline")
+       def removeMe(self):
+               self.session = None
+
+       def addContact(self, jid):
+               LogEvent(INFO, self.jabberID)
+               userHandle = jid2msn(jid)
+               self.session.legacycon.addContact(msn.FORWARD_LIST, userHandle)
+
+               # Handle adding a contact that has previously been removed
+               msnContact = self.session.legacycon.getContacts().getContact(userHandle)
+               if msnContact and msnContact.lists & msn.REVERSE_LIST:
+                       self.session.legacycon.contactAddedMe(userHandle)
+               self.authContact(jid)
+               self.session.contactList.getContact(jid).contactGrantsAuth()
+       
+       def removeContact(self, jid):
+               LogEvent(INFO, self.jabberID)
+               jid = jid2msn(jid)
+               self.session.legacycon.remContact(msn.FORWARD_LIST, jid)
+       
+       
+       def authContact(self, jid):
+               LogEvent(INFO, self.jabberID)
+               userHandle = jid2msn(jid)
+               d = self.session.legacycon.remContact(msn.PENDING_LIST, userHandle)
+               if d:
+                       self.session.legacycon.addContact(msn.REVERSE_LIST, userHandle)
+               self.session.legacycon.remContact(msn.BLOCK_LIST, userHandle)
+               self.session.legacycon.addContact(msn.ALLOW_LIST, userHandle)
+       
+       def deauthContact(self, jid):
+               LogEvent(INFO, self.jabberID)
+               jid = jid2msn(jid)
+               self.session.legacycon.remContact(msn.ALLOW_LIST, jid)
+               self.session.legacycon.remContact(msn.PENDING_LIST, jid)
+               self.session.legacycon.addContact(msn.BLOCK_LIST, jid)
+
+
+
+       def syncJabberLegacyLists(self):
+               """ Synchronises the MSN contact list on server with the Jabber contact list """
+
+               global defaultAvatar
+
+               # We have to make an MSNContactList from the XDB data, then compare it with the one the server sent
+               # Any subscription changes must be sent to the client, as well as changed in the XDB
+               LogEvent(INFO, self.jabberID, "Start.")
+               result = self.session.pytrans.xdb.request(self.jabberID, disco.IQROSTER)
+               oldContactList = msn.MSNContactList()
+               if result:
+                       for item in result.elements():
+                               user = item.getAttribute("jid")
+                               sub = item.getAttribute("subscription")
+                               lists = item.getAttribute("lists")
+                               if not lists:
+                                       lists = jabsub2msnlist(sub) # Backwards compatible
+                               lists = int(lists)
+                               contact = msn.MSNContact(userHandle=user, screenName="", lists=lists)
+                               oldContactList.addContact(contact)
                
-       def connectionLost(self, reason):
-               if(self.session):
-                       debug.log("LegacyConnection: \"%s\" - connectionLost(\"%s\")" % (self.session.jabberID, reason))
-                       text = lang.get(self.session.lang).msnDisconnected % ("Error") # FIXME, a better error would be nice =P
-                       self.session.sendMessage(to=self.session.jabberID, fro=config.jid, body=text)
-                       self.session.removeMe() # Tear down the session
+               newXDB = Element((None, "query"))
+               newXDB.attributes["xmlns"] = disco.IQROSTER
+               
+               contactList = self.session.legacycon.getContacts()
+
 
+               # Convienence functions
+               def addedToList(num):
+                       return (not (oldLists & num) and (lists & num))
+               def removedFromList(num):
+                       return ((oldLists & num) and not (lists & num))
+               
+               for contact in contactList.contacts.values():
+                       # Compare with the XDB <item/> entry
+                       oldContact = oldContactList.getContact(contact.userHandle)
+                       if oldContact == None:
+                               oldLists = 0
+                       else:
+                               oldLists = oldContact.lists
+                       lists = contact.lists
+                       
+                       # Create the Jabber representation of the
+                       # contact base on the old list data and then
+                       # sync it with current
+                       jabContact = self.session.contactList.createContact(msn2jid(contact.userHandle, False), msnlist2jabsub(oldLists))
+                       jabContact.updateAvatar(defaultAvatar, push=False)
+
+                       if addedToList(msn.FORWARD_LIST):
+                               jabContact.syncGroups(getGroupNames(contact, contactList), push=False)
+                               jabContact.syncContactGrantedAuth()
+
+                       if removedFromList(msn.FORWARD_LIST):
+                               jabContact.syncContactRemovedAuth()
+
+                       if addedToList(msn.ALLOW_LIST):
+                               jabContact.syncUserGrantedAuth()
+
+                       if addedToList(msn.BLOCK_LIST) or removedFromList(msn.ALLOW_LIST):
+                               jabContact.syncUserRemovedAuth()
+
+                       if (not (lists & msn.ALLOW_LIST) and not (lists & msn.BLOCK_LIST) and (lists & msn.REVERSE_LIST)) or (lists & msn.PENDING_LIST):
+                               jabContact.contactRequestsAuth()
+
+                       if removedFromList(msn.REVERSE_LIST):
+                               jabContact.contactDerequestsAuth()
+
+                       jabContact.syncRoster()
+                       
+                       item = newXDB.addElement("item")
+                       item.attributes["jid"] = contact.userHandle
+                       item.attributes["subscription"] = msnlist2jabsub(lists)
+                       item.attributes["lists"] = str(lists)
+               
+               # Update the XDB
+               self.session.pytrans.xdb.set(self.jabberID, disco.IQROSTER, newXDB)
+               LogEvent(INFO, self.jabberID, "End.")
+       
+       def saveLegacyList(self):
+               contactList = self.session.legacycon.getContacts()
+               if not contactList: return
+
+               newXDB = Element((None, "query"))
+               newXDB.attributes["xmlns"] = disco.IQROSTER
+       
+               for contact in contactList.contacts.values():
+                       item = newXDB.addElement("item")
+                       item.attributes["jid"] = contact.userHandle
+                       item.attributes["subscription"] = msnlist2jabsub(contact.lists) # Backwards compat
+                       item.attributes["lists"] = str(contact.lists)
+
+               self.session.pytrans.xdb.set(self.jabberID, disco.IQROSTER, newXDB)
+               LogEvent(INFO, self.jabberID, "Finished saving list.")
+