]> code.delx.au - pymsnt/commitdiff
Recoding middle-ware wrapper stuff.
authorjamesbunton <jamesbunton@55fbd22a-6204-0410-b2f0-b6c764c7e90a>
Sun, 11 Dec 2005 14:32:12 +0000 (14:32 +0000)
committerjamesbunton <jamesbunton@55fbd22a-6204-0410-b2f0-b6c764c7e90a>
Sun, 11 Dec 2005 14:32:12 +0000 (14:32 +0000)
git-svn-id: http://delx.cjb.net/svn/pymsnt/trunk@49 55fbd22a-6204-0410-b2f0-b6c764c7e90a

committer: jamesbunton <jamesbunton@55fbd22a-6204-0410-b2f0-b6c764c7e90a>

src/ft.py
src/legacy/glue.py
src/legacy/msnw.py [deleted file]
src/tlib/msn/__init__.py
src/tlib/msn/msn.py
src/tlib/msn/msnw.py [new file with mode: 0644]
src/tlib/msn/test_msn.py
src/tlib/msn/test_msnw.py [new file with mode: 0644]

index ec983595bcaea4b5d3c6df1fc4546f51170c5544..a671fba143b49b244790f44dd8ca2a873d72607e 100644 (file)
--- a/src/ft.py
+++ b/src/ft.py
@@ -134,6 +134,7 @@ class FileTransfer(resource.Resource):
                        return page.render(request)
 
 oobSite = server.Site(FileTransfer())
-reactor.listenTCP(int(config.ftOOBPort), oobSite)
+# FIXME
+#reactor.listenTCP(int(config.ftOOBPort), oobSite)
 
 
index edb9fbbdf6cb94bcdc5a49ea1e10f846ed411d2a..00c181f6e7c0bd1abea920b9bf45d51b4f852543 100644 (file)
@@ -4,7 +4,7 @@
 import utils
 from twisted.internet import task
 from tlib.xmlw import Element
-from tlib import msn, msnp2p
+from tlib import msn
 from debug import LogEvent, INFO, WARN, ERROR
 import sha
 import groupchat
@@ -82,8 +82,9 @@ def startStats(statistics):
 
 def updateStats(statistics):
        stats = statistics.stats
-       stats["AvatarCount"] = msnp2p.MSNP2P_Avatar.TRANSFER_COUNT
-       stats["FailedAvatarCount"] = msnp2p.MSNP2P_Avatar.ERROR_COUNT
+       # FIXME
+       #stats["AvatarCount"] = msnp2p.MSNP2P_Avatar.TRANSFER_COUNT
+       #stats["FailedAvatarCount"] = msnp2p.MSNP2P_Avatar.ERROR_COUNT
 
 
 def msn2jid(msnid):
diff --git a/src/legacy/msnw.py b/src/legacy/msnw.py
deleted file mode 100644 (file)
index 9a23a91..0000000
+++ /dev/null
@@ -1,824 +0,0 @@
-# Copyright 2004-2005 James Bunton <james@delx.cjb.net>
-# Licensed for distribution under the GPL version 2, check COPYING for details
-
-from twisted.internet import reactor
-from twisted.internet.protocol import ClientFactory
-from twisted.python import log
-from debug import LogEvent, INFO, WARN, ERROR
-from tlib import msn
-import math
-import base64
-import binascii
-import math
-import config
-import utils
-import lang
-
-MAXMESSAGESIZE = 1400
-
-
-class MSNConnection:
-       """ Manages all the Twisted factories, etc """
-       def __init__(self, username, password):
-               self.username = username
-               self.password = password
-               self.inited = False
-               self.tries = 0
-               self.initMe()
-               LogEvent(INFO, self.session.jabberID)
-               
-       def initMe(self):
-               if self.inited:
-                       MSNConnection.removeMe(self)
-               
-               self.switchboardSessions = {}
-               dispatchFactory = DispatchFactory(self)
-               reactor.connectTCP('messenger.hotmail.com', 1863, dispatchFactory)
-               self.notificationFactory = msn.NotificationFactory()
-               self.notificationFactory.userHandle = self.username
-               self.notificationFactory.password = self.password
-               self.notificationFactory.msncon = self
-               self.notificationFactory.protocol = Notification
-               self.notificationFactory.initialListVersion = self.initialListVersion
-               self.notificationProtocol = None
-               
-               self.savedEvents = SavedEvents()
-               
-               self.inited = True
-               
-               LogEvent(INFO, self.session.jabberID)
-       
-       def removeMe(self):
-               LogEvent(INFO, self.session.jabberID)
-               if self.notificationProtocol:
-                       self.notificationProtocol.removeMe()
-               if self.notificationFactory:
-                       self.notificationFactory.msncon = None
-               self.notificationFactory = None
-               self.notificationProtocol = None
-               self.savedEvents = SavedEvents()
-               for userHandle in self.switchboardSessions.copy():
-                       self.switchboardSessions[userHandle].removeMe()
-               self.switchboardSessions = {}
-       
-       def resourceOffline(self, offlineResource):
-               for contact in self.switchboardSessions.keys():
-                       if self.switchboardSessions[contact].resource == offlineResource:
-                               self.switchboardSessions[contact].resource = self.highestResource()
-       
-       def getContacts(self):
-               if self.notificationFactory:
-                       return self.notificationFactory.contacts
-               else:
-                       return None
-               
-       
-       def sendMessage(self, remoteUser, resource, text, noerror):
-               LogEvent(INFO, self.session.jabberID)
-               if self.notificationProtocol:
-                       if not self.switchboardSessions.has_key(remoteUser):
-                               self.switchboardSessions[remoteUser] = SwitchboardSession(self, remoteUser, resource)
-                       self.switchboardSessions[remoteUser].resource = resource
-                       self.switchboardSessions[remoteUser].sendMessage(text.replace("\n", "\r\n"), noerror)
-               elif not noerror:
-                       self.failedMessage(remoteUser, text)
-       
-       def requestAvatar(self, userHandle):
-               LogEvent(INFO, self.session.jabberID)
-               resource = self.session.highestResource()
-               if config.getAllAvatars:
-                       if not self.switchboardSessions.has_key(userHandle):
-                               self.switchboardSessions[userHandle] = SwitchboardSession(self, userHandle, resource)
-                       else:
-                               self.switchboardSessions[userHandle].requestAvatar()
-               else:
-                       if self.switchboardSessions.has_key(userHandle): # Only request avatars for open switchboards
-                               self.switchboardSessions[userHandle].requestAvatar()
-       
-       def sendTypingToContact(self, remoteUser):
-               if self.switchboardSessions.has_key(remoteUser):
-                       self.switchboardSessions[remoteUser].sendTypingNofication()
-       
-       def notificationProtocolReady(self, notificationProtocol):
-               self.notificationProtocol = notificationProtocol
-               self.loggedIn()
-               self.tries = 0
-       
-       def sendSavedEvents(self):
-               # Hack for events sent before we're logged in
-               self.savedEvents.send(self)
-               self.savedEvents = None
-       
-       def changeAvatar(self, imageData):
-               if self.notificationProtocol:
-                       self.notificationProtocol.changeAvatar(imageData, push=True)
-               else:
-                       self.savedEvents.avatarImageData = imageData
-       
-       def changeStatus(self, statusCode, screenName, personal):
-               if self.notificationProtocol:
-                       def cb1(arg):
-                               self.ourStatusChanged(arg[0])
-                       def cb2(arg):
-                               self.ourNickChanged(arg[0])
-                       def cb3(arg):
-                               self.ourPersonalChanged(personal)
-                       LogEvent(INFO, self.session.jabberID)
-                       if statusCode:
-                               statusCode = str(statusCode.encode("utf-8"))
-                               self.notificationProtocol.changeStatus(statusCode).addCallback(cb1)
-                       if screenName:
-                               screenName = str(screenName.encode("utf-8"))
-                               self.notificationProtocol.changeScreenName(screenName).addCallback(cb2)
-                       if personal:
-                               personal = str(personal.encode("utf-8"))
-                       else:
-                               personal = ""
-                       self.notificationProtocol.changePersonalMessage(personal).addCallback(cb3)
-               else:
-                       self.savedEvents.statusCode = statusCode
-                       self.savedEvents.screenName = screenName
-                       self.savedEvents.personal = personal
-       
-       def connectionLostBase(self, reason):
-               # Attempts to reconnect
-               if self.tries < 5 and self.session:
-                       reactor.callLater(2 ** self.tries, self.initMe)
-                       self.tries += 1
-               else:
-                       self.connectionLost(self)
-       
-       def addContact(self, listType, userHandle):
-               if self.notificationProtocol:
-                       return self.notificationProtocol.addContact(listType, str(userHandle))
-               else:
-                       self.savedEvents.addContacts.append((listType, str(userHandle)))
-       
-       def remContact(self, listType, userHandle, groupID=0):
-               if self.notificationProtocol:
-                       return self.notificationProtocol.remContact(listType, str(userHandle))
-               else:
-                       self.savedEvents.remContacts.append((listType, str(userHandle)))
-       
-       
-       
-       def initialEmailNotification(self, inboxunread, foldersunread):
-               pass
-       
-       def realtimeEmailNotification(self, mailfrom, fromaddr, subject):
-               pass
-       
-       def loggedIn(self):
-               pass
-       
-       def loginFailure(self, message):
-               pass
-               
-       def multipleLogin(self):
-               pass
-       
-       def gotMessage(self, remoteUser, resource, text):
-               pass
-       
-       def avatarHashChanged(self, userHandle, hash):
-               pass
-       
-       def gotAvatarImage(self, to, image):
-               pass
-       
-       def gotSendRequest(self, fileReceive):
-               pass
-       
-       def listSynchronized(self):
-               pass
-       
-       def contactStatusChanged(self, remoteUser):
-               pass
-       
-       def ourStatusChanged(self, statusCode):
-               pass
-       
-       def ourNickChanged(self, nick):
-               pass
-       
-       def ourPersonalChanged(self, personal):
-               pass
-       
-       def userMapping(self, passport, jid):
-               pass
-       
-       def gotContactTyping(self, remoteUser, resource):
-               pass
-       
-       def serverGoingDown(self):
-               pass
-       
-       def accountNotVerified(self):
-               pass
-       
-       def userAddedMe(self, userHandle):
-               pass
-       
-       def userRemovedMe(self, userHandle):
-               pass
-       
-       def failedMessage(self, remoteUser, message):
-               pass
-       
-       def connectionLost(self):
-               pass
-
-
-
-class SavedEvents:
-       def __init__(self):
-               self.nickname = ""
-               self.statusCode = ""
-               self.personal = ""
-               self.avatarImageData = ""
-               self.addContacts = []
-               self.remContacts = []
-       
-       def send(self, msncon):
-               if self.avatarImageData:
-                       msncon.notificationProtocol.changeAvatar(self.avatarImageData, push=False)
-               if self.nickname or self.statusCode or self.personal:
-                       msncon.changeStatus(self.statusCode, self.nickname, self.personal)
-               for listType, userHandle in self.addContacts:
-                       msncon.addContact(listType, userHandle)
-               for listType, userHandle in self.remContacts:
-                       msncon.remContact(listType, userHandle)
-                       
-
-
-def switchToGroupchat(switchboardSession, user1, user2):
-       gcsbs = GroupchatSwitchboardSession()
-       from glue import LegacyGroupchat, msn2jid
-       groupchat = LegacyGroupchat(session=switchboardSession.msncon.session, resource=None, existing=True, switchboardSession=gcsbs)
-       gcsbs.groupchat = groupchat
-       gcsbs.msncon = switchboardSession.msncon
-       gcsbs.switchboard = switchboardSession.switchboard
-       gcsbs.switchboard.switchboardSession = gcsbs
-       gcsbs.ready = True
-       gcsbs.userJoined(user1)
-       gcsbs.userJoined(user2)
-       groupchat.sendUserInvite(msn2jid(switchboardSession.remoteUser))
-       switchboardSession.removeMe(False)
-       LogEvent(INFO, gcsbs.msncon.session.jabberID)
-       return gcsbs
-
-
-class SwitchboardSessionBase:
-       def sendMessage(self, message, noerror):
-               if self.ready:
-                       def failedMessage(ignored):
-                               if isinstance(self, GroupchatSwitchboardSession):
-                                       tempmsncon.failedMessage(self.groupchat.roomJID(), message)
-                               else:
-                                       tempmsncon.failedMessage(self.remoteUser, message)
-                                       
-                       tempmsncon = self.msncon # In case MSN tells us the message failed after removeMe()
-
-                       LogEvent(INFO, self.ident)
-                       message = str(message.encode("utf-8"))
-                       
-                       if len(message) < MAXMESSAGESIZE:
-                               msnmessage = msn.MSNMessage(message=message)
-                               msnmessage.setHeader("Content-Type", "text/plain; charset=UTF-8")
-                               msnmessage.ack = msn.MSNMessage.MESSAGE_NACK
-
-                               d = self.switchboard.sendMessage(msnmessage)
-                               if not noerror:
-                                       d.addCallback(failedMessage)
-                       else:
-                               chunks = int(math.ceil(len(message) / float(MAXMESSAGESIZE)))
-                               chunk = 0
-                               guid = msn.random_guid()
-                               while chunk < chunks:
-                                       offset = chunk * MAXMESSAGESIZE
-                                       text = message[offset : offset + MAXMESSAGESIZE]
-
-                                       msnmessage = msn.MSNMessage(message=text)
-                                       msnmessage.setHeader("Message-ID", guid)
-                                       if chunk == 0:
-                                               msnmessage.setHeader("Content-Type", "text/plain; charset=UTF-8")
-                                               msnmessage.setHeader("Chunks", str(chunks))
-                                       else:
-                                               msnmessage.setHeader("Chunk", str(chunk))
-                                       msnmessage.ack = msn.MSNMessage.MESSAGE_NACK
-
-                                       d = self.switchboard.sendMessage(msnmessage)
-                                       if not noerror:
-                                               d.addCallback(failedMessage)
-                                       chunk += 1
-
-                       self.resetTimer()
-               else:
-                       self.messageBuffer.append((message, noerror))
-       
-       def gotAvatarImage(self, to, image):
-               self.msncon.gotAvatarImage(to, image)
-       
-       def gotSendRequest(self, fileReceive):
-               self.msncon.gotSendRequest(fileReceive)
-
-       def switchboardReady(self, switchboard):
-               LogEvent(INFO, self.ident)
-               self.ready = True
-               self.switchboard = switchboard
-               self.flushBuffer()
-       
-       def resetTimer(self):
-               pass
-
-
-class GroupchatSwitchboardSession(SwitchboardSessionBase):
-       def __init__(self, groupchat=None, makeSwitchboard=False):
-               self.removed = False
-
-               self.msncon = None
-               if groupchat:
-                       self.ident = groupchat.roomJID()
-                       self.groupchat = groupchat
-                       self.msncon = self.groupchat.session.legacycon
-               else:
-                       self.ident = str(self)
-                       self.groupchat = None
-               self.switchboard = None
-               self.ready = False
-               self.messageBuffer = []
-               self.invitedUsers = []
-               self.oneUserHasJoined = False
-               
-               if makeSwitchboard and groupchat:
-                       LogEvent(INFO, self.ident, "Requesting switchboard.")
-                       d = self.msncon.notificationProtocol.requestSwitchboardServer()
-                       d.addCallback(self.sbRequestAccepted)
-                       d.addErrback(self.removeMe)
-               
-               if self.msncon:
-                       LogEvent(INFO, self.ident, "Created groupchat for " + self.msncon.username)
-       
-       def removeMe(self):
-               if self.removed:
-                       log.err("removeMe called more than once!")
-                       return
-               self.removed = True
-
-               LogEvent(INFO, self.ident)
-               self.msncon = None
-               if self.switchboard:
-                       self.switchboard.removeMe()
-               self.switchboard = None
-               self.groupchat = None
-               self.ready = False
-
-       def sbRequestAccepted(self, (host, port, key)):
-               # Connect to the switchboard server
-               LogEvent(INFO, self.ident)
-               reactor.connectTCP(host, port, SwitchboardFactory(self, key))
-       
-       def sendMessage(self, message, noerror):
-               if self.oneUserHasJoined:
-                       SwitchboardSessionBase.sendMessage(self, message, noerror)
-               else:
-                       self.messageBuffer.append((message, noerror))
-       
-       def inviteUser(self, userHandle):
-               userHandle = str(userHandle)
-               if self.ready:
-                       LogEvent(INFO, self.ident)
-                       self.switchboard.inviteUser(userHandle)
-               else:
-                       self.invitedUsers.append(userHandle)
-       
-       def gotMessage(self, message):
-               self.groupchat.messageReceived(message.userHandle, message.getMessage())
-       
-       def flushBuffer(self):
-               for m, noerror in self.messageBuffer[:]:
-                       self.messageBuffer.remove((m, noerror))
-                       self.sendMessage(m, noerror)
-               
-               for i in self.invitedUsers[:]:
-                       self.invitedUsers.remove(i)
-                       self.inviteUser(i)
-       
-       def userJoined(self, userHandle):
-               LogEvent(INFO, self.ident)
-               self.oneUserHasJoined = True
-               self.flushBuffer()
-               self.groupchat.contactJoined(userHandle)
-       
-       def userLeft(self, userHandle):
-               LogEvent(INFO, self.ident)
-               self.groupchat.contactLeft(userHandle)
-
-
-
-class SwitchboardSession(SwitchboardSessionBase):
-       def __init__(self, msncon, remoteUser, resource, reply=False, host=None, port=None, key=None, sessionID=None):
-               self.removed = False
-
-               self.ident = (msncon.session.jabberID, remoteUser)
-               self.msncon = msncon
-               self.remoteUser = str(remoteUser)
-               self.resource = str(resource)
-               
-               self.killTimer = reactor.callLater(30.0*60.0, self.removeMe)
-               
-               self.switchboard = None # The SwitchboardClient class
-               self.messageBuffer = [] # Any messages sent before the switchboard is ready are buffered
-               self.ready = False # Is True when we are connected to the switchboard, and the remote user has accepted our invite
-               
-               if not reply:
-                       # Request a switchboard
-                       d = self.msncon.notificationProtocol.requestSwitchboardServer()
-                       d.addCallback(self.sbRequestAccepted)
-                       d.addErrback(self.removeMe)
-               else:
-                       reactor.connectTCP(host, port, SwitchboardFactory(self, key, sessionID, reply))
-               
-               LogEvent(INFO, self.ident)
-       
-       def removeMe(self, sbflag=True):
-               if self.removed:
-                       log.err("removeMe called more than once!")
-                       return
-               self.removed = True
-
-               LogEvent(INFO, self.ident)
-               for message, noerror in self.messageBuffer:
-                       if not noerror:
-                               self.msncon.failedMessage(self.remoteUser, message)
-               self.messageBuffer = []
-
-               del self.msncon.switchboardSessions[self.remoteUser]
-               self.msncon = None
-               if sbflag and self.switchboard:
-                       self.switchboard.removeMe()
-               self.switchboard = None
-               self.ready = False
-               if self.killTimer and not self.killTimer.called:
-                       self.killTimer.cancel()
-               self.killTimer = None
-
-       def resetTimer(self):
-               # Sets a count down timer to kill this switchboard session in 30 minutes
-               self.killTimer.cancel()
-               self.killTimer = reactor.callLater(30.0*60.0, self.removeMe)
-       
-       def sbRequestAccepted(self, (host, port, key)):
-               # Connect to the switchboard server
-               reactor.connectTCP(host, port, SwitchboardFactory(self, key))
-       
-       def sendTypingNofication(self):
-               if self.ready:
-                       self.switchboard.sendTypingNotification()
-       
-       def contactTyping(self):
-               self.msncon.gotContactTyping(self.remoteUser, self.resource)
-       
-       def flushBuffer(self):
-               for m, noerror in self.messageBuffer[:]:
-                       self.messageBuffer.remove((m, noerror))
-                       self.sendMessage(m, noerror)
-       
-       def gotMessage(self, message):
-               self.msncon.gotMessage(self.remoteUser, self.resource, message.getMessage())
-               self.resetTimer()
-       
-       CAPS = msn.MSNContact.MSNC1 | msn.MSNContact.MSNC2 | msn.MSNContact.MSNC3 | msn.MSNContact.MSNC4
-       def requestAvatar(self):
-               if not self.switchboard: return
-               msnContacts = self.msncon.getContacts()
-               if not msnContacts: return
-               msnContact = msnContacts.getContact(self.remoteUser)
-               if not (msnContact and msnContact.caps & self.CAPS and msnContact.msnobj): return
-               if msnContact.msnobjGot: return
-               msnContact.msnobjGot = True # This is deliberately set before we get the avatar. So that we don't try to reget failed avatars over & over
-               self.switchboard.sendAvatarRequest(self.remoteUser, msnContact.msnobj)
-       
-       def userJoined(self, userHandle):
-               if userHandle != self.remoteUser:
-                       # Another user has joined, so we now have three participants (these two and ourself)
-                       switchToGroupchat(self, self.remoteUser, userHandle)
-               else:
-                       self.requestAvatar()
-       
-       def userLeft(self, userHandle):
-               if userHandle == self.remoteUser:
-                       self.removeMe()
-
-
-
-class DispatchFactory(ClientFactory):
-       def __init__(self, msncon):
-               self.msncon = msncon
-       
-       def buildProtocol(self, addr):
-               p = Dispatch(self.msncon)
-               del self.msncon # No longer needed
-               return p
-       
-       def clientConnectionFailed(self, connector, reason):
-               self.msncon.connectionLostBase(reason)
-       
-
-class Dispatch(msn.DispatchClient):
-       def __init__(self, msncon):
-               msn.DispatchClient.__init__(self)
-               self.msncon = msncon
-               self.userHandle = self.msncon.username
-       
-       def __del__(self):
-               self.factory = None
-               self.msncon = None
-       
-       def gotNotificationReferral(self, host, port):
-               self.transport.loseConnection()
-               if self.msncon and self.msncon.session and self.msncon.session.alive:
-                       reactor.connectTCP(host, port, self.msncon.notificationFactory)
-
-
-
-class Notification(msn.NotificationClient):
-       def __init__(self):
-               self.removed = False
-
-               msn.NotificationClient.__init__(self)
-
-       def removeMe(self):
-               if self.removed:
-                       log.err("removeMe called more than once!")
-                       return
-               self.removed = True
-
-               self.logOut()
-               self.transport.loseConnection()
-               if self.factory.msncon:
-                       self.factory.msncon.notificationProtocol = None
-                       self.factory.msncon = None
-               self.factory = None
-
-       def badConditions(self):
-               if not (self.factory and self.factory.msncon and self.factory.msncon.session and self.factory.msncon.session.alive):
-                       if not self.removed:
-                               self.removeMe()
-                       return True
-               return False
-               
-       
-       def loginFailure(self, message):
-               if self.badConditions(): return
-               self.factory.msncon.loginFailure(message)
-       
-       def loggedIn(self, userHandle, verified):
-               if self.badConditions(): return
-
-               self.factory.msncon.notificationProtocolReady(self)
-               if not verified:
-                       self.factory.msncon.accountNotVerified()
-               
-               msn.NotificationClient.loggedIn(self, userHandle, verified)
-               
-               LogEvent(INFO, self.factory.msncon.session.jabberID)
-       
-       def msnAlertReceived(self, body, action, subscr):
-               if self.badConditions(): return
-               self.factory.msncon.msnAlert(body, action, subscr)
-
-       def initialEmailNotification(self, inboxunread, foldersunread):
-               if self.badConditions() or not config.mailNotifications: return
-               self.factory.msncon.initialEmailNotification(inboxunread, foldersunread)
-
-       def realtimeEmailNotification(self, mailfrom, fromaddr, subject):
-               if self.badConditions() or not config.mailNotifications: return
-               self.factory.msncon.realtimeEmailNotification(mailfrom, fromaddr, subject)
-       
-       def connectionLost(self, reason):
-               if self.badConditions(): return
-               def wait():
-                       LogEvent(INFO, self.factory.msncon.session.jabberID)
-                       msn.NotificationClient.connectionLost(self, reason)
-                       self.factory.msncon.connectionLostBase(reason)
-               # Make sure this event is handled after any others
-               reactor.callLater(0, wait)
-       
-       def listSynchronized(self, *args):
-               if self.badConditions(): return
-               LogEvent(INFO, self.factory.msncon.session.jabberID)
-               self.factory.msncon.listSynchronized()
-               if self.badConditions(): return # Just in case the session is deregistered
-               self.factory.msncon.sendSavedEvents()
-               self.setPrivacyMode(False)
-       
-       def gotSwitchboardInvitation(self, sessionID, host, port, key, remoteUser, screenName):
-               if self.badConditions(): return
-               LogEvent(INFO, self.factory.msncon.session.jabberID)
-               sbs = SwitchboardSession(self.factory.msncon, remoteUser, self.factory.msncon.session.highestResource(), True, host, port, key, sessionID)
-               if self.factory.msncon.switchboardSessions.has_key(remoteUser):
-                       self.factory.msncon.switchboardSessions[remoteUser].removeMe()
-               self.factory.msncon.switchboardSessions[remoteUser] = sbs
-       
-       def avatarHashChanged(self, userHandle, hash):
-               if self.badConditions(): return
-               LogEvent(INFO, self.factory.msncon.session.jabberID)
-               hash = base64.decodestring(hash)
-               hash = binascii.hexlify(hash)
-               self.factory.msncon.avatarHashChanged(userHandle, hash)
-       
-       def contactStatusChanged(self, statusCode, userHandle, screenName):
-               if self.badConditions(): return
-               LogEvent(INFO, self.factory.msncon.session.jabberID)
-               
-               self.factory.msncon.contactStatusChanged(userHandle)
-       
-       def contactPersonalChanged(self, userHandle, personal):
-               if self.badConditions(): return
-               msn.NotificationClient.contactPersonalChanged(self, userHandle, personal)
-               self.factory.msncon.contactStatusChanged(userHandle)
-       
-       def contactOffline(self, userHandle):
-               if self.badConditions(): return
-               LogEvent(INFO, self.factory.msncon.session.jabberID)
-               msn.NotificationClient.contactOffline(self, userHandle)
-               
-               self.factory.msncon.contactStatusChanged(userHandle)
-       
-       def userAddedMe(self, userGuid, userHandle, screenName):
-               if self.badConditions(): return
-               LogEvent(INFO, self.factory.msncon.session.jabberID)
-               msn.NotificationClient.userAddedMe(self, userGuid, userHandle, screenName)
-               self.factory.msncon.userAddedMe(userHandle)
-       
-       def userRemovedMe(self, userGuid, userHandle):
-               if self.badConditions(): return
-               LogEvent(INFO, self.factory.msncon.session.jabberID)
-               msn.NotificationClient.userRemovedMe(self, userGuid, userHandle)
-               self.factory.msncon.userRemovedMe(userHandle)
-       
-       def multipleLogin(self):
-               if self.badConditions(): return
-               LogEvent(INFO, self.factory.msncon.session.jabberID)
-               self.factory.msncon.multipleLogin()
-
-
-
-class SwitchboardFactory(ClientFactory):
-       def __init__(self, switchboardSession, key, sessionID=None, reply=False):
-               self.switchboardSession = switchboardSession
-               self.key = key
-               self.sessionID = sessionID
-               self.reply = reply
-       
-       def buildProtocol(self, addr):
-               p = Switchboard(self.switchboardSession)
-               if p.badConditions(): return p
-               p.key = self.key
-               p.sessionID = self.sessionID
-               p.reply = self.reply
-               p.userHandle = self.switchboardSession.msncon.username
-               p.factory = self
-               return p
-
-class Switchboard(msn.SwitchboardClient):
-       def __init__(self, switchboardSession):
-               self.removed = False
-
-               self.switchboardSession = switchboardSession
-               self.chattingUsers = []
-               self.callid = None
-               msn.SwitchboardClient.__init__(self)
-               if self.badConditions(): return
-               self.msnobj = self.switchboardSession.msncon.notificationProtocol.msnobj
-               LogEvent(INFO, self.switchboardSession.ident)
-       
-       def removeMe(self):
-               if self.removed:
-                       log.err("removeMe called more than once!")
-                       return
-               self.removed = True
-
-               self.transport.loseConnection()
-               LogEvent(INFO, self.switchboardSession.ident)
-               self.switchboardSession = None
-               self.factory.switchboardSession = None
-               self.factory = None
-
-               if self.callid and not self.callid.called:
-                       self.callid.cancel() # Cancel the invite fail message
-               self.callid = None
-
-       def badConditions(self):
-               if not (self.switchboardSession and self.switchboardSession.msncon and self.switchboardSession.msncon.session and self.switchboardSession.msncon.session.alive):
-                       if self.switchboardSession:
-                               if not self.switchboardSession.removed:
-                                       self.switchboardSession.removeMe()
-                       elif not self.removed:
-                               self.removeMe()
-                       return True
-               return False
-       
-       def loggedIn(self):
-               if self.badConditions(): return
-               if (not self.reply) and isinstance(self.switchboardSession, SwitchboardSession):
-                       def failCB(arg=None):
-                               LogEvent(INFO, ident, "User has not joined after 30 seconds.")
-                               self.switchboardSession.removeMe()
-                       d = self.inviteUser(self.switchboardSession.remoteUser)
-                       d.addErrback(failCB)
-                       ident = self.switchboardSession.ident
-                       # If the user doesn't join then we want to tear down the SwitchboardSession
-                       self.callid = reactor.callLater(30.0, failCB)
-               
-               else:
-                       self.readySwitchboardSession()
-       
-       def readySwitchboardSession(self):
-               self.switchboardSession.switchboardReady(self)
-               for user in self.chattingUsers:
-                       self.switchboardSession.userJoined(user)
-               if self.callid and not self.callid.called:
-                       self.callid.cancel() # Cancel the invite fail message (only applies if we needed to invite the user)
-               self.callid = None
-       
-       def gotChattingUsers(self, users):
-               for user in users:
-                       self.chattingUsers.append(user)
-       
-       def userJoined(self, userHandle, screenName):
-               if self.badConditions(): return
-               # FIXME - check this is correct
-               if (not self.reply) and isinstance(self.switchboardSession, SwitchboardSession):
-                       self.readySwitchboardSession()
-               LogEvent(INFO, self.switchboardSession.ident)
-               self.switchboardSession.userJoined(userHandle)
-       
-       def userLeft(self, userHandle):
-               if self.badConditions(): return
-               LogEvent(INFO, self.switchboardSession.ident)
-               def wait():
-                       if self.badConditions(): return
-                       self.switchboardSession.userLeft(userHandle)
-               # Make sure this event is handled after any others (eg, gotMessage)
-               reactor.callLater(0, wait)
-       
-       def gotMessage(self, message):
-               if self.badConditions():
-                       LogEvent(WARN, self.switchboardSession.ident, "gotMessage() called too late. Dropped a message!")
-                       return
-
-               LogEvent(INFO, self.switchboardSession.ident)
-               cTypes = [s.lstrip() for s in message.getHeader("Content-Type").split(';')]
-               if "text/plain" in cTypes:
-                       try:
-                               if len(cTypes) > 1 and cTypes[1].find("UTF-8") >= 0:
-                                       message.message = message.message.decode("utf-8")
-                               self.switchboardSession.gotMessage(message)
-                       except:
-                               self.switchboardSession.gotMessage(lang.get(self.switchboardSession.msncon.session.lang).msnDroppedMessage) # FIXME, this is a little deep
-                               raise
-                       return
-               if "text/x-clientcaps" in cTypes:
-                       if message.hasHeader("JabberID"):
-                               jid = message.getHeader("JabberID")
-                               self.switchboardSession.msncon.userMapping(message.userHandle, jid)
-                       return
-               LogEvent(INFO, self.switchboardSession.ident, "Discarding unknown message type.")
-       
-       def userTyping(self, message):
-               if self.badConditions(): return
-               if isinstance(self.switchboardSession, SwitchboardSession): # Ignore typing in groupchats
-                       if message.userHandle == self.switchboardSession.remoteUser:
-                               self.switchboardSession.contactTyping()
-       
-       def sendClientCaps(self):
-               message = msn.MSNMessage()
-               message.setHeader("Content-Type", "text/x-clientcaps")
-               message.setHeader("Client-Name", "PyMSNt")
-               message.setHeader("JabberID", str(self.switchboardSession.msncon.session.jabberID)) # FIXME, this is a little deep
-               self.sendMessage(message)
-       
-       def sendMessage(self, message):
-               # A little bit of fancyness to make sure that clientcaps
-               # only gets sent after the first text message.
-               if message.getHeader("Content-Type").startswith("text"):
-                       self.sendMessage = type(self.sendMessage)(msn.SwitchboardClient.sendMessage, self, Switchboard)
-                       self.sendClientCaps()
-                       return self.sendMessage(message)
-               else:
-                       return msn.SwitchboardClient.sendMessage(self, message)
-       
-       def gotAvatarImage(self, to, image):
-               if self.badConditions(): return
-               self.switchboardSession.gotAvatarImage(to, image)
-       
-       def gotSendRequest(self, fileReceive):
-               if self.badConditions():
-                       fileReceive.accept(False)
-                       return
-               LogEvent(INFO, self.switchboardSession.ident)
-               self.switchboardSession.gotSendRequest(fileReceive)
-
index 864acc7380aa5738d475a7b55eaf772c7320f4bd..72656911734b02844baa9b6ad1d0db7fd66ed7ba 100644 (file)
@@ -1,2 +1,2 @@
-from msn import *
-from msnft import *
+from msnw import MSNConnection
+import msn
index c951f77805a94b2094ad431ad0e95d735c2f2e91..2bffefcff68e3aae7fe5d127851fca9ec1a152bd 100644 (file)
@@ -152,7 +152,7 @@ STATUS_LUNCH   = 'LUN'
 PINGSPEED = 50.0
 
 DEBUGALL = False
-LINEDEBUG = False
+LINEDEBUG = True
 MESSAGEDEBUG = False
 MSNP2PDEBUG = False
 
@@ -834,16 +834,13 @@ class MSNEventBase(LineReceiver):
         """
         log.msg('Error %s' % (errorCodes[errorCode]))
 
+
 class DispatchClient(MSNEventBase):
     """
     This class provides support for clients connecting to the dispatch server
     @ivar userHandle: your user handle (passport) needed before connecting.
     """
 
-    # eventually this may become an attribute of the
-    # factory.
-    userHandle = ""
-
     def connectionMade(self):
         MSNEventBase.connectionMade(self)
         self.sendLine('VER %s %s' % (self._nextTransactionID(), MSN_PROTOCOL_VERSION))
@@ -856,10 +853,10 @@ class DispatchClient(MSNEventBase):
             self.transport.loseConnection()
             raise MSNProtocolError, "Invalid version response"
         id = self._nextTransactionID()
-        self.sendLine("CVR %s %s %s" % (id, MSN_CVR_STR, self.userHandle))
+        self.sendLine("CVR %s %s %s" % (id, MSN_CVR_STR, self.factory.userHandle))
 
     def handle_CVR(self, params):
-        self.sendLine("USR %s TWN I %s" % (self._nextTransactionID(), self.userHandle))
+        self.sendLine("USR %s TWN I %s" % (self._nextTransactionID(), self.factory.userHandle))
 
     def handle_XFR(self, params):
         if len(params) < 4: raise MSNProtocolError, "Invalid number of parameters for XFR"
@@ -885,6 +882,18 @@ class DispatchClient(MSNEventBase):
         pass
 
 
+class DispatchFactory(ClientFactory):
+    """
+    This class keeps the state for the DispatchClient.
+
+    @ivar userHandle: the userHandle to request a notification
+                      server for.
+    """
+    protocol = DispatchClient
+    userHandle = ""
+
+
+
 class NotificationClient(MSNEventBase):
     """
     This class provides support for clients connecting
@@ -1088,7 +1097,7 @@ class NotificationClient(MSNEventBase):
             self.handleAvatarHelper(msnContact, params[5])
         else:
             self.handleAvatarGoneHelper(msnContact)
-        self.gotContactStatus(params[1], params[2], unquote(params[3]))
+        self.gotContactStatus(params[2], params[1], unquote(params[3]))
 
     def handleAvatarGoneHelper(self, msnContact):
         if msnContact.msnobj:
@@ -1124,7 +1133,7 @@ class NotificationClient(MSNEventBase):
             self.handleAvatarHelper(msnContact, params[4])
         else:
             self.handleAvatarGoneHelper(msnContact)
-        self.contactStatusChanged(params[0], params[1], unquote(params[2]))
+        self.contactStatusChanged(params[1], params[0], unquote(params[2]))
 
     def handle_FLN(self, params):
         checkParamLen(len(params), 1, 'FLN')
@@ -1416,22 +1425,22 @@ class NotificationClient(MSNEventBase):
         """
         pass
 
-    def gotContactStatus(self, statusCode, userHandle, screenName):
+    def gotContactStatus(self, userHandle, statusCode, screenName):
         """
         Called when we receive a list of statuses upon login.
 
-        @param statusCode: 3-letter status code
         @param userHandle: the contact's user handle (passport)
+        @param statusCode: 3-letter status code
         @param screenName: the contact's screen name
         """
         pass
 
-    def contactStatusChanged(self, statusCode, userHandle, screenName):
+    def contactStatusChanged(self, userHandle, statusCode, screenName):
         """
         Called when we're notified that a contact's status has changed.
 
-        @param statusCode: 3-letter status code
         @param userHandle: the contact's user handle (passport)
+        @param statusCode: 3-letter status code
         @param screenName: the contact's screen name
         """
         pass
@@ -1938,6 +1947,7 @@ class NotificationClient(MSNEventBase):
             self.pingCheckTask.stop()
             self.pingCheckTask = None
         self.sendLine("OUT")
+        self.transport.loseConnection()
 
 class NotificationFactory(ClientFactory):
     """
@@ -1993,7 +2003,8 @@ class SwitchboardClient(MSNEventBase):
                      to a switchboard invitation
     @ivar reply: set this to 1 in connectionMade or before to signifiy
                  that you are replying to a switchboard invitation.
-    @ivar msnobj: the MSNObject for the user. So that the switchboard can distribute it.
+    @ivar msnobj: the MSNObject for the user's avatar. So that the
+                  switchboard can distribute it to anyone who asks.
     """
 
     key = 0
@@ -2037,7 +2048,7 @@ class SwitchboardClient(MSNEventBase):
     def _checkTyping(self, message, cTypes):
         """ helper method for checkMessage """
         if 'text/x-msmsgscontrol' in cTypes and message.hasHeader('TypingUser'):
-            self.userTyping(message)
+            self.gotContactTyping(message)
             return 1
 
     def _checkFileInvitation(self, message, info):
@@ -2228,10 +2239,10 @@ class SwitchboardClient(MSNEventBase):
         """
         pass
 
-    def userTyping(self, message):
+    def gotContactTyping(self, message):
         """
         called when we receive the special type of message notifying
-        us that a user is typing a message.
+        us that a contact is typing a message.
 
         @param message: the associated MSNMessage object
         """
diff --git a/src/tlib/msn/msnw.py b/src/tlib/msn/msnw.py
new file mode 100644 (file)
index 0000000..3204827
--- /dev/null
@@ -0,0 +1,576 @@
+# Copyright 2004-2005 James Bunton <james@delx.cjb.net>
+# Licensed for distribution under the GPL version 2, check COPYING for details
+
+# Twisted imports
+from twisted.internet import reactor
+from twisted.internet.defer import Deferred
+from twisted.internet.protocol import ClientFactory
+
+# System imports
+import math, base64, binascii, math
+
+# Local imports
+from debug import LogEvent, INFO, WARN, ERROR
+from tlib.msn import msn
+
+# Imports from msn
+from msn import FORWARD_LIST, ALLOW_LIST, BLOCK_LIST, REVERSE_LIST, PENDING_LIST
+from msn import STATUS_ONLINE, STATUS_OFFLINE, STATUS_HIDDEN, STATUS_IDLE, STATUS_AWAY, STATUS_BUSY, STATUS_BRB, STATUS_PHONE, STATUS_LUNCH
+
+MAXMESSAGESIZE     = 1400
+SWITCHBOARDTIMEOUT = 30.0*60.0
+GETALLAVATARS      = True
+
+
+"""
+All interaction should be with the MSNConnection class.
+You should not directly instantiate any objects of other classes.
+"""
+
+class MSNConnection:
+       """ Manages all the Twisted factories, etc """
+       def __init__(self, username, password, ident):
+               """ Connects to the MSN servers.
+               @param username: the MSN passport to connect with.
+               @param password: the password for this account.
+               @param ident: a unique identifier to use in logging.
+               """
+               self.username = username
+               self.password = password
+               self.ident = ident
+               self.timeout = None
+               self.connect()
+               LogEvent(INFO, self.ident)
+       
+       def connect(self):
+               """ Automatically called by the constructor """
+               self.connectors = []
+               self.switchboardSessions = {}
+               self.savedEvents = SavedEvents() # Save any events that occur before connect
+               self._getNotificationReferral()
+
+       def _getNotificationReferral(self):
+               def timeout():
+                       if not d.called: d.errback()
+               self.timeout = reactor.callLater(30, timeout)
+               dispatchFactory = msn.DispatchFactory()
+               dispatchFactory.userHandle = self.username
+               dispatchFactory.protocol = DispatchClient
+               d = Deferred()
+               dispatchFactory.d = d
+               d.addCallbacks(self._gotNotificationReferral, self.connectionFailed)
+               self.connectors.append(reactor.connectTCP("messenger.hotmail.com", 1863, dispatchFactory))
+               LogEvent(INFO, self.ident)
+       
+       def _gotNotificationReferral(self, (host, port)):
+               self.timeout.cancel()
+               # Create the NotificationClient
+               self.notificationFactory = msn.NotificationFactory()
+               self.notificationFactory.userHandle = self.username
+               self.notificationFactory.password = self.password
+               self.notificationFactory.msncon = self
+               self.notificationFactory.protocol = NotificationClient
+               self.connectors.append(reactor.connectTCP(host, port, self.notificationFactory))
+               LogEvent(INFO, self.ident)
+       
+       def _sendSavedEvents(self):
+               self.savedEvents.send(self)
+               self.savedEvents = None
+       
+       def _notificationClientReady(self, notificationClient):
+               self.notificationClient = notificationClient
+       
+       def _ensureSwitchboardSession(self, userHandle):
+               if not self.switchboardSessions.has_key(userHandle):
+                       sb = OneSwitchboardSession(self, userHandle)
+                       sb.connect()
+                       self.switchboardSessions[userHandle] = sb
+
+       
+
+       # API calls
+       def getContacts(self):
+               """ Gets the contact list.
+
+               @return an instance of MSNContactList (do not modify) if connected,
+                               or None if not.
+               """
+               if self.notificationFactory:
+                       return self.notificationFactory.contacts
+               else:
+                       return None
+       
+       def sendMessage(self, userHandle, text, noerror=False):
+               """
+               Sends a message to a contact. Can only be called after listSynchronized().
+
+               @param userHandle: the contact's MSN passport.
+               @param text: the text to send.
+               @param noerror: Set this to True if you don't want failed messages to bounce.
+               """
+               LogEvent(INFO, self.ident)
+               if self.notificationClient:
+                       self._ensureSwitchboardSession(userHandle)
+                       self.switchboardSessions[userHandle].sendMessage(text, noerror)
+               elif not noerror:
+                       self.failedMessage(userHandle, text)
+
+       def requestAvatar(self, userHandle):
+               """
+               Requests the avatar of a contact.
+
+               @param userHandle: the contact to request an avatar from.
+               @return: a Deferred() if the avatar can be fetched at this time.
+                        This will fire with an argument of a tuple with the PNG 
+                                image data as the only element.
+                                Otherwise returns None
+               """
+
+               LogEvent(INFO, self.ident)
+               if not self.notificationClient: return
+               if GETALLAVATARS:
+                       self._ensureSwitchboardSession(userHandle)
+               sb = self.switchboardSessions.get(userHandle)
+               if sb: return sb.sendAvatarRequest()
+       
+       def sendFile(self, userHandle, filename, filesize):
+               """
+               Used to send a file to a contact.
+
+               @param username: the passport of the contact to send a file to.
+               @param filename: the name of the file to send.
+               @param filesize: the size of the file to send.
+        
+               @return: (fileSend, d) A FileSend object and a Deferred.
+                        The Deferred will pass one argument in a tuple,
+                        whether or not the transfer is accepted. If you
+                        receive a True, then you can call write() on the
+                        fileSend object to send your file. Call close()
+                        when the file is done.
+                        NOTE: You MUST write() exactly as much as you
+                        declare in filesize.
+               """
+               raise NotImplementedError # May have to wait for the switchboardSession to exist
+
+       def sendTypingToContact(self, userHandle):
+               """
+               Sends typing notification to a contact.
+               @param userHandle: the contact to notify of our typing.
+               """
+
+               sb = self.switchboardSessions.get(userHandle)
+               if sb: return sb.sendTypingNotification()
+       
+       def changeAvatar(self, imageData):
+               """
+               Changes the user's avatar.
+               @param imageData: the new PNG avatar image data.
+               """
+               if self.notificationClient:
+                       LogEvent(INFO, self.ident)
+                       self.notificationClient.changeAvatar(imageData, push=True)
+               else:
+                       self.savedEvents.avatarImageData = imageData
+       
+       def changeStatus(self, statusCode, screenName, personal):
+               """
+               Changes your status details. All details must be given with
+               each call. This can be called before connection if you wish.
+
+               @param statusCode: the user's new status (look in msn.statusCodes).
+               @param screenName: the user's new screenName (up to 127 characters).
+               @param personal: the user's new personal message.
+               """
+
+               if self.notificationClient:
+                       count = 0
+                       def cb():
+                               if count == 3:
+                                       self.ourStatusChanged(statusCode, screenName, personal)
+                       LogEvent(INFO, self.ident)
+                       self.notificationClient.changeStatus(statusCode.encode("utf-8")).addCallback(cb)
+                       self.notificationClient.changeScreenName(screenName.encode("utf-8")).addCallback(cb)
+                       if not personal: personal = ""
+                       self.notificationClient.changePersonalMessage(personal.encode("utf-8"))
+               else:
+                       self.savedEvents.statusCode = statusCode
+                       self.savedEvents.screenName = screenName
+                       self.savedEvents.personal = personal
+                               
+       def addContact(self, listType, userHandle):
+               """ See msn.NotificationClient.addContact """
+               if self.notificationClient:
+                       return self.notificationClient.addContact(listType, str(userHandle))
+               else:
+                       self.savedEvents.addContacts.append((listType, str(userHandle)))
+                       
+       def remContact(self, listType, userHandle):
+               """ See msn.NotificationClient.remContact """
+               if self.notificationClient:
+                       return self.notificationClient.remContact(listType, str(userHandle))
+               else:
+                       self.savedEvents.remContacts.append((listType, str(userHandle)))
+       
+       def logOut(self):
+               """ Shuts down the whole connection. Don't try to call any
+               other methods after this one. """
+               if self.notificationClient:
+                       self.notificationClient.logOut()
+               for c in self.connectors:
+                       c.disconnect()
+               if self.notificationFactory:
+                       self.notificationFactory.msncon = None
+               LogEvent(INFO, self.ident)
+               
+       
+       # Reimplement these!
+       def connectionFailed(self, reason=''):
+               """ Called when the connection to the server failed. """
+       
+       def connectionLost(self, reason=''):
+               """ Called when we are disconnected. """
+       
+       def multipleLogin(self):
+               """ Called when the server says there has been another login
+               for this account. """
+       
+       def serverGoingDown(self):
+               """ Called when the server says that it will be going down. """
+       
+       def accountNotVerified(self):
+               """ Called if this passport has not been verified. Certain
+               functions are not available. """
+       
+       def userMapping(self, passport, jid):
+               """ Called when it is brought to our attention that one of the
+               MSN contacts has a Jabber ID. You should communicate with Jabber. """
+       
+       def loggedIn(self):
+               """ Called when we have authenticated, but before we receive
+               the contact list. """
+       
+       def listSynchronized(self):
+               """ Called when we have received the contact list. All methods
+               in this class are now valid. """
+
+       def ourStatusChanged(self, statusCode, screenName, personal):
+               """ Called when the user's status has changed. """
+       
+       def gotMessage(self, userHandle, text):
+               """ Called when a contact sends us a message """
+       
+       def failedMessage(self, userHandle, text):
+               """ Called when a message we sent has been bounced back. """
+
+       def contactAvatarChanged(self, userHandle, hash):
+               """ Called when we receive a changed avatar hash for a contact.
+               You should call requestAvatar(). """
+       
+       def contactStatusChanged(self, userHandle, statusCode, screenName):
+               """ Called when we receive status information for a contact. """
+       
+       def contactPersonalChanged(self, personal):
+               """ Called when we receive a new personal message for a contact. """
+       
+       def gotFileReceive(self, fileReceive):
+               """ Called when a contact sends the user a file.
+               Call accept(fileHandle) or reject() on the object. """
+       
+       def contactAddedMe(self, userHandle):
+               """ Called when a contact adds the user to their list. """
+       
+       def contactRemovedMe(self, userHandle):
+               """ Called when a contact removes the user from their list. """
+
+
+class SavedEvents:
+       def __init__(self):
+               self.nickname = ""
+               self.statusCode = ""
+               self.personal = ""
+               self.avatarImageData = ""
+               self.addContacts = []
+               self.remContacts = []
+       
+       def send(self, msncon):
+               if self.avatarImageData:
+                       msncon.notificationClient.changeAvatar(self.avatarImageData, push=False)
+               if self.nickname or self.statusCode or self.personal:
+                       msncon.changeStatus(self.statusCode, self.nickname, self.personal)
+               for listType, userHandle in self.addContacts:
+                       msncon.addContact(listType, userHandle)
+               for listType, userHandle in self.remContacts:
+                       msncon.remContact(listType, userHandle)
+
+
+
+class DispatchClient(msn.DispatchClient):
+       def gotNotificationReferral(self, host, port):
+               if self.factory.d.called: return # Too slow! We've already timed out
+               self.factory.d.callback((host, port))
+
+
+class NotificationClient(msn.NotificationClient):
+       def loginFailure(self, message):
+               self.factory.msncon.connectionFailed(message)
+       
+       def loggedIn(self, userHandle, verified):
+               LogEvent(INFO, self.factory.msncon.ident)
+               msn.NotificationClient.loggedIn(self, userHandle, verified)
+               self.factory.msncon._notificationClientReady(self)
+               if not verified:
+                       self.factory.msncon.accountNotVerified()
+       
+       def logOut(self):
+               msn.NotificationClient.logOut(self)
+       
+       def connectionLost(self, reason):
+               if not self.factory.msncon: return # If we called logOut
+               def wait():
+                       LogEvent(INFO, self.factory.msncon.ident)
+                       msn.NotificationClient.connectionLost(self, reason)
+                       self.factory.msncon.connectionLost(reason)
+               # Make sure this event is handled after any others
+               reactor.callLater(0, wait)
+       
+       def gotMSNAlert(self, body, action, subscr):
+               LogEvent(INFO, self.factory.msncon.ident)
+               self.factory.msncon.gotMSNAlert(body, action, subscr)
+
+       def gotInitialEmailNotification(self, inboxunread, foldersunread):
+               LogEvent(INFO, self.factory.msncon.ident)
+               self.factory.msncon.gotInitialEmailNotification(inboxunread, foldersunread)
+
+       def gotRealtimeEmailNotification(self, mailfrom, fromaddr, subject):
+               LogEvent(INFO, self.factory.msncon.ident)
+               self.factory.msncon.gotRealtimeEmailNotification(mailfrom, fromaddr, subject)
+       
+       def userAddedMe(self, userGuid, userHandle, screenName):
+               LogEvent(INFO, self.factory.msncon.ident)
+               self.factory.msncon.contactAddedMe(userHandle)
+       
+       def userRemovedMe(self, userHandle):
+               LogEvent(INFO, self.factory.msncon.ident)
+               self.factory.msncon.contactRemovedMe(userHandle)
+       
+       def listSynchronized(self, *args):
+               LogEvent(INFO, self.factory.msncon.ident)
+               self.factory.msncon._sendSavedEvents()
+               self.factory.msncon.listSynchronized()
+       
+       def contactAvatarChanged(self, userHandle, hash):       
+               LogEvent(INFO, self.factory.msncon.ident)
+               self.factory.msncon.contactAvatarChanged(userHandle, hash)
+       
+       def gotContactStatus(self, userHandle, statusCode, screenName): 
+               LogEvent(INFO, self.factory.msncon.ident)
+               self.factory.msncon.contactStatusChanged(userHandle, statusCode, screenName)
+       
+       def contactStatusChanged(self, userHandle, statusCode, screenName):
+               LogEvent(INFO, self.factory.msncon.ident)
+               self.factory.msncon.contactStatusChanged(userHandle, statusCode, screenName)
+       
+       def contactOffline(self, userHandle):
+               LogEvent(INFO, self.factory.msncon.ident)
+               self.factory.msncon.contactStatusChanged(userHandle, msn.STATUS_OFFLINE, "")
+
+       def gotSwitchboardInvitation(self, sessionID, host, port, key, userHandle, screenName):
+               LogEvent(INFO, self.factory.msncon.ident)
+               sb = SwitchboardSession(self.factory.msncon, userHandle, host, port, key, sessionID)
+               sbOld = self.factory.msncon.switchboardSessions.get(userHandle)
+               if sbOld:
+                       sbOld.disconnect()
+               self.factory.msncon.switchboardSessions[userHandle] = sb
+       
+       def multipleLogin(self):
+               LogEvent(INFO, self.factory.msncon.ident)
+               self.factory.msncon.multipleLogin()
+       
+       def serverGoingDown(self):
+               LogEvent(INFO, self.factory.msncon.ident)
+               self.factory.msncon.serverGoingDown()
+
+       
+
+def switchToGroupchat(*args):
+       raise NotImplementedError
+
+
+class SwitchboardSessionBase:
+       def __init__(self, msncon):
+               self.msncon = msncon
+               self.ident = (msncon.ident, "INVALID!!")
+               self.messageBuffer = []
+               self.ready = False
+               self.killTimer = reactor.callLater(SWITCHBOARDTIMEOUT, self.transport.loseConnection)
+
+       def __del__(self):
+               LogEvent(INFO, self.ident)
+               del self.msncon
+               self.transport.disconnect()
+
+               for message, noerror in self.messageBuffer:
+                       if not noerror:
+                               self.msncon.failedMessage(self.remoteUser, message)
+
+               if not self.killTimer.called:
+                       self.killTimer.cancel()
+               del self.killTimer
+       
+       def connect(self):
+               LogEvent(INFO, self.ident)
+               def sbRequestAccepted((host, port, key)):
+                       LogEvent(INFO, self.ident)
+                       self.key = key
+                       factory = ClientFactory()
+                       factory.buildProtocol = lambda: self
+                       reactor.connectTCP(host, port, factory)
+               def sbRequestFailed(ignored=None):
+                       LogEvent(INFO, self.ident)
+                       del self.msncon.switchboardSessions[self.remoteUser]
+               d = self.msncon.notificationClient.requestSwitchboardServer()
+               d.addCallbacks(sbRequestAccepted, sbRequestFailed)
+       
+       def flushBuffer(self):
+               for message, noerror in self.messageBuffer[:]:
+                       self.messageBuffer.remove((m, noerror))
+                       self.sendMessage(m, noerror)
+
+       def resetTimer(self):
+               self.killTimer.cancel()
+               self.killTimer = reactor.callLater(SWITCHBOARDTIMEOUT, self.transport.loseConnection)
+
+       def failedMessage(self, ignored):
+               raise NotImplementedError
+
+       def sendMessage(text, noerror):
+               if not self.ready:
+                       self.messageBuffer.append((message, noerror))
+               else:
+                       LogEvent(INFO, self.ident)
+                       def failedMessage(ignored):
+                               if not noerror:
+                                       self.failedMessage(text)
+
+                       if len(message) < MAXMESSAGESIZE:
+                               message = msn.MSNMessage(message=str(text.replace("\n", "\r\n").encode("utf-8")))
+                               message.setHeader("Content-Type", "text/plain; charset=UTF-8")
+                               message.ack = msn.MSNMessage.MESSAGE_NACK
+
+                               d = msn.SwitchboardClient.sendMessage(self, message)
+                               if not noerror:
+                                       d.addCallback(failedMessage)
+
+                       else:
+                               chunks = int(math.ceil(len(text) / float(MAXMESSAGESIZE)))
+                               chunk = 0
+                               guid = msn.random_guid()
+                               while chunk < chunks:
+                                       offset = chunk * MAXMESSAGESIZE
+                                       text = message[offset : offset + MAXMESSAGESIZE]
+                                       message = msn.MSNMessage(message=str(text.replace("\n", "\r\n").encode("utf-8")))
+                                       message.ack = msn.MSNMessage.MESSAGE_NACK
+                                       if chunk == 0:
+                                               message.setHeader("Content-Type", "text/plain; charset=UTF-8")
+                                               message.setHeader("Chunks", str(chunks))
+                                       else:
+                                               message.setHeader("Chunk", str(chunk))
+
+                                       d = msn.SwitchboardClient.sendMessage(self, message)
+                                       if not noerror:
+                                               d.addCallback(failedMessage)
+
+                                       chunk += 1
+
+                       self.resetTimer()
+       
+       def gotFileReceive(self, fileReceive):
+               LogEvent(INFO, self.ident)
+               self.msncon.gotFileReceive(fileReceive)
+       
+
+
+class OneSwitchboardSession(SwitchboardSessionBase, msn.SwitchboardClient):
+       def __init__(self, msncon, remoteUser):
+               SwitchboardSessionBase.__init__(self, msncon)
+               msn.SwitchboardClient.__init__(self)
+               self.remoteUser = remoteUser
+               self.ident = (self.msncon, self.remoteUser)
+               self.chattingUsers = []
+       
+       def _ready(self):
+               LogEvent(INFO, self.ident)
+               self.ready = True
+               for user in self.chattingUsers:
+                       self.userJoined(user)
+               if not self.timeout.called:
+                       self.timeout.cancel()
+               del self.timeout
+               self.flushBuffer()
+       
+       def failedMessage(self, text):
+               self.msncon.failedMessage(self.remoteUser, text)
+       
+       # Callbacks
+       def loggedIn(self):
+               LogEvent(INFO, self.ident)
+               if not self.reply:
+                       def failCB(arg=None):
+                               LogEvent(INFO, ident, "User has not joined after 30 seconds.")
+                               del self.msncon.switchboardSessions[self.remoteUser]
+                       d = self.inviteUser(self.remoteUser)
+                       d.addErrback(failCB)
+                       self.timeout = reactor.callLater(30.0, failCB)
+               else:
+                       self._ready()
+       
+       def gotChattingUsers(self, users):
+               for userHandle in users.keys():
+                       self.chattingUsers.append(userHandle)
+       
+       def userJoined(self, userHandle, screenName):
+               LogEvent(INFO, self.ident)
+               if not self.reply:
+                       self._ready()
+               if userHandle != self.remoteUser:
+                       # Another user has joined, so we now have three participants.
+                       switchToGroupchat(self, self.remoteUser, userHandle)
+               else:
+                       self.requestAvatar()
+
+       def userLeft(self, userHandle):
+               def wait():
+                       if userHandle == self.remoteUser:
+                               del self.msncon.switchboardSessions[self.remoteUser]
+               reactor.callLater(0, wait) # Make sure this is handled after everything else
+
+       def gotMessage(self, message):
+               LogEvent(INFO, self.ident)
+               self.msncon.gotMessage(self.remoteUser, message.getMessage())
+       
+       def gotFileReceive(self, fileReceive):
+               LogEvent(INFO, self.ident)
+               self.msncon.gotFileReceive(fileReceive)
+       
+       def gotContactTyping(self, message):
+               LogEvent(INFO, self.ident)
+               self.msncon.gotContactTyping(message.userHandle)
+       
+       def sendTypingNotification(self):
+               LogEvent(INFO, self.ident)
+               if self.ready:
+                       self.sendTypingNotification()
+       
+       CAPS = msn.MSNContact.MSNC1 | msn.MSNContact.MSNC2 | msn.MSNContact.MSNC3 | msn.MSNContact.MSNC4
+       def requestAvatar(self):
+               if not self.ready: return
+               msnContacts = self.msncon.getContacts()
+               if not msnContacts: return
+               msnContact = msnContacts.getContact(self.remoteUSer)
+               if not (msnContact and msnContact.caps & self.CAPS and msnContact.msnobj): return
+               if msnContact.msnobjGot: return
+               msnContact.msnobjGot = True # This is deliberately set before we get the avatar. So that we don't try to reget failed avatars over & over
+               self.sendAvatarRequest(self.remoteUser, msnContact.msnobj)
+       
+       
+
index bcf26007febafb89b2f99dd74d876954514cf664..e418875e3f3df60b006095ce7fed0487a5e8d16d 100644 (file)
@@ -146,13 +146,13 @@ class DummyNotificationClient(msn.NotificationClient):
     def gotProfile(self, message):
         self.state = 'PROFILE'
 
-    def gotContactStatus(self, code, userHandle, screenName):
+    def gotContactStatus(self, userHandle, code, screenName):
         if code == msn.STATUS_AWAY and userHandle == "foo@bar.com" and screenName == "Test Screen Name":
             c = self.factory.contacts.getContact(userHandle)
             if c.caps & msn.MSNContact.MSNC1 and c.msnobj:
                 self.state = 'INITSTATUS'
 
-    def contactStatusChanged(self, code, userHandle, screenName):
+    def contactStatusChanged(self, userHandle, code, screenName):
         if code == msn.STATUS_LUNCH and userHandle == "foo@bar.com" and screenName == "Test Name":
             self.state = 'NEWSTATUS'
 
@@ -809,7 +809,7 @@ class DummySwitchboardClient(msn.SwitchboardClient):
         if userHandle == "friend@hotmail.com":
             self.state = 'USERLEFT'
 
-    def userTyping(self, message):
+    def gotContactTyping(self, message):
         if message.userHandle == 'foo@bar.com':
             self.state = 'USERTYPING'
 
diff --git a/src/tlib/msn/test_msnw.py b/src/tlib/msn/test_msnw.py
new file mode 100644 (file)
index 0000000..165e932
--- /dev/null
@@ -0,0 +1,167 @@
+# Copyright 2005 James Bunton <james@delx.cjb.net>
+# Licensed for distribution under the GPL version 2, check COPYING for details
+
+"""
+Test cases for msnw (MSN Wrapper)
+"""
+
+# Twisted imports
+from twisted.internet.defer import Deferred
+from twisted.internet import reactor, error
+from twisted.trial import unittest
+from twisted.python import log
+
+# System imports
+import sys
+
+# Local imports
+import msnw
+
+
+# Settings
+TIMEOUT = 30.0 # Connection timeout in seconds
+LOGGING = True
+USER1 = "messengertest1@hotmail.com"
+PASS1 = "hellohello"
+USER2 = "messengertest2@hotmail.com"
+PASS2 = "hellohello"
+
+
+
+if LOGGING:
+       log.startLogging(sys.stdout)
+
+
+checkCount = 0 # Uck!
+def clearAccount(msncon):
+       """ Clears the contact list of the given MSNConnection. Returns a
+       Deferred which fires when the task is complete.
+       """
+       d = Deferred()
+       count = 0
+       global checkCount
+       checkCount = 0
+       def cb(ignored=None):
+               global checkCount
+               checkCount += 1
+               if checkCount == count:
+                       d.callback(None)
+
+       for msnContact in msncon.getContacts().contacts.values():
+               for list in [msnw.FORWARD_LIST, msnw.BLOCK_LIST, msnw.ALLOW_LIST, msnw.PENDING_LIST]:
+                       if msnContact.lists & list:
+                               msncon.remContact(list, msnContact.userHandle).addCallback(cb)
+                               count += 1
+
+       if count == 0:
+               reactor.callLater(0, d.callback, None)
+       return d
+
+
+####################
+# Basic connection #
+####################
+
+class MSNConnection(msnw.MSNConnection):
+       def __init__(self, username, password, ident, testCase):
+               msnw.MSNConnection.__init__(self, username, password, ident)
+               self.testCase = testCase
+
+       def listSynchronized(self):
+               # Now we're fully connected
+               self.testCase.done = "SYNCED"
+       
+       def gotMessage(self, userHandle, text):
+               self.testCase.done = "GOTMESSAGE"
+               self.message = (userHandle, text)
+       
+       def contactAddedMe(self, userHandle):
+               print "CONTACT ADDED ME"
+               self.contactAdded = userHandle
+       
+
+class BasicConnection(unittest.TestCase):
+       def failed(self, why):
+               self.done = True
+       
+       def testConnect(self):
+               self.done = False
+               self.timeout = reactor.callLater(TIMEOUT, self.failed)
+               self.user = MSNConnection(USER1, PASS1, "user", self)
+               while not self.done:
+                       reactor.iterate(0.1)
+               self.failUnless((self.done == "SYNCED"), "Failed to connect to MSN servers.")
+               self.user.logOut()
+               reactor.iterate(0.1)
+               try:
+                       self.timeout.cancel()
+               except (error.AlreadyCancelled, error.AlreadyCalled):
+                       pass
+
+class BasicTests(unittest.TestCase):
+       def setUp(self):
+               self.failure = None
+               self.timeout = None
+               self.done = False
+               
+               # Login both accounts
+               self.user1 = MSNConnection(USER1, PASS1, "user1", self)
+               self.loop("Logging in.", cond="SYNCED")
+               self.user2 = MSNConnection(USER2, PASS2, "user2", self)
+               self.loop("Logging in.", cond="SYNCED")
+
+               # Purge both contact lists
+               clearAccount(self.user1).addCallback(self.cb)
+               self.loop("Purging user1 contact list.")
+               clearAccount(self.user2).addCallback(self.cb)
+               self.loop("Purging user2 contact list.")
+
+               # Adding users to each other's lists
+               self.user1.addContact(msnw.FORWARD_LIST, USER2).addCallback(self.cb)
+               self.loop("Adding user2 to user1's forward list.")
+               self.user1.addContact(msnw.ALLOW_LIST, USER2).addCallback(self.cb)
+               self.loop("Adding user2 to user1's allow list.")
+               self.user2.addContact(msnw.FORWARD_LIST, USER1).addCallback(self.cb)
+               self.loop("Adding user1 to user2's forward list.")
+               self.user2.addContact(msnw.ALLOW_LIST, USER1).addCallback(self.cb)
+               self.loop("Adding user1 to user2's allow list.")
+
+               # Check the contacts have seen each other
+               self.loop("Waiting...")
+               self.failUnless((self.user1.contactAdded == USER2 and self.user2.contactAdded == USER1), "Contacts can't see each other.")
+
+
+       def tearDown(self):
+               self.user1.logOut()
+               self.user2.logOut()
+               reactor.iterate(0.1)
+       
+       def cb(self, ignored=None):
+               self.done = True
+
+       def loop(self, failMsg, cond=None):
+               # Loops with a timeout
+               self.done = False
+               self.timeout = reactor.callLater(TIMEOUT, self.failed, "Timeout: " + failMsg)
+               while not self.done:
+                       reactor.iterate(0.1)
+               try:
+                       self.timeout.cancel()
+               except (error.AlreadyCancelled, error.AlreadyCalled):
+                       pass
+               if self.failure:
+                       self.fail(self.failure)
+               if cond:
+                       self.failUnless((self.done == cond), "Failed: " + failMsg)
+       
+       def failed(self, why):
+               self.failure = why
+               self.done = True
+
+       # The tests!
+       def testMessageExchange(self):
+               self.user1.sendMessage(USER2, "Hi user2")
+               self.loop("Timeout exchanging message 1.", cond="GOTMESSAGE")
+               self.failUnless((self.user1.message == (USER2, "Hi user1")), "Failed to transfer message 1.")
+
+