X-Git-Url: https://code.delx.au/pymsnt/blobdiff_plain/cca826babd7e5a73cb4084a45b14a446f9d2225d..ce1cf65ab2ddbf0be6b2c747b72ac26197a580cd:/src/tlib/msn/msnw.py diff --git a/src/tlib/msn/msnw.py b/src/tlib/msn/msnw.py index 66d2105..87ed044 100644 --- a/src/tlib/msn/msnw.py +++ b/src/tlib/msn/msnw.py @@ -7,28 +7,25 @@ from twisted.internet.defer import Deferred from twisted.internet.protocol import ClientFactory # System imports -import math, base64, binascii, math +import math, base64, binascii # 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. +All interaction should be with the MSNConnection and MultiSwitchboardSession classes. You should not directly instantiate any objects of other classes. """ class MSNConnection: """ Manages all the Twisted factories, etc """ + MAXMESSAGESIZE = 1400 + SWITCHBOARDTIMEOUT = 30.0*60.0 + GETALLAVATARS = False + def __init__(self, username, password, ident): """ Connects to the MSN servers. @param username: the MSN passport to connect with. @@ -39,6 +36,8 @@ class MSNConnection: self.password = password self.ident = ident self.timeout = None + self.notificationFactory = None + self.notificationClient = None self.connect() LogEvent(INFO, self.ident) @@ -51,7 +50,11 @@ class MSNConnection: def _getNotificationReferral(self): def timeout(): - if not d.called: d.errback() + self.timeout = None + dispatchFactory.d = None + if not d.called: + d.errback(Exception("Timeout")) + self.logOut() # Clean up everything self.timeout = reactor.callLater(30, timeout) dispatchFactory = msn.DispatchFactory() dispatchFactory.userHandle = self.username @@ -64,6 +67,7 @@ class MSNConnection: def _gotNotificationReferral(self, (host, port)): self.timeout.cancel() + self.timeout = None # Create the NotificationClient self.notificationFactory = msn.NotificationFactory() self.notificationFactory.userHandle = self.username @@ -75,7 +79,6 @@ class MSNConnection: def _sendSavedEvents(self): self.savedEvents.send(self) - self.savedEvents = None def _notificationClientReady(self, notificationClient): self.notificationClient = notificationClient @@ -115,7 +118,7 @@ class MSNConnection: elif not noerror: self.failedMessage(userHandle, text) - def requestAvatar(self, userHandle): + def sendAvatarRequest(self, userHandle): """ Requests the avatar of a contact. @@ -128,7 +131,7 @@ class MSNConnection: LogEvent(INFO, self.ident) if not self.notificationClient: return - if GETALLAVATARS: + if MSNConnection.GETALLAVATARS: self._ensureSwitchboardSession(userHandle) sb = self.switchboardSessions.get(userHandle) if sb: return sb.sendAvatarRequest() @@ -141,8 +144,9 @@ class MSNConnection: @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, + @return: A Deferred, which will fire with an argument of: + (fileSend, d) A FileSend object and a Deferred. + The new 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() @@ -150,11 +154,15 @@ class MSNConnection: NOTE: You MUST write() exactly as much as you declare in filesize. """ - raise NotImplementedError # May have to wait for the switchboardSession to exist + msnContact = self.getContacts().getContact(userHandle) + if not msnContact: + raise ValueError, "Contact not found" + self._ensureSwitchboardSession(userHandle) + return self.switchboardSessions[userHandle].sendFile(msnContact, filename, filesize) def sendTypingToContact(self, userHandle): """ - Sends typing notification to a contact. + Sends typing notification to a contact. Should send every 5secs. @param userHandle: the contact to notify of our typing. """ @@ -169,8 +177,8 @@ class MSNConnection: if self.notificationClient: LogEvent(INFO, self.ident) self.notificationClient.changeAvatar(imageData, push=True) - else: - self.savedEvents.avatarImageData = imageData + # Save the avatar for reuse on disconnection + self.savedEvents.avatarImageData = imageData def changeStatus(self, statusCode, screenName, personal): """ @@ -182,20 +190,25 @@ class MSNConnection: @param personal: the user's new personal message. """ + if not screenName: screenName = self.username + if not statusCode: statusCode = msn.STATUS_ONLINE + if not personal: personal = "" if self.notificationClient: - count = 0 - def cb(): - if count == 3: + changeCount = [0] # Hack for Python's limited scope :( + def cb(ignored=None): + changeCount[0] += 1 + if changeCount[0] == 3: self.ourStatusChanged(statusCode, screenName, personal) + def errcb(ignored=None): + pass # FIXME, should we do something here? 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 + self.notificationClient.changeStatus(statusCode.encode("utf-8")).addCallbacks(cb, errcb) + self.notificationClient.changeScreenName(screenName.encode("utf-8")).addCallbacks(cb, errcb) + self.notificationClient.changePersonalMessage(personal.encode("utf-8")).addCallbacks(cb, errcb) + # Remember the saved status + self.savedEvents.statusCode = statusCode + self.savedEvents.screenName = screenName + self.savedEvents.personal = personal def addContact(self, listType, userHandle): """ See msn.NotificationClient.addContact """ @@ -213,15 +226,23 @@ class MSNConnection: def logOut(self): """ Shuts down the whole connection. Don't try to call any - other methods after this one. """ + other methods after this one. Except maybe connect() """ if self.notificationClient: self.notificationClient.logOut() for c in self.connectors: c.disconnect() + self.connectors = [] if self.notificationFactory: + self.notificationFactory.stopTrying() self.notificationFactory.msncon = None - self.connectors = [] + self.notificationFactory = None + for sbs in self.switchboardSessions.values(): + if hasattr(sbs, "transport") and sbs.transport: + sbs.transport.loseConnection() self.switchboardSessions = {} + if self.timeout: + self.timeout.cancel() + self.timeout = None LogEvent(INFO, self.ident) @@ -229,6 +250,9 @@ class MSNConnection: def connectionFailed(self, reason=''): """ Called when the connection to the server failed. """ + def loginFailed(self, reason=''): + """ Called when the account could not be logged in. """ + def connectionLost(self, reason=''): """ Called when we are disconnected. """ @@ -261,19 +285,32 @@ class MSNConnection: def gotMessage(self, userHandle, text): """ Called when a contact sends us a message """ + def gotGroupchat(self, msnGroupchat, userHandle): + """ Called when a conversation with more than one contact begins. + userHandle is the person who invited us. + The overriding method is expected to set msnGroupchat.groupchat to an object + that implements the following methods: + contactJoined(userHandle) + contactLeft(userHandle) + gotMessage(userHandle, text) + + The object received as 'msnGroupchat' is an instance of MultiSwitchboardSession. + """ + + def gotContactTyping(self, userHandle): + """ Called when a contact sends typing notification. + Will be called once every 5 seconds. """ + 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(). """ + You should call sendAvatarRequest(). """ - def contactStatusChanged(self, userHandle, statusCode, screenName): + def contactStatusChanged(self, userHandle): """ 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. """ @@ -283,11 +320,26 @@ class MSNConnection: def contactRemovedMe(self, userHandle): """ Called when a contact removes the user from their list. """ + + def gotInitialEmailNotification(self, inboxunread, foldersunread): + """ Received at login to tell about the user's Hotmail status """ + + def gotRealtimeEmailNotification(self, mailfrom, fromaddr, subject): + """ Received in realtime whenever an email comes into the hotmail account """ + + def gotMSNAlert(self, body, action, subscr): + """ An MSN Alert (http://alerts.msn.com) was received. Body is the + text of the alert. 'action' is a url for more information, + 'subscr' is a url to modify your your alerts subscriptions. """ + + def gotAvatarImageData(self, userHandle, imageData): + """ An contact's avatar has been received because a switchboard + session with them was started. """ class SavedEvents: def __init__(self): - self.nickname = "" + self.screenName = "" self.statusCode = "" self.personal = "" self.avatarImageData = "" @@ -297,8 +349,8 @@ class SavedEvents: 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) + if self.screenName or self.statusCode or self.personal: + msncon.changeStatus(self.statusCode, self.screenName, self.personal) for listType, userHandle in self.addContacts: msncon.addContact(listType, userHandle) for listType, userHandle in self.remContacts: @@ -308,30 +360,48 @@ class SavedEvents: 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)) + d = self.factory.d + self.factory.d = None + if not d or d.called: + return # Too slow! We've already timed out + d.callback((host, port)) class NotificationClient(msn.NotificationClient): + def doDisconnect(self, *args): + if hasattr(self, "transport") and self.transport: + self.transport.loseConnection() + def loginFailure(self, message): - self.factory.msncon.connectionFailed(message) + self.factory.msncon.loginFailed(message) def loggedIn(self, userHandle, verified): LogEvent(INFO, self.factory.msncon.ident) msn.NotificationClient.loggedIn(self, userHandle, verified) self.factory.msncon._notificationClientReady(self) + self.factory.msncon.loggedIn() if not verified: self.factory.msncon.accountNotVerified() def logOut(self): msn.NotificationClient.logOut(self) + # If we explicitly log out, then all of these events + # are now redundant + self.loginFailure = self.doDisconnect + self.loggedIn = self.doDisconnect + self.connectionLost = lambda reason: msn.NotificationClient.connectionLost(self, reason) def connectionLost(self, reason): - if not self.factory.msncon: return # If we called logOut + if not self.factory.msncon: + # If MSNConnection.logOut is called before _notificationClientReady + return + def wait(): LogEvent(INFO, self.factory.msncon.ident) msn.NotificationClient.connectionLost(self, reason) - self.factory.msncon.connectionLost(reason) + if self.factory.maxRetries > self.factory.retries: + self.factory.stopTrying() + self.factory.msncon.connectionLost(reason) # Make sure this event is handled after any others reactor.callLater(0, wait) @@ -366,24 +436,28 @@ class NotificationClient(msn.NotificationClient): def gotContactStatus(self, userHandle, statusCode, screenName): LogEvent(INFO, self.factory.msncon.ident) - self.factory.msncon.contactStatusChanged(userHandle, statusCode, screenName) + self.factory.msncon.contactStatusChanged(userHandle) def contactStatusChanged(self, userHandle, statusCode, screenName): LogEvent(INFO, self.factory.msncon.ident) - self.factory.msncon.contactStatusChanged(userHandle, statusCode, screenName) + self.factory.msncon.contactStatusChanged(userHandle) - def contactOffline(self, userHandle): + def contactPersonalChanged(self, userHandle, personal): LogEvent(INFO, self.factory.msncon.ident) - self.factory.msncon.contactStatusChanged(userHandle, msn.STATUS_OFFLINE, "") + self.factory.msncon.contactStatusChanged(userHandle) + def contactOffline(self, userHandle): + LogEvent(INFO, self.factory.msncon.ident) + self.factory.msncon.contactStatusChanged(userHandle) + def gotSwitchboardInvitation(self, sessionID, host, port, key, userHandle, screenName): LogEvent(INFO, self.factory.msncon.ident) + sb = self.factory.msncon.switchboardSessions.get(userHandle) + if sb and sb.transport: + sb.transport.loseConnection() sb = OneSwitchboardSession(self.factory.msncon, userHandle) - sb.connectReply(host, port, key, sessionID) - sbOld = self.factory.msncon.switchboardSessions.get(userHandle) - if sbOld: - sbOld.disconnect() self.factory.msncon.switchboardSessions[userHandle] = sb + sb.connectReply(host, port, key, sessionID) def multipleLogin(self): LogEvent(INFO, self.factory.msncon.ident) @@ -395,29 +469,33 @@ class NotificationClient(msn.NotificationClient): -def switchToGroupchat(*args): - raise NotImplementedError - - -class SwitchboardSessionBase: +class SwitchboardSessionBase(msn.SwitchboardClient): def __init__(self, msncon): + msn.SwitchboardClient.__init__(self) self.msncon = msncon + self.msnobj = msncon.notificationClient.msnobj self.userHandle = msncon.username self.ident = (msncon.ident, "INVALID!!") self.messageBuffer = [] + self.funcBuffer = [] self.ready = False - def __del__(self): + def connectionLost(self, reason): + msn.SwitchboardClient.connectionLost(self, reason) LogEvent(INFO, self.ident) - del self.msncon - self.transport.disconnect() + self.ready = False + self.msncon = None + self.msnobj = None + self.ident = (self.ident[0], self.ident[1], "Disconnected!") - for message, noerror in self.messageBuffer: - if not noerror: - self.msncon.failedMessage(self.remoteUser, message) + def loggedIn(self): + LogEvent(INFO, self.ident) + self.ready = True + self.flushBuffer() def connect(self): LogEvent(INFO, self.ident) + self.ready = False def sbRequestAccepted((host, port, key)): LogEvent(INFO, self.ident) self.key = key @@ -433,6 +511,7 @@ class SwitchboardSessionBase: def connectReply(self, host, port, key, sessionID): LogEvent(INFO, self.ident) + self.ready = False self.key = key self.sessionID = sessionID self.reply = 1 @@ -444,64 +523,147 @@ class SwitchboardSessionBase: for message, noerror in self.messageBuffer[:]: self.messageBuffer.remove((message, noerror)) self.sendMessage(message, noerror) + for f in self.funcBuffer[:]: + self.funcBuffer.remove(f) + f() - def failedMessage(self, ignored): + def failedMessage(self, *ignored): raise NotImplementedError - def sendMessage(self, text, noerror): + def sendClientCaps(self): + message = msn.MSNMessage() + message.setHeader("Content-Type", "text/x-clientcaps") + message.setHeader("Client-Name", "PyMSNt") + if hasattr(self.msncon, "jabberID"): + message.setHeader("JabberID", str(self.msncon.jabberID)) + self.sendMessage(message) + + def sendMessage(self, message, noerror=False): + # Check to make sure that clientcaps only gets sent after + # the first text type message. + if isinstance(message, msn.MSNMessage) and message.getHeader("Content-Type").startswith("text"): + self.sendMessage = self.sendMessageReal + self.sendClientCaps() + return self.sendMessage(message, noerror) + else: + return self.sendMessageReal(message, noerror) + + def sendMessageReal(self, text, noerror=False): + if not isinstance(text, basestring): + msn.SwitchboardClient.sendMessage(self, text) + return if not self.ready: self.messageBuffer.append((text, noerror)) else: LogEvent(INFO, self.ident) + text = str(text.replace("\n", "\r\n").encode("utf-8")) def failedMessage(ignored): if not noerror: self.failedMessage(text) - if len(text) < MAXMESSAGESIZE: - message = msn.MSNMessage(message=str(text.replace("\n", "\r\n").encode("utf-8"))) - message.setHeader("Content-Type", "text/plain; charset=UTF-8") + if len(text) < MSNConnection.MAXMESSAGESIZE: + message = msn.MSNMessage(message=text) message.ack = msn.MSNMessage.MESSAGE_NACK d = msn.SwitchboardClient.sendMessage(self, message) if not noerror: - d.addCallback(failedMessage) + d.addCallbacks(failedMessage, failedMessage) else: - chunks = int(math.ceil(len(text) / float(MAXMESSAGESIZE))) + chunks = int(math.ceil(len(text) / float(MSNConnection.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"))) + offset = chunk * MSNConnection.MAXMESSAGESIZE + message = msn.MSNMessage(message=text[offset : offset + MSNConnection.MAXMESSAGESIZE]) message.ack = msn.MSNMessage.MESSAGE_NACK + message.setHeader("Message-ID", guid) if chunk == 0: - message.setHeader("Content-Type", "text/plain; charset=UTF-8") message.setHeader("Chunks", str(chunks)) else: + message.delHeader("MIME-Version") + message.delHeader("Content-Type") message.setHeader("Chunk", str(chunk)) d = msn.SwitchboardClient.sendMessage(self, message) if not noerror: - d.addCallback(failedMessage) + d.addCallbacks(failedMessage, failedMessage) chunk += 1 - def gotFileReceive(self, fileReceive): - LogEvent(INFO, self.ident) - self.msncon.gotFileReceive(fileReceive) + +class MultiSwitchboardSession(SwitchboardSessionBase): + """ Create one of me to chat to multiple contacts """ + + def __init__(self, msncon): + """ Automatically creates a new switchboard connection to the server """ + SwitchboardSessionBase.__init__(self, msncon) + self.ident = (self.msncon.ident, repr(self)) + self.contactCount = 0 + self.groupchat = None + self.connect() + + def failedMessage(self, text): + self.groupchat.gotMessage("BOUNCE", text) + def sendMessage(self, text, noerror=False): + """ Used to send a mesage to the groupchat. Can be called immediately + after instantiation. """ + if self.contactCount > 0: + SwitchboardSessionBase.sendMessage(self, text, noerror) + else: + #self.messageBuffer.append((message, noerror)) + pass # They're sending messages to an empty room. Ignore. + + def inviteUser(self, userHandle): + """ Used to invite a contact to the groupchat. Can be called immediately + after instantiation. """ + userHandle = str(userHandle) + if self.ready: + LogEvent(INFO, self.ident, "immediate") + msn.SwitchboardClient.inviteUser(self, userHandle) + else: + LogEvent(INFO, self.ident, "pending") + self.funcBuffer.append(lambda: msn.SwitchboardClient.inviteUser(self, userHandle)) + + def gotMessage(self, message): + self.groupchat.gotMessage(message.userHandle, message.getMessage()) + + def userJoined(self, userHandle, screenName=''): + LogEvent(INFO, self.ident) + self.contactCount += 1 + self.groupchat.contactJoined(userHandle) + + def userLeft(self, userHandle): + LogEvent(INFO, self.ident) + self.contactCount -= 1 + self.groupchat.contactLeft(userHandle) + -class OneSwitchboardSession(SwitchboardSessionBase, msn.SwitchboardClient): +class OneSwitchboardSession(SwitchboardSessionBase): def __init__(self, msncon, remoteUser): SwitchboardSessionBase.__init__(self, msncon) - msn.SwitchboardClient.__init__(self) - self.remoteUser = remoteUser - self.ident = (self.msncon, self.remoteUser) + self.remoteUser = str(remoteUser) + self.ident = (self.msncon.ident, self.remoteUser) self.chattingUsers = [] self.timeout = None + def connectionLost(self, reason): + if self.timeout: + self.timeout.cancel() + self.timeout = None + for message, noerror in self.messageBuffer: + if not noerror: + self.failedMessage(message) + self.messageBuffer = [] + + if self.msncon and self.msncon.switchboardSessions.has_key(self.remoteUser): + # Unexpected disconnection. Must remove us from msncon + self.msncon.switchboardSessions.pop(self.remoteUser) + + SwitchboardSessionBase.connectionLost(self, reason) + def _ready(self): LogEvent(INFO, self.ident) self.ready = True @@ -509,8 +671,17 @@ class OneSwitchboardSession(SwitchboardSessionBase, msn.SwitchboardClient): self.userJoined(user) if self.timeout: self.timeout.cancel() - del self.timeout + self.timeout = None self.flushBuffer() + + def _switchToMulti(self, userHandle): + LogEvent(INFO, self.ident) + del self.msncon.switchboardSessions[self.remoteUser] + self.__class__ = MultiSwitchboardSession + del self.remoteUser + self.contactCount = 0 + self.msncon.gotGroupchat(self, userHandle) + assert self.groupchat def failedMessage(self, text): self.msncon.failedMessage(self.remoteUser, text) @@ -520,7 +691,11 @@ class OneSwitchboardSession(SwitchboardSessionBase, msn.SwitchboardClient): LogEvent(INFO, self.ident) if not self.reply: def failCB(arg=None): - LogEvent(INFO, ident, "User has not joined after 30 seconds.") + self.timeout = None + self.transport.loseConnection() + if not (self.msncon and self.msncon.switchboardSessions.has_key(self.remoteUser)): + return + LogEvent(INFO, self.ident, "User has not joined after 30 seconds.") del self.msncon.switchboardSessions[self.remoteUser] d = self.inviteUser(self.remoteUser) d.addErrback(failCB) @@ -538,19 +713,45 @@ class OneSwitchboardSession(SwitchboardSessionBase, msn.SwitchboardClient): self._ready() if userHandle != self.remoteUser: # Another user has joined, so we now have three participants. - switchToGroupchat(self, self.remoteUser, userHandle) + remoteUser = self.remoteUser + self._switchToMulti(remoteUser) + self.userJoined(remoteUser) + self.userJoined(userHandle) else: - self.requestAvatar() + def updateAvatarCB((imageData, )): + if self.msncon: + self.msncon.gotAvatarImageData(self.remoteUser, imageData) + d = self.sendAvatarRequest() + if d: + d.addCallback(updateAvatarCB) def userLeft(self, userHandle): def wait(): if userHandle == self.remoteUser: - del self.msncon.switchboardSessions[self.remoteUser] + if self.msncon and self.msncon.switchboardSessions.has_key(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()) + cTypes = [s.strip() for s in message.getHeader("Content-Type").split(';')] + if "text/plain" == cTypes[0]: + try: + if len(cTypes) > 1 and cTypes[1].lower().find("utf-8") >= 0: + text = message.getMessage().decode("utf-8") + else: + text = message.getMessage() + self.msncon.gotMessage(self.remoteUser, text) + except UnicodeDecodeError: + LogEvent(WARN, self.ident, "Message lost!") + self.msncon.gotMessage(self.remoteUser, "A message was lost.") + raise + elif "text/x-clientcaps" == cTypes[0]: + if message.hasHeader("JabberID"): + jid = message.getHeader("JabberID") + self.msncon.userMapping(message.userHandle, jid) + else: + LogEvent(INFO, self.ident, "Discarding unknown message type.") def gotFileReceive(self, fileReceive): LogEvent(INFO, self.ident) @@ -563,10 +764,10 @@ class OneSwitchboardSession(SwitchboardSessionBase, msn.SwitchboardClient): def sendTypingNotification(self): LogEvent(INFO, self.ident) if self.ready: - self.sendTypingNotification() + msn.SwitchboardClient.sendTypingNotification(self) CAPS = msn.MSNContact.MSNC1 | msn.MSNContact.MSNC2 | msn.MSNContact.MSNC3 | msn.MSNContact.MSNC4 - def requestAvatar(self): + def sendAvatarRequest(self): if not self.ready: return msnContacts = self.msncon.getContacts() if not msnContacts: return @@ -574,7 +775,15 @@ class OneSwitchboardSession(SwitchboardSessionBase, msn.SwitchboardClient): 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) + return msn.SwitchboardClient.sendAvatarRequest(self, msnContact) + def sendFile(self, msnContact, filename, filesize): + def doSendFile(ignored=None): + d.callback(msn.SwitchboardClient.sendFile(self, msnContact, filename, filesize)) + d = Deferred() + if self.ready: + reactor.callLater(0, doSendFile) + else: + self.funcBuffer.append(doSendFile) + return d -