# -*- coding: utf-8 -*-

#   This file is part of emesene.
#
#    Emesene is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    emesene is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with emesene; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

import os
import time
import urllib
import gobject

import Msnobj
import Socket # emesenelib
import p2p.transfers

import common

EMESENE_ACK_CHECK_MSECS = 30

class Switchboard(gobject.GObject):
    '''This class represents a switchboard connection and provides methods
    and signals to interact with it'''
    
    __gsignals__ = {
        'typing' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
            (gobject.TYPE_STRING,)),
        # body, format, charset
        'message-sent' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
            (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, 
                gobject.TYPE_PYOBJECT)),
        # message
        'non-sent-message' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
            (gobject.TYPE_PYOBJECT,)),
        'nudge-sent' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
            ()),
        'ink-sent' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
            ()),
        # data
        'action-sent' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
            (gobject.TYPE_STRING,)),
        'user-join' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
            (gobject.TYPE_STRING,)),
        'user-leave' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
            (gobject.TYPE_STRING,)),
        
        'message' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
            #tid, nick, body, format, charset, hasP4context
            (gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, 
             gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)),
        'action-message' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
            (gobject.TYPE_STRING, gobject.TYPE_STRING)),
            #tid, data
        'ink-message' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
            (gobject.TYPE_STRING, gobject.TYPE_STRING)),
            #tid, filename

        'nudge': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
            (gobject.TYPE_STRING,)),
        'status-change': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
            
        # this signal is emited when a message
        # with a custom emoticon is received
        'custom-emoticon-received': \
            (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
            (gobject.TYPE_STRING,gobject.TYPE_PYOBJECT,)),
        
        # tid, msnobj
        'wink': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
            (gobject.TYPE_STRING,gobject.TYPE_PYOBJECT,)),
    } 
    
    MESSAGE_LIMIT = 1202

    def __init__(self, id, msn, type, status = 'pending'):
        '''class contructor
        type can be 'requested' or 'invited', its 'requested' if we request the
        switchboard and guess what invited means? :P.
        status is pending initialy, then when we process the connection string is
        connected and is stablished when someone join the conversation, 
        if something goes wrong the status is closed'''

        gobject.GObject.__init__(self)
        self.id = id
        
        self.status = status
        self.validStatus = ['pending', 'connected', 'established',
                            'closed', 'error']
        self.invalidTransitions = {
            'connected': ['pending'],
            'established': ['pending', 'connected']
        }
        self.connectionString = ''
        
        self.msn = msn

        self.user = self.msn.user.lower()
        self.proxy = self.msn.proxy
        
        self.host = ''
        self.port = 0
        self.authenticationType = ''
        self.authenticationString = ''
        self.sessionID = ''
        self.command = ''
        self.socket = None

        self.members = {}
        self.outDict = {}   
        self.msg_count = 0
        # messageQueue: a list that holds the messages sent when 
        # the status was 'pending'. It's a list of dicts like this
        # {'msg': msg, 'header': header, 'acknowledgeType': type}'
        self.messageQueue = [] 

        # invitationQueue: a list of mails to invite when we go
        # to established status
        self.invitationQueue = []
        # inkSessions: {session: dict{'file','total','actual'}}
        self.inkSessions = {}
        # pendingCEs {mail: [(creator, msnobj, name), ...]}
        # these are params to getCustomEmoticon()
        self.pendingCEs = {}

        # { Message-ID: MultiPacketBuffer }
        self.packet_buffer = {}

        self.p2p_output_id = 0
        self.p2p_output_interval = 100
        
        self.firstMessage = False
        self.firstUser = ''

        self.error = 0
        # needed for logger
        self.started = time.time()
        
        # if True, conversation will overwrite sender's nicks 
        # with p4 context, if available
        self.canUseP4 = msn.canUseP4
        
    def __repr__(self):
        return '<Switchboard users: ' + str(self.members.keys()) + '>' 

    def emit(self, signal, *args):
        gobject.GObject.emit(self, signal, *args)
        if self.msn:
            self.msn.emit('switchboard::' + signal, self, signal, args)
   
    def parse(self):
        '''parse the connection string to fill the attributes'''
    
        if self.connectionString == '':
            return        

        if self.connectionString.split()[0] == 'XFR':
            # example
            # XFR 15 SB 207.46.108.37:1863 CKI 17262740.1050826919.32308\r\n
            (command, tid, sb, host,
             authenticationType,
             authenticationString,
             u, server) = self.connectionString.split()[:8]
        else:
            # example
            # RNG 11752013 207.46.108.38:1863 CKI 849102291.520491113 \
            # example@passport.com Example%20Name
            (command, sessionID, host,
             authenticationType,
             authenticationString,
             user, userName, u, server) = self.connectionString.split()[:9]

            self.sessionID = sessionID
             
            self.addMember(user, userName, initial=True)

        self.command = command
        self.host = host.split(':')[0]
        self.port = int(host.split(':')[1])
        self.authenticationType = authenticationType
        self.authenticationString = authenticationString
        
    def leaveChat(self):
        '''leave the conversation'''
        self.setStatus('closed')
        try:
            self.socket.send('OUT\r\n')
        except Exception, e:
            pass

    def socketHangup(self, obj=None):
        '''The socket got into IO_HUP or IO_ERR status'''
        common.debug(str(self) + ' hangup/error', 'switchboard')
        if self.status != 'error':
            self.setStatus('closed')
        return False
        
    def process(self, obj=None):
        '''read the info from Socket and process the info'''
        if self.socket == None:
            return False
        
        def close(message, status='closed'):
            common.debug(message, 'switchboard')
            self.setStatus(status)
            
        try:
            (command, tid, params) = self.socket.receiveCommand()
        except Exception, e:
            close(str(self) + ' error, closing (receive)\n' + str(e))
            return False
        
        if command == '':
            # empty command means some socket borkedness
            close('received empty command, the socket is broken')
            return False
        elif command == 'MSG':
            try:
                message = self.socket.receivePayload(int(params.split()[1]))
            except Exception, e:
                close(str(self) + ' error, closing (payload)\n' + str(e))
                return False
                
            common.debug(message, 'switchboard')
            try:
                header, body = splitMsg(message)
            except IndexError:
                common.debug("malformed message", "switchboard")

            # handle multi-part message
            if 'Message-ID' in header:
                message_id = header['Message-ID']
                
                if not self.packet_buffer.has_key(message_id):
                    self.packet_buffer[message_id] = MultiPacketBuffer()
                
                buf = self.packet_buffer[message_id]
                buf.append_chunk(header, body)
                
                if buf.is_complete():
                    header, body = buf.get_message()
                    del self.packet_buffer[message_id]
                else:
                    return # message not complete yet
                
            Type = ''
            
            if "P4-Context" in header and self.canUseP4:
                nick = header['P4-Context']
                hasP4 = True
            else:
                nick = params.split()[0]
                hasP4 = False
                
            if "Content-Type" in header:
                Type = header["Content-Type"]

            if Type.startswith('text/plain'):
                # MSG god@gmail.com God%20http://www.400monkeys.com/God/ 111\r\n
                # MIME-Version: 1.0
                # Content-Type: text/plain; charset=UTF-8
                # X-MMS-IM-Format: FN=Sans; EF=; CO=000000; PF=0
                # 
                # a
                
                if not self.firstMessage:
                    self.firstMessage = True
                    self.msn.emit('new-conversation', self.firstUser, self)
                
                format = ''
                if 'X-MMS-IM-Format' in header:
                    format = header['X-MMS-IM-Format']
                
                try:
                    charset = Type.split('text/plain; charset=')[1]
                except IndexError:
                    charset = ''
                
                self.emit('message', tid, nick, body, format, charset, hasP4)
                self.msn.emit('message-received', tid)

                # WLM doesn't reply invites before the actual MSG
                if tid in self.pendingCEs:
                    for mail, msnobj, name in self.pendingCEs[tid]:
                        self.getCustomEmoticon(mail, msnobj, name)
                    del self.pendingCEs[tid]
                
            elif Type == 'text/x-msnmsgr-datacast':
                # datacasts: nudges, winks and actions
                if body.find('ID: 1') != -1:
                    # nudges
                    if not self.firstMessage:
                        self.firstMessage = True
                        self.msn.emit('new-conversation', self.firstUser, self)
                    
                    self.msn.emit('nudge-received', tid)
                    self.emit('nudge', tid)
                
                elif body.find('ID: 2') != -1:
                    # winks
                    data = body.split("Data: ")[1]
                    if not self.firstMessage:
                        self.firstMessage = True
                        self.msn.emit('new-conversation', self.firstUser, self)
                    msnobj = Msnobj.createFromString(data, False)
                    self.get_wink(tid, msnobj)
                    
                    self.emit('wink', tid, msnobj)
                    
                elif body.find('ID: 4') != -1:
                    # actions
                    if not self.firstMessage:
                        self.firstMessage = True
                        self.msn.emit('new-conversation', self.firstUser, self)
                    data = body.split("Data: ")[1] 
                    self.emit('action-message', tid, data)
                    self.msn.emit('message-received', tid)
                    
            
            elif Type == 'text/x-msmsgscontrol':
                self.emit('typing', tid)

            elif Type == 'text/x-mms-emoticon' or \
                 Type == 'text/x-mms-animemoticon':
                if not self.firstMessage:
                    self.firstMessage = True
                    self.msn.emit('new-conversation', self.firstUser, self)
                self.parseCustomEmoticon(message)

            elif Type == 'application/x-msnmsgrp2p' and \
                 'P2P-Dest' in header and \
                 header['P2P-Dest'].lower() == self.user.lower():
                
                self.msn.p2p[tid].receive_message(body)

            else:
                common.debug("Unhandled content type: " + str(header),
                    'switchboard')
                
        elif command == 'USR':
            if params.split(' ')[0] == 'OK':
                self.setStatus('connected')
            else:
                close('can\'t connect to switchboard: USR ' + str(params))

        elif command == 'IRO':
            #IRO 1 1 1 god@gmail.com urlencoded%20nick 1615642660\r\n
            (currentNumber, totalNumber, mail, nick, clientID) = \
                params.split(' ')
            self.addMember(mail.lower(), nick)
            
            self.emit('user-join', mail.lower())
                
            self.setStatus('established') 
            
        elif command == 'JOI':
            # JOI god@gmail.com urlencoded%20nick 1615642660\r\n
            (nick, clientID) = params.split()[:2] # likely to break..
            self.addMember(tid, nick)
            
            self.emit('user-join', tid)
                
            self.setStatus('established') 
                
        elif command == 'ANS':
            if params == 'OK':
                self.setStatus('connected')
            else:
                close('can\'t connect to switchboard: ANS ' + str(params))
        elif command == 'BYE':
            wasGroupChat = self.isGroupChat()
            self.leave(tid)
            # TODO, check when a bye means an OUT
            # from the other side.
            if params == '1' or wasGroupChat:
                self.emit('user-leave', tid)
                
        elif command == 'ACK':
            self.msg_count -= 1
            if int(tid) in self.outDict:
                del self.outDict[int(tid)]
                #print "deleted ", tid, " have left:", self.outDict 
            return False
        elif command == 'NAK': #untested, never had any of this.
            self.msg_count -= 1
            if int(tid) in self.outDict:
                msg = self.outDict[int(tid)]
                self.check_outDict(tid)   
            return False 
        try:
            self.error = int(command)
            if len(self.members) == 0:
                close('server error %d, closing switchboard' % self.error, \
                    'error')
                self.setStatus('error')
                # flush message in queue as oims
                for mess in self.messageQueue:
                    self.msn.msnOIM.send(self.firstUser, mess['msg'])
                return False
        except ValueError:
            pass

        return True
        
    def add_to_outdict(self, ctype, tid, msg):
        '''this method adds your outgoing text message to a dict, and checks
           back in TIMEOUT milliseconds if the message has been delivered 
           correctly'''
        if ctype == 'text/plain; charset=UTF-8' or ctype == 'text/x-msnmsgr-datacast':
            # crappy sendPayloadCommand returns ++tid so...
            self.outDict[int(tid) - 1] = msg
            gobject.timeout_add_seconds(EMESENE_ACK_CHECK_MSECS, self.check_outDict, int(tid) - 1)
            #print "added number", tid - 1
    
    def check_outDict(self, tid, dontClose=0):
        '''hy, i check for acks otherwise i kill me (switchboard)'''
        if int(tid) in self.outDict:
            # we haven't received the ack in the specified timeout.
            # a good thing is recreate the switchboard, so we set our status
            # to closed and spit out to the conversation the non-sent message
            # wdyt of this? --c10ud
            self.emit('non-sent-message', self.outDict[int(tid)])
            del self.outDict[int(tid)]
            common.debug('Timeout on ACKing in switchboard because of ' + str(tid) + \
                    ' closing switchboard', 'switchboard')
            self.setStatus('closed') 
            
        return False
    
    def getStyle(self, message=''): #borrowed from Conversation.py
        '''return the style string to use in the sendMessage method'''

        effectValue = ''

        if self.config.user['fontBold']:
            effectValue += 'B'

        if self.config.user['fontItalic']:
            effectValue += 'I'

        if self.config.user['fontUnderline']:
            effectValue += 'U'

        if self.config.user['fontStrike']:
            effectValue += 'S'

        color = self.config.user['fontColor'].replace('#', '')
        color = color[ 4:6 ] + color[ 2:4 ] + color[ :2 ]

        face = self.config.user['fontFace'].replace(' ', '%20')

        return "X-MMS-IM-Format: FN=" + face + \
            "; EF=" + effectValue + "; CO=" + color + \
            "; PF=0 ;RL=" + self.getRTL(message)
    
    def parseCustomEmoticon(self, message):
        body = message.split('\r\n\r\n')[1]
        l = body.split('\t')
        d = {}
      
        while len(l) > 0:
            if len(l) < 2:
                break
            shortcut = l.pop(0)
            msnobjString = l.pop(0)
            msnobj = Msnobj.createFromString(msnobjString, False)
           
            if msnobj != None:
                self.emit('custom-emoticon-received', shortcut, msnobj)
                filename = shortcut + '_' + msnobj.sha1d + '.tmp'
                filename = urllib.quote(filename).replace('/', '_')
                completeFileName = self.msn.cacheDir + os.sep + filename
                
                if not os.path.isfile(completeFileName):
                    # WLM doesn't reply invites before the actual MSG
                    mail = msnobj.getCreator()
                    if mail not in self.pendingCEs:
                        self.pendingCEs[mail] = []
                    self.pendingCEs[mail].append((mail, msnobj, \
                        completeFileName))
                else:
                    self.msn.emit('custom-emoticon-transfered', None, \
                        msnobj, completeFileName)

    def _output_ready(self):
        '''This function sucks. Ya, rly (c10ud)'''
        self.p2p_set_output_connected(True)

        if self.msg_count < 5:
            self.msn.p2p[self.firstUser].output_ready(self)

            if self.msg_count < 2 and self.p2p_output_interval > 50:
                self.p2p_output_interval -= 10

        elif self.p2p_output_interval < 300:
            self.p2p_output_interval += 10

        return True

    def p2p_set_output_connected(self, value):
        '''(dis)connects socket output_ready "signal"'''
        if self.socket:
            if self.p2p_output_id:
                gobject.source_remove(self.p2p_output_id)
                self.p2p_output_id = 0
            
            if value:
                self.p2p_output_id = gobject.timeout_add(
                    self.p2p_output_interval, self._output_ready)

    def p2p_send(self, message, mail):
        '''Sends a p2p message, called by P2PManager'''
        self.sendMessage(str(message), '', 'application/x-msnmsgrp2p',
            'D', 'P2P-Dest: ' + str(mail))

    def sendMessage(self, msg='', format='', \
                     contentType='text/plain; charset=UTF-8', \
                     acknowledgeType='A', extraHeader=''):
        
        header = "MIME-Version: 1.0\r\n"
        
        if contentType != "":
            header += "Content-Type: " + contentType + "\r\n"

        if extraHeader != "":
            header += extraHeader + '\r\n'
            
        if contentType == 'text/plain; charset=UTF-8':
            msg = msg[:1100] #TODO: use something like MAX_MESSAGE_LENGTH
            self.emit('message-sent', msg, format, 
                contentType.split('charset=')[1])
        
        if format != "":
            header += format + "\r\n\r\n"
        else:
            header += "\r\n"
        
        if self.status != 'established':
            self.messageQueue.append({
                'msg': msg, \
                'header': header, \
                'acknowledgeType': acknowledgeType, \
                'contentType': contentType \
            })

        elif self.status == 'established':
            try:
                tid = self.socket.sendPayloadCommand('MSG', acknowledgeType, \
                    header + msg)
                # append to out queue, only if this is a message
                self.add_to_outdict(contentType, tid, msg)
                self.msg_count += 1
            except Exception, e:
                #raise e
                common.debug('socket error on switchboard, closing',
                    'switchboard')
                common.debug(str(e), 'switchboard')
                self.setStatus('closed')
        
    def sendKeepAlive(self):
        ''' sends a keepalive to current switchboard '''
        self.sendMessage('', '', 'text/x-keep-alive', 'A')            

    def flushMessageQueue(self):
        '''send all the unsended messages'''
        
        if self.status != 'established' or len(self.messageQueue) == 0:
            return
        
        for i in self.messageQueue:
            try:
                tid = self.socket.sendPayloadCommand('MSG', \
                    i['acknowledgeType'], i['header'] + i['msg'])
                self.add_to_outdict(i['contentType'], tid, i['msg'])
                self.msg_count += 1
            except Exception, e:
                common.debug('socket error on switchboard, ' + \
                    'closing switchboard', 'switchboard')
                common.debug(str(e), 'switchboard')
                self.setStatus('closed')
        # flushed
        self.messageQueue = []
        common.debug('message queue flushed', 'switchboard')

    def sendNudge(self):
        '''a easy method to send a nudge'''

        self.sendMessage('ID: 1\r\n\r\n', '', 'text/x-msnmsgr-datacast')
        self.emit('nudge-sent')

    def sendAction(self, data):
        '''a easy method to send an action message - this works with MSNC6+'''

        self.sendMessage('ID: 4\r\nData: %s\r\n' % data, '',
            'text/x-msnmsgr-datacast')
        self.emit('action-sent', data)

    def sendIsTyping(self):
        '''a easy method to send the is typing message'''

        self.sendMessage('\r\n', '', 'text/x-msmsgscontrol\r\nTypingUser: ' + 
            self.user)

    def invite(self, mail):
        '''invite somebody to the switchboard conversation'''

        mail = mail.lower()
        if self.firstUser == '':
            self.firstUser = mail

        if self.status in ('connected', 'established') and \
           not mail in self.members.keys():
            try:
                self.socket.sendCommand('CAL', mail)
            except Exception, e:
                #raise e
                common.debug('socket error on switchboard, ' +
                    'closing switchboard', 'switchboard')
                common.debug(str(e), 'switchboard')
                self.setStatus('closed')
                return
            
            if mail in self.invitationQueue:
                self.invitationQueue.pop(self.invitationQueue.index(mail))
        elif not mail in self.invitationQueue:
            self.invitationQueue.append(mail.lower())

    def leave(self, mail):
        '''remove the mail from the members list'''
        
        if mail.lower() in self.members.keys():
            if len(self.members) == 1:
                self.setStatus('closed')
            else:
                del self.members[mail.lower()]
    
    def setConnectionString(self, connectionString):
        '''we received the conecction string (the first response of a XFR
        or the RNG) and here we process it'''
        
        self.connectionString = connectionString        
        self.parse()
        self.connectSocket()
        
    def connectSocket(self):
        '''connect the socket'''

        if self.proxy != None:
            self.socket = Socket.HTTPSocket(self.host, self.port, \
                self.proxy, 'SB')
        else:
            self.socket = Socket.Socket(self.host, self.port)
            
        self.socket.connect('input', self.process)
        self.socket.connect('hangup', self.socketHangup)
        
        if self.command == 'XFR':
            try:
                self.socket.sendCommand("USR", self.user + " " + \
                    self.authenticationString)
            except Exception, e:
                #raise e
                common.debug('socket error on switchboard, ' + \
                    'closing switchboard', 'switchboard')
                common.debug(str(e), 'switchboard')
                self.setStatus('closed')
            
        else: #RNG
            # ANS 1 alice@passport.com 1056411141.26158 17342299\r\n
            try:
                self.socket.sendCommand("ANS", self.user + " " + \
                    self.authenticationString + " " + self.sessionID)
                # forcing some stuff here
                self.setStatus('connected')
                self.setStatus('established')                
            except Exception, e:
                #raise e
                common.debug('socket error on switchboard, ' + \
                    'closing switchboard', 'switchboard')
                common.debug(str(e), 'switchboard')
                self.setStatus('closed')

        if len(self.members) == 1:
            self.msn.p2p[self.firstUser].register(self)
        else:
            self.msn.p2p[self.firstUser].unregister(self)
        
    def addMember(self, mail, nick, initial=False):
        '''add a member to the members dict'''
        
        mail = mail.lower()
        self.members[mail] = nick.replace('%20', ' ')
        
        if not initial:
            if len(self.members) == 1:
                self.firstUser = mail
                self.msn.p2p[mail].register(self)
            else:
                self.msn.p2p[mail].unregister(self)

    def setStatus(self, status):
        '''set the status, the status can be 'pending', 'stablished' or 'closed'
        PLEASE change the status here and not directly because we may want
        to do thing when a status is changed
        XXX-DX: i think we need a property here, we can't ask "PLEASE" that
        way...'''
        
        if status in self.validStatus and self.status != status:
            if self.invalidTransitions.has_key(self.status) and \
               status in self.invalidTransitions[self.status]:
                return

            self.status = status

            if status in ('closed', 'error'):
                self.msn.p2p[self.firstUser].unregister(self)
                if self.socket:
                    self.socket.hangup()
            if status == 'error':
                try:
                    self.socket.send('OUT\r\n')
                except Exception, e:
                    pass
                self.socket = None
            if status == 'connected':
                for i in self.invitationQueue:
                    self.invite(i)
                    
                self.invitationQueue = []
            elif self.status == 'established':
                self.flushMessageQueue()
            
            self.emit('status-change')
        
    def getId(self): #FIXME: getter
        '''return the id of the switchboard, this is a unique identification
        of the switchboards. The value is the value of the Trid of the
        command that created the switchboard or the sessionid if the other
        user started the conversation, the value doesnt matter, what
        matters is that its unique.'''
        
        return self.id
    
    def getOnlineUsers(self):
        '''This method returns a list ol mails of the contacts who are
        not offline'''
        
        return self.msn.contactManager.getOnlineUsers()
    
    def getMembers(self):
        '''return a list of the members in the switchboard'''
        
        return self.members.keys()

    def isGroupChat(self):
        return (len(self.members) > 1)
        
    def getInvitedMembers(self):
        '''return a list of the members invited but not joined'''
        
        return self.invitationQueue
        
    def getDisplayPicture(self, email):
        '''start a P2P session to get the display picture'''
        if self.msn is None:
            return

        email = email.lower()
        contact = self.msn.contactManager.getContact(email)
        
        if contact == None:
            common.debug('contact (' + email + ') not found in ' + \
                'getDisplayPicture', 'switchboard')
            return
        
        msnobj = contact.msnobj
        
        if msnobj == None:
            common.debug(email + ' has no msnobj in getDisplayPicture', \
                'switchboard')
            return

        #print "Switchboard.getDisplayPicture(email=%s)" % email

        filename = os.path.join(self.msn.cacheDir, contact.displayPicturePath)
        if not os.path.exists(filename):
            #print "Requesting avatar for ", email
            p2p.transfers.Receiver(self.msn.p2p[email], msnobj)
        else:
            #print "Avatar cached at %s - updating path" % filename
            self.msn.emit("display-picture-changed", self, msnobj, email)
            
    def getCustomEmoticon(self, email, msnobj, filename):
        email = email.lower()
        msnobj.filename = filename
        p2p.transfers.Receiver(self.msn.p2p[email], msnobj)
        
    def sendCustomEmoticons(self, message):
        msnObjs = []
        msnObj = ''
        i = 0
        msnOM = self.msn.getMsnObjectsManager()
        for CE in msnOM.getIds():
            if i == 4:
                i = 0
                msnObjs.append(msnObj)
                msnObj = ''
            if message.find(CE) != -1:
                msnObj += CE + '\t' + str(msnOM.getById(CE)) + '\t'
                i += 1
        if msnObj != '':
            msnObjs.append(msnObj)
        for msnObj in msnObjs:
            self.sendMessage(msnObj, contentType='text/x-mms-animemoticon')
            
    def get_wink(self, email, msnobj):
        p2p.transfers.Receiver(self.msn.p2p[email], msnobj)

class MultiPacketBuffer:
    '''this class represents the buffer for a multichunk MIME-message'''
    
    def __init__(self):
        '''initialize the buffer'''
        self.chunks_total = 0
        self.chunks_received = 0
        self.body = []
        self.header = {}
        
    def append_chunk(self, header, body):
        '''appends a new chunk to the buffer'''
        
        if 'Chunks' in header:
            self.chunks_total = int(header['Chunks'])
        
        for key, val in header.iteritems():
            if key not in ['Chunks', 'Chunk', 'Message-ID']:
                self.header[key] = val
        
        self.chunks_received += 1
        self.body.append(body)
        
    def get_message(self):
        '''returns a (header, body) tuple.
        header is a dict, body is a string'''
        return (self.header, ''.join(self.body))
    
    def is_complete(self):
        '''returns True if we have all chunks, False otherwise'''
        return (self.chunks_received == self.chunks_total)
        
def splitMsg(message):
    '''return header(dict), body(str)'''
    part = message.split('\r\n\r\n')
    def htuple(x):
        parts = x.split(': ')
        return (parts[0], ': '.join(parts[1:]))
    header = dict([htuple(i) for i in part[0].split('\r\n')])
    body = '\r\n\r\n'.join(part[1:])
    return header, body
