Logo Search packages:      
Sourcecode: kmess version File versions  Download package

chatmaster.cpp

/***************************************************************************
                          chatmaster.cpp  -  description
                             -------------------
    begin                : Sat Jan 18 2003
    copyright            : (C) 2003 by Mike K. Bennett
    email                : mkb137b@hotmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "chatmaster.h"

#include "chatwindow.h"
#include "chatmessage.h"

#include "../currentaccount.h"
#include "../kmessdebug.h"
#include "../msnobject.h"

#include "../contact/contact.h"
#include "../contact/contactlist.h"
#include "../network/msnswitchboardconnection.h"

#include "../network/applications/applicationlist.h"
#include "../network/applications/mimeapplication.h"
#include "../network/applications/p2papplication.h"
#include "../network/applications/picturetransferp2p.h"
#include "../network/applications/filetransfer.h"
#include "../network/applications/filetransferp2p.h"
#include "../network/applications/gnomemeeting.h"

#include <qfile.h>
#include <qtimer.h>

#include <klocale.h>
#include <knotifyclient.h>



// The constructor
ChatMaster::ChatMaster(QObject *parent)
 : QObject(parent, "ChatMaster"),
   initialized_(false)
{
  pendingDisplayPictures_.clear();
  pendingMimeMessages_.setAutoDelete(true);
}



// The destructor
ChatMaster::~ChatMaster()
{
  disconnected();

#ifdef KMESSDEBUG_CHATMASTER
  kdDebug() << "ChatMaster::~ChatMaster() - DESTROYED." << endl;
#endif
}


// The user has disconnected, so close all open chats and switchboard connections
void ChatMaster::disconnected()
{
  if( chatWindows_.count() > 0 )
  {
#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster::disconnected() - Closing " << chatWindows_.count() << " open chat windows." << endl;
#endif

    // Go through all the chat windows
    ChatWindow *chatWindow;
    QPtrListIterator<ChatWindow> it( chatWindows_ );
    while( ( chatWindow = it.current() ) != 0 )
    {
      ++it;

      // Disconnect the chat windows so they won't cause problems
      chatWindow->setSwitchboardConnection( 0 );

      // We should disable each and every part you can input to, not the whole window
      chatWindow->disable();
    }

    // Clear the chat window collection
    chatWindows_.setAutoDelete(false);
    chatWindows_.clear();
  }

  if( switchboardConnections_.count() > 0 )
  {
#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster::disconnected() - Closing open switchboards." << endl;
#endif

    MsnSwitchboardConnection *current;
    QPtrListIterator<MsnSwitchboardConnection> it( switchboardConnections_ );
    while( ( current = it.current() ) != 0 )
    {
      ++it;

      delete current;
    }
  }
}



// Forward the signal of a new incoming message from a chat window
void ChatMaster::forwardNewChatMessage( const ChatMessage &message, ChatWindow *chatWindow )
{
#ifdef KMESSTEST
  ASSERT( chatWindow != 0 );
#endif
#ifdef KMESSDEBUG_CHATMASTER
  kdDebug() << "ChatMaster::forwardNewChatMessage() - Forwarding the new chat message signal." << endl;
#endif
  emit newChatMessage( message, chatWindow );
}



// Forward a new switchboard server request signal coming from an existing switchboard connection
void ChatMaster::forwardRequestNewSwitchboard( QString handle )
{
#ifdef KMESSDEBUG_CHATMASTER
  kdDebug() << "ChatMaster::forwardRequestSwitchboard() - Forwarding the new switchboard request signal for "
            << handle << "." << endl;
#endif

  emit requestSwitchboard( handle, ChatInformation::CONNECTION_REFRESH );
}



// Forward a request to add or remove a contact from the contactlist, which comes from the ContactFrame.
void ChatMaster::forwardSetContactAdded( QString handle, bool isAdded )
{
  if( isAdded )
  {
    emit addContact( handle );
  }
  else
  {
    emit removeContact( handle, false );
  }
}



// Forward a request to allow a contact, which comes from the ContactFrame.
void ChatMaster::forwardSetContactAllowed( QString handle )
{
    emit allowContact( handle );
}



// Forward a request to block or unblock a contact, which comes from the ContactFrame.
void ChatMaster::forwardSetContactBlocked( QString handle, bool isBlocked )
{
  if( isBlocked )
  {
    emit blockContact( handle );
  }
  else
  {
    emit unblockContact( handle );
  }
}



// Return the application list for a given contact
ApplicationList * ChatMaster::getApplicationList(const QString &handle)
{
  ApplicationList *appList = 0;

  // Get the contact.
  ContactBase *contact = CurrentAccount::instance()->getContactByHandle(handle);
  if(KMESS_NULL(contact)) return 0;

  // Get the application list
  if( contact->hasApplicationList() )
  {
    appList = contact->getApplicationList();
  }
  else
  {
    // Create object if it wasn't created yet for this contact.
#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster::getApplicationList() - Creating application list for contact " << handle << endl;
#endif
    appList = contact->createApplicationList();

    // Connect signals from the applist
    connect( appList, SIGNAL(         newApplication(Application*)        ),   // A new application was created.
             this,      SLOT( slotConnectApplication(Application*)        ));
    connect( appList, SIGNAL(                 putMsg(const MimeMessage&, const QString&, bool)   ),   // Request to send message
             this,      SLOT( slotDeliverMimeMessage(const MimeMessage&, const QString&, bool)   ));
  }

  return appList;
}



// Return the chat window which uses the given switchboard connection
ChatWindow * ChatMaster::getChatWindowBySwitchboard(const MsnSwitchboardConnection *connection)
{
  // Look through the chat windows for an exclusive chat with the contact given
  for( ChatWindow *chatWindow = chatWindows_.first(); chatWindow; chatWindow = chatWindows_.next() )
  {
    if( chatWindow->getSwitchboardConnection() == connection )
    {
      return chatWindow;
    }
  }

  return 0;
}



// Return the chat window where we're having an conversation with the given contact.
ChatWindow * ChatMaster::getContactChatWindow(const QString &handle, bool privateChat)
{
  ChatWindow *result = 0;

  // Look through the chat windows for an exclusive chat with the contact given
  for( ChatWindow *chatWindow = chatWindows_.first(); chatWindow; chatWindow = chatWindows_.next() )
  {
    if( chatWindow->isExclusiveChatWithContact(handle) )
    {
      return chatWindow; // best candidate found.
    }
    else if( ! privateChat && chatWindow->isContactInChat( handle ) )
    {
      result = chatWindow;  // keep as option.
    }
  }

  return result;
}



// Return the chat window where we're having a conversation with the given contacts.
ChatWindow *ChatMaster::getContactsChatWindow( const QStringList &handles )
{
  // Do nothing if there are no open chats or if the list is empty (faster)
  if( handles.isEmpty() || chatWindows_.isEmpty() )
  {
    return 0;
  }

  ChatWindow *result = 0;

  // Look through the chat windows for a group chat with all the list's contacts
  for( ChatWindow *chatWindow = chatWindows_.first(); chatWindow; chatWindow = chatWindows_.next() )
  {
    bool found = true;

    // Check every contact in the list to see if they're all present in this chat
    for( QStringList::ConstIterator it = handles.begin(); it != handles.end(); ++it )
    {
      if( ! chatWindow->isContactInChat( *it ) )
      {
        found = false;
        break;
      }
    }

    if( found )
    {
      result = chatWindow;
      break;
    }
  }

  return result;
}



// Return the chat connection where we're having an conversation with the given contact.
MsnSwitchboardConnection * ChatMaster::getContactSwitchboardConnection(const QString &handle, bool privateChat)
{
  MsnSwitchboardConnection *current;
  QPtrListIterator<MsnSwitchboardConnection> it( switchboardConnections_ );
  while( ( current = it.current() ) != 0 )
  {
    ++it;

    if( ! current->isContactInChat( handle ) )
    {
      continue;
    }

    if( ( ! privateChat )
    ||  (   privateChat && current->isExclusiveChatWithContact( handle ) ) )
    {
      return current;
    }
  }

  return 0;

  // TODO: I've replaced this method's code to use ChatMaster's internal list of switchboards. If it works correctly,
  // the old code, commented below, can be deleted later on, maybe for 1.5 final.
  /*
  ContactBase *contact = CurrentAccount::instance()->getContactByHandle(handle);
  if(KMESS_NULL(contact)) return 0;

  MsnSwitchboardConnection *connection;

  // Get the connections directly from the contact, no need to navigate through all chat windows.
  const QPtrList<MsnSwitchboardConnection> &connections = contact->getSwitchboardConnections();
  QPtrListIterator<MsnSwitchboardConnection> it(connections);

  MsnSwitchboardConnection *result = 0;
  while( it.current() != 0 )
  {
    connection = it.current();
    if( connection->isExclusiveChatWithContact(handle) )
    {
      return connection;  // best candidate found
    }
    else if( ! privateChat && connection->isContactInChat(handle) )
    {
      result = connection;  // keep as option.
    }

    ++it;
  }

  // Have a second try, check for currently inactive switchboards.
  if( result == 0 )
  {
    QPtrListIterator<MsnSwitchboardConnection> it( switchboardConnections_ );
    while( it.current() != 0 )
    {
      connection = it.current();
      if( connection->isEmpty() && connection->getLastContact() == handle )
      {
        return connection;
      }
      ++it;
    }
  }

  return result;
  */
}



// Initialize the class
bool ChatMaster::initialize()
{
  if ( initialized_ )
  {
    kdDebug() << "ChatMaster::initialize() - Already initialized." << endl;
    return false;
  }

  // Connect the contact.
  CurrentAccount *currentAccount = CurrentAccount::instance();
  const ContactList *contactList = currentAccount->getContactList();
  connect( contactList, SIGNAL(     contactChangedMsnObject(Contact*) ),
           this,        SLOT  ( slotContactChangedMsnObject(Contact*) ));

  initialized_ = true;
  return true;
}



// Check whether the contact is in any of the existing chat windows.
bool ChatMaster::isContactInChat( const QString &handle )
{
  // Find the chat window where we have an exclusive chat
  return ( getContactChatWindow( handle, false ) != 0 );
}



// Verify if a new chat can be opened and requests a switchboard
void ChatMaster::requestChat( QString handle )
{
  // Look through the chat windows for an exclusive chat with the given contact
  ChatWindow *chatWindow = getContactChatWindow( handle, true );

  // Create a new one if needed
  if( chatWindow == 0 )
  {
#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster::requestChat() - Requesting a connection to " << handle << endl;
#endif

    emit requestSwitchboard( handle, ChatInformation::CONNECTION_CHAT );
  }
  else
  {
    // Or notify about the presence of the existing one.
    raiseChat( chatWindow, true );
  }
}



// A chat window is closing
void ChatMaster::slotChatWindowClosing(QObject *chatWindow)
{
#ifdef KMESSDEBUG_CHATMASTER
  kdDebug() << "ChatMaster::slotChatWindowClosing() - Marking chat window as closing." << endl;
#endif

  // Make sure this chat window don't receive new messages.
  ChatWindow *window = static_cast<ChatWindow*>( chatWindow );
  closingChatWindows_.append( window );

  // The contains check avoids deleting references to old switchboards which may be outdated and don't exist anymore.
  MsnSwitchboardConnection *switchboard = window->getSwitchboardConnection();
  if( switchboard && switchboardConnections_.contains( switchboard ) )
  {
    delete switchboard;
  }
}


// A chat window was destroyed
void ChatMaster::slotChatWindowDestroyed(QObject *chatWindow)
{
#ifdef KMESSDEBUG_CHATMASTER
  kdDebug() << "ChatMaster::slotChatWindowDestroyed() - Removing chat window from list." << endl;
#endif

  // Remove from the list
  ChatWindow *window = static_cast<ChatWindow*>( chatWindow );
  chatWindows_        .remove( window );
  closingChatWindows_ .remove( window );
}



// A new application was created for a contact.
void ChatMaster::slotConnectApplication(Application *application)
{
#ifdef KMESSDEBUG_CHATMASTER
  kdDebug() << "ChatMaster::slotConnectApplication() - Connecting application signals." << endl;
#endif

  connect( application, SIGNAL( applicationMessage(const ChatMessage&) ),
           this,          SLOT( showSpecialMessage(const ChatMessage&) ) );
}



// Append the message to the queue, waiting to be delivered
void ChatMaster::queueMessage( const MimeMessage &message, const QString &handle, bool privateChatRequired )
{
#ifdef KMESSDEBUG_CHATMASTER
  kdDebug() << "ChatMaster::queueMessage() - Adding message for '" << handle << "' to the queue "
            << "(size=" << ( pendingMimeMessages_.count() + 1 ) << ")." << endl;
#endif
  // NOTE: Store these pending in ApplicationList class instead?

  PendingMimeMessage *pending = new PendingMimeMessage;
  pending->handle  = handle;
  pending->message = message;
  pending->privateChatRequired = privateChatRequired;

  // No active connection found.
  // Request a new chat with the contact, keep the message pending.
  pendingMimeMessages_.append( pending );
}



// Raise an existing chat window. Forcing a raise is reasonable only when the user requests to, since it makes the window
// to grab keyboard focus and be raised on top of other windows.
void ChatMaster::raiseChat( ChatWindow *chatWindow, bool force )
{
  if( chatWindow == 0 || ! chatWindows_.contains( chatWindow ) )
  {
#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster::raiseChat() - Invalid raising request received; chat is probably already closed." << endl;
#endif
    return;
  }

  // Making windows raise and get focus interrupts the user... it's better to make them appear minimized,
  // and let them just flash in the taskbar.

  if( chatWindow->hasFocus() )
  {
    return;
  }

  if( force )
  {
    chatWindow->hide();

    if( chatWindow->isMinimized() )
      chatWindow->showNormal();
    else
      chatWindow->show();
  }
  else if( chatWindow->isHidden() )
  {
    chatWindow->showMinimized();
  }

  // Along with notifying about it, also force the window to show and receive focus.
  if( force )
  {
    chatWindow->raise();
    chatWindow->setFocus();
    chatWindow->setActiveWindow();
  }
}



/**
 * @brief Send all pending mime messages for the contact.
 * @param handle      The contact to send messages for.
 * @param connection  The switchboard connection that can be used to send the messages.
 */
00538 void ChatMaster::sendPendingMimeMessages( const QString &handle, MsnSwitchboardConnection *connection )
{
#ifdef KMESSDEBUG_CHATMASTER
  kdDebug() << "ChatMaster: Sending pending messages for contact " << handle << ", if any." << endl;
#endif

  bool isPrivateChat = (connection->getContactsInChat().size() < 2);

  PendingMimeMessage *pendingMimeMessage = pendingMimeMessages_.first();
  while( pendingMimeMessage != 0 )
  {
    // Ignore messages for other contacts
    if( pendingMimeMessage->handle != handle )
    {
      // Get next message
      pendingMimeMessage = pendingMimeMessages_.next();
      continue;
    }

    // Ignore messages that can only be sent on a private chat.
    if( pendingMimeMessage->privateChatRequired && ! isPrivateChat )
    {
#ifdef KMESSDEBUG_CHATMASTER
      kdDebug() << "ChatMaster: Switchboard is not a private chat, keeping message in queue." << endl;
#endif

      // Get next message
      pendingMimeMessage = pendingMimeMessages_.next();
      continue;
    }

    // Check when the connection is busy again
    if( connection->isBusy() )
    {
#ifdef KMESSDEBUG_CHATMASTER
      kdDebug() << "ChatMaster: Switchboard is busy again, keeping remaining messages in the queue." << endl;
#endif
      return;
    }

    // Check whether the contact is still in the chat.
    if( ! connection->isConnected() || ! connection->getContactsInChat().contains( handle ) )
    {
      // getContactsInChat() may be empty, don't re-invite the last contact to deviver the message
#ifdef KMESSDEBUG_CHATMASTER
      kdDebug() << "ChatMaster: Switchboard is not connected or contact left chat, keeping remaining messages in the queue." << endl;
#endif
      return;
    }

    // Send the message
    connection->sendApplicationMessage( pendingMimeMessage->message );

    // Remove from queue
    pendingMimeMessages_.remove( pendingMimeMessage );  // updates current()
    pendingMimeMessage = pendingMimeMessages_.current();
  }
}



// Deliver a chat message to the chat window (can be an Application message or an Offline-IM from MsnNotificationConnection).
void ChatMaster::showSpecialMessage( const ChatMessage &message )
{
#ifdef KMESSDEBUG_CHATMASTER
  kdDebug() << "ChatMaster::showSpecialMessage() - Delivering application/OIM chat message to chat window." << endl;
#endif

  const QString &handle = message.getContactHandle();

#ifdef KMESSDEBUG_CHATMASTER
  kdDebug() << "ChatMaster::showSpecialMessage() - Message info, Contact: " << handle << " - Message body: '" << message.getBody() << "' - "
               "Message type: " << message.getType() << " - Message class: " << message.getContentsClass() << "." << endl;
#endif

  ChatWindow *chatWindow = getContactChatWindow( handle, true );

  // Deliver the message to the contact's chat window
  if( chatWindow != 0 )
  {
#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster::showSpecialMessage() - Chat window exists, delivering message." << endl;
#endif

    // The chat window is currently closing
    if( closingChatWindows_.contains( chatWindow ) )
    {
      if( message.isNormalMessage() )
      {
        // TODO: Sometimes, you close a window when the other contact sends a new message, and you lose it.
        // Maybe it's possible to avoid the loss, for example with a notification balloon to notify of the message.
        kdWarning() << "ChatMaster::showSpecialMessage() - Chat window is closing, suppressing message '" << message.getBody() << "' from '" << handle << "'." << endl;
      }
#ifdef KMESSDEBUG_CHATMASTER
      else
      {
        kdDebug() << "ChatMaster::showSpecialMessage() - Chat window is closing, suppressing message." << endl;
      }
#endif

      return;
    }

    chatWindow->receivedMessage( message );
    return;
  }

  // No window is available - we should check if it's needed to create a new window, or if the message
  // can be safely ignored.
   switch( message.getContentsClass() )
  {
    case ChatMessage::CONTENT_MESSAGE:
    case ChatMessage::CONTENT_NOTIFICATION_NUDGE:
    case ChatMessage::CONTENT_NOTIFICATION_WINK:
    case ChatMessage::CONTENT_SYSTEM_NOTICE:
    case ChatMessage::CONTENT_SYSTEM_ERROR:
    case ChatMessage::CONTENT_APP_INVITE:
#ifdef KMESSDEBUG_CHATMASTER
      kdDebug() << "ChatMaster::showSpecialMessage() - Message is important, delivering it." << endl;
#endif
      break;

    default:
#ifdef KMESSDEBUG_CHATMASTER
      kdDebug() << "ChatMaster::showSpecialMessage() - Message is not important, will be ignored." << endl;
#endif
      return;
  }


  // Check if a switchboard is present
  MsnSwitchboardConnection *switchboard = getContactSwitchboardConnection( handle, true );
  if( switchboard == 0 )
  {
    // Create a new offline switchboard connection to deliver the message. It can be converted to an
    // online switchboard one if needed.
    switchboard = startSwitchboard( ChatInformation( 0, handle, 0, ChatInformation::CONNECTION_OFFLINE ) );
    if( switchboard == 0 )
    {
#ifdef KMESSDEBUG_CHATMASTER
      kdDebug() << "ChatMaster::showSpecialMessage() - Could not create an offline switchboard." << endl;
#endif
      return;
    }
  }

  // Create a new chat window for the message
  chatWindow = createChatWindow( switchboard );
  if( chatWindow == 0 )
  {
#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster::showSpecialMessage() - Could not create a chat window." << endl;
#endif
    return;
  }

  // Deliver the message to the new chat window
  chatWindow->receivedMessage( message );
}



// Display an MSN Object that was received.
void ChatMaster::showMsnObject(const QString &handle, const MsnObject &msnObject, ChatWindow *chatWindow)
{
  QString fileName;
  QPtrListIterator<ChatWindow> it( chatWindows_ );
  QString emoticonCode;

  // Get the contact
  // Note I don't use msnObject.getCreator, because I don't want to trust the other client!
  ContactBase *contact = 0;
  Contact     *msnContact = 0;

  // Get msn object filename
  fileName = PictureTransferP2P::getPictureFileName( msnObject );

  // Handle the picture
  switch( msnObject.getType() )
  {
    case MsnObject::DISPLAYPIC:
      // Get the real MSN contact from the MSN contact list.
      // Picture is stored in the ContactExtension because it's not an official property of the contact (not stored server-side).
      msnContact = CurrentAccount::instance()->getContactList()->getContactByHandle(handle);
      if(KMESS_NULL(msnContact)) return;
      msnContact->getExtension()->setContactPicturePath( fileName );
      pendingDisplayPictures_.remove( handle );
      break;

    case MsnObject::WINK:
      // Show the Wink in the chat window where the original datacast message came from.
      if(KMESS_NULL(chatWindow)) return;
      chatWindow->showWink(handle, fileName, msnObject.getFriendly());
      break;

    case MsnObject::EMOTICON:
      // Inform the contact that a custom emoticon can be parsed.
      contact = CurrentAccount::instance()->getContactByHandle(handle);
      if(KMESS_NULL(contact)) return;
      emoticonCode = contact->getEmoticonCode(msnObject.getDataHash());  // always put before addEmoticonFile()!
      contact->addEmoticonFile( msnObject.getDataHash(), fileName );

      // Update all chat windows where the contact is.
      while( it.current() != 0 )
      {
        chatWindow = it.current();
        if( chatWindow->isContactInChat(handle) )
        {
          chatWindow->updateCustomEmoticon(handle, emoticonCode);
        }
        ++it;
      }
      break;

    case MsnObject::BACKGROUND:
      // handle background transfers

    case MsnObject::VOICECLIP:
      // handle voicelip transfers

    default:
      kdWarning() << "ChatMaster::slotShowMsnObject() - Unable to handle MsnObject pictures of type '" << msnObject.getType() << "'." << endl;
  }
}



// Determine what to do when a contact changed it's picture.
void ChatMaster::slotContactChangedMsnObject( Contact *contact )
{
  QString handle = contact->getHandle();
  MsnObject msnObject( *contact->getMsnObject() );

  // If the contact no longer likes to display an msn object, remove it
  if( contact->getMsnObject() == 0 )
  {
#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster::slotContactChangedMsnObject() - Contact '" << handle << "' removed its MSN object" << endl;
#endif

    contact->getExtension()->setContactPicturePath( QString::null );
    return;
  }

  // See if the file already exists.
  QString objectFileName = PictureTransferP2P::getPictureFileName( msnObject );
  if( QFile::exists( objectFileName ) )
  {
    if( QImage::imageFormat( objectFileName ) != 0
    &&  msnObject.verifyFile( objectFileName ) )
    {
#ifdef KMESSDEBUG_CHATMASTER
      kdDebug() << "ChatMaster::slotContactChangedMsnObject() - Picture for '" << handle << "' is already downloaded." << endl;
#endif

      contact->getExtension()->setContactPicturePath( objectFileName );
      return;
    }
    else
    {
#ifdef KMESSDEBUG_CHATMASTER
    bool corruptQt = QImage::imageFormat( objectFileName ) == 0;
    kdDebug() << "ChatMaster::slotContactChangedMsnObject() - Picture for '" << handle
              << "' is already downloaded, but corrupt "
              << ( corruptQt ? "(detected by QImage)" : "(detected by MsnObject)" )
              << ", deleting it: '" << objectFileName << "'" << endl;
#endif
      QFile::remove( objectFileName );

      // If the file was set as picture. reset right away.
      if( contact->getExtension()->getContactPicturePath() == objectFileName )
      {
#ifdef KMESSDEBUG_CHATMASTER
        kdWarning() << "ChatMaster::slotContactChangedMsnObject() - Corrupt picture was already set, "
                    << "resetting contact picture." << endl;
#endif
        contact->getExtension()->setContactPicturePath( QString::null );
      }
    }
  }

  // If the contact is active in a chat,
  // download the new picture straight away.
  MsnSwitchboardConnection *switchboard = getContactSwitchboardConnection( handle, true );
  if( switchboard != 0 )
  {
#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster::slotContactChangedMsnObject() - Contact '" << handle << "' changed its MSN object." << endl;
#endif

    // Automatically decides which switchboard is the best to use,
    // this may change during the transfer.
    startMsnObjectDownload( handle, &msnObject, 0 );
    return;
  }


  // Queue a request to download it later in the background.
  // That method will check for the prerequisites to download it,
  // since that may change between this point and the timedUpdate() call.

#ifdef KMESSDEBUG_CHATMASTER
  kdDebug() << "ChatMaster::slotContactChangedMsnObject() - Contact '" << handle << "' changed its MSN object, queueing a request for it." << endl;
#endif

  // Request the contact's msnobject
  if( ! pendingDisplayPictures_.contains( handle ) )
  {
    pendingDisplayPictures_.append( handle );
  }
}



// A contact joined to one of our switchboard sessions.
void ChatMaster::slotContactJoinedChat(QString handle)
{
  // Get switchboard connection from slot sender
  MsnSwitchboardConnection *connection = static_cast<MsnSwitchboardConnection*>( const_cast<QObject*>( sender() ) );
  if(KMESS_NULL(connection)) return;

  // Send any pending messages, if any
  if( pendingMimeMessages_.count() > 0 )
  {
    // Find the chat window where the contact is the only participant.
    if( connection->getContactsInChat().size() > 1 )
    {
      // Contact joined chat conversation with multiple partipipants, ignore this.
#ifdef KMESSDEBUG_CHATMASTER
      kdDebug() << "Contact '" << handle << "' joined multi-chat, not sending pending messages." << endl;
#endif
    }
    else
    {
#ifdef KMESSDEBUG_CHATMASTER
      kdDebug() << "ChatMaster::slotContactJoinedChat() - Contact '" << handle << "' joined chat, checking for pending messages." << endl;
#endif

      // Send the messages
      if( pendingMimeMessages_.count() > 0 )
      {
        sendPendingMimeMessages( handle, connection );
      }
    }
  }


  // Check whether the contact picture is outdated.
  // Use getContactByHandle() from the MSN contact list to get the msn object.
  const Contact *contact = CurrentAccount::instance()->getContactList()->getContactByHandle( handle );
  if( contact != 0 && contact->hasP2PSupport() )
  {
    if( contact->getMsnObject() != 0 )
    {
      startMsnObjectDownload( contact->getHandle(), contact->getMsnObject(), 0 );
    }
  }
}



/**
 * @brief Deliver an application command to the correct application.
 */
00902 void ChatMaster::slotDeliverAppCommand(QString cookie, QString handle, QString command)
{
#ifdef KMESSDEBUG_CHATMASTER
  kdDebug() << "ChatMaster::slotDeliverAppCommand() - Delivering application command." << endl;
#endif

  Application *app = 0;

  // Get the contact.
  ContactBase *contact = CurrentAccount::instance()->getContactByHandle(handle);
  if(KMESS_NULL(contact)) return;

  // Get the application list
  if( contact->hasApplicationList() )
  {
    app = contact->getApplicationList()->getApplicationByCookie(cookie);
  }

  // App not found.
  if( app == 0 )
  {
    kdWarning() << "ChatMaster::slotDeliverAppCommand() - Application not found to deliver user command (contact=" << contact << ", command=" << command << ")." << endl;
    return;
  }

  // Deliver the 'accept' or 'cancel' command.
  app->gotCommand( command );
}



/**
 * @brief Deliver a message from an Application to the switchboard connection.
 * @param message The Mime message to deliver. The message can contain a normal MIME or binary P2P payload.
 * @param handle  The contact the message should be delivered to.
 * @param privateChatRequired  Whether a private chat is required to deliver the message. If not, a multi-chat will be used when available.
 *
 * This method automatically determines which switchboard is should use to deliver the message.
 * When the contact is not present in any of the existing switchboard connections, a new connection request will be made.
 * The message will be queued for delivery to the switchboard so it can be delivered once it's available.
 * Conversations with multiple contacts are avoided because of the networking overhead;
 * since the switchboard is a broadcast channel, each participant receives the message.
 *
 * Unlike the direct connection link, the switchboard is only capable of transferring mime messages.
 * To transfer the P2P data, the P2PApplication has to wrap the message as MimeMessage payload.
 */
00948 void ChatMaster::slotDeliverMimeMessage(const MimeMessage &message, const QString &handle, bool privateChatRequired)
{
#ifdef KMESSDEBUG_CHATMASTER
  kdDebug() << "ChatMaster::slotDeliverMimeMessage() - Delivering application message for '" << handle << "' to switchboard." << endl;
#endif
#ifdef KMESSTEST
  QString contentType = message.getValue("Content-Type");
  ASSERT( contentType == "application/x-msnmsgrp2p"
       || contentType == "text/x-msmsgsinvite"
       || contentType.section(';', 0, 0) == "text/x-msmsgsinvite" );  // for ; charset= suffix
  if( contentType == "application/x-msnmsgrp2p" )
  {
    ASSERT( message.getValue("P2P-Dest") == handle );
  }
#endif

  MsnSwitchboardConnection *connection = getContactSwitchboardConnection(handle, privateChatRequired);

  if( connection != 0 )
  {
    if( connection->isBusy() )
    {
      // Connection has unacked messages, keep waiting until the switchboard is ready to send
#ifdef KMESSDEBUG_CHATMASTER
      kdDebug() << "ChatMaster::slotDeliverMimeMessage() - Switchboard is currently busy, queueing message until the switchboard is ready to send." << endl;
#endif
      queueMessage( message, handle, privateChatRequired );

      // Pause application so it won't continue to send much more messages (see P2PApplication::slotSendData()).
      ApplicationList *appList = getApplicationList(handle);
      if( appList != 0 )
      {
        appList->pauseApplications();
      }
    }
    else
    {
      // Deliver the message
      connection->sendApplicationMessage(message);
    }
  }
  else
  {
#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster::slotDeliverMimeMessage() - No switchboard available to deliver message,"
              << " requesting chat and queueing message " << (pendingMimeMessages_.count() + 1) << "." << endl;
#endif

    emit requestSwitchboard( handle, ChatInformation::CONNECTION_BACKGROUND );
    queueMessage( message, handle, privateChatRequired );
  }
}



// The switchboard received a Mime message
void ChatMaster::slotGotMessage(const MimeMessage &message, const QString &handle)
{
  ApplicationList *appList = getApplicationList(handle);
  if( appList != 0 )
  {
    appList->gotMessage(message);
  }
}



// The switchboard received a P2P message
void ChatMaster::slotGotMessage(const P2PMessage &message, const QString &handle)
{
  ApplicationList *appList = getApplicationList(handle);
  if( appList != 0 )
  {
    appList->gotMessage(message);
  }
}



// The switchboard received an msn object
void ChatMaster::slotGotMsnObject(const QString &msnObjectData, const QString &handle)
{
  // Get the contact.
  ContactBase *contact = CurrentAccount::instance()->getContactByHandle(handle);
  if(KMESS_NULL(contact)) return;

  // Get the switchboard connection where the emoticon/wink/voiceclip was posted
  const MsnSwitchboardConnection *connection = static_cast<const MsnSwitchboardConnection*>( sender() );
  ChatWindow *chatWindow = 0;
  if( connection != 0 )
  {
    chatWindow = getChatWindowBySwitchboard(connection);
  }

  // Parse the msn object
  MsnObject msnObject(msnObjectData);

  // Get the contact name,
  QString friendlyName = CurrentAccount::instance()->getContactFriendlyNameByHandle(handle);

  // Determine the statusmessage to display.
  QString statusMessage;
  MsnObject::MsnObjectType objectType = msnObject.getType();
  if( objectType == MsnObject::WINK )
  {
    statusMessage = i18n("%1 is sending a wink: %2").arg(friendlyName).arg(msnObject.getFriendly());
  }

  // Show the status message
  if( ! statusMessage.isEmpty() )
  {
    if( ! KMESS_NULL(connection) && ! KMESS_NULL(chatWindow) )
    {
      chatWindow->showStatusMessage( statusMessage );
    }
  }

  startMsnObjectDownload( handle, &msnObject, chatWindow );
}



// A msn object (picture, wink, emoticon) was received for the contact.
void ChatMaster::slotMsnObjectReceived(const QString &handle, const MsnObject &msnObject)
{
#ifdef KMESSDEBUG_CHATMASTER
  kdDebug() << "ChatMaster::slotMsnObjectReceived() - Received msn object from: " << handle << "." << endl;
#endif

  ChatWindow *chatWindow = 0;

  // Get sender of this signal, slotGotMsnObject() assigned this to the p2papp.
  const P2PApplication *application = static_cast<const P2PApplication*>( sender() );
  if( application != 0 )
  {
    chatWindow = application->getChatWindow();
    if( chatWindow != 0 && ! chatWindows_.contains(chatWindow) )
    {
#ifdef KMESSDEBUG_CHATMASTER
      kdDebug() << "ChatMaster::slotMsnObjectReceived() - Original chat window not found for received MSNObject "
                << "(objecttype=" << msnObject.getType()
                << " contact="    << handle << ")." << endl;
#endif
      chatWindow = getContactChatWindow(handle, true);
    }
  }

  showMsnObject(handle, msnObject, chatWindow);
}



// The switchboard is ready to send more messages.
void ChatMaster::slotSwitchboardReady()
{
  // No need to send pending messages or resume an application (a message is always queued before the app is paused).
  if( pendingMimeMessages_.isEmpty() )
  {
#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster::slotSwitchboardReady() - A switchboard is ready to send more messages, no messages pending." << endl;
#endif
    return;
  }

  // Get the connection
  MsnSwitchboardConnection *connection = static_cast<MsnSwitchboardConnection*>( const_cast<QObject*>( sender() ) );
  if(KMESS_NULL(connection)) return;

  // Get the contacts
  const QStringList &contacts = connection->getContactsInChat();
  bool isPrivateChat          = (contacts.size() < 2);

#ifdef KMESSDEBUG_CHATMASTER
  kdDebug() << "ChatMaster::slotSwitchboardReady() - A switchboard is ready to send more messages, " << pendingMimeMessages_.count() << " messages pending." << endl;
#endif

  // Send all pending messages
  sendPendingMimeMessages( contacts[0], connection );

  // See if the switchboard is still ready.
  // Don't use connection->isBusy() as it will break with
  // the test above when exactly all messages are sent before the connection became busy.
  if( ! pendingMimeMessages_.isEmpty() )
  {
#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster::slotSwitchboardReady() - Switchboard is busy again, not notifying applications." << endl;
#endif
    return;
  }

  // A switchboard is still available.
  // If the contact applications were paused, resume them now.
  ContactBase *contact = CurrentAccount::instance()->getContactByHandle( contacts[0] );
  if( contact != 0 && contact->hasApplicationList() )
  {
    contact->getApplicationList()->resumeApplications(isPrivateChat);
  }
}



// Delete an existing switchboard
void ChatMaster::slotSwitchboardDelete( MsnSwitchboardConnection *closing )
{
  switchboardConnections_.remove( closing );

  for( ChatWindow *chatWindow = chatWindows_.first(); chatWindow; chatWindow = chatWindows_.next() )
  {
    if( chatWindow->getSwitchboardConnection() == closing )
    {
      chatWindow->setSwitchboardConnection( 0 );
    }
  }

  disconnect( closing, 0, this, 0 );

  closing->deleteLater();
}



// Configure and start the Mime application object.
void ChatMaster::startApplication( MimeApplication *application )
{
  // Get the application list.
  ApplicationList *appList = getApplicationList( application->getContactHandle() );
  if(KMESS_NULL(appList)) return;

  // Add to list
  appList->addApplication( application );   // requires MimeApplication/P2PApplication type.

  // Initialize appliation
  slotConnectApplication( application );
  application->start();
}



// Configure and start the P2P application object.
void ChatMaster::startApplication( P2PApplication *application )
{
  // Get the application list.
  ApplicationList *appList = getApplicationList( application->getContactHandle() );
  if(KMESS_NULL(appList)) return;

  // Add to list.
  appList->addApplication( application );   // requires MimeApplication/P2PApplication type.

  // Initialize application
  slotConnectApplication( application );
  application->start();
}



// Start a connection with the information gathered from the Notification connection
MsnSwitchboardConnection *ChatMaster::startSwitchboard( const ChatInformation &chatInfo )
{
  MsnSwitchboardConnection *switchboard = getContactSwitchboardConnection( chatInfo.getContactHandle(), true );
  ChatWindow *chatWindow = getChatWindowBySwitchboard( switchboard );

  if( switchboard != 0 && chatInfo.getType() != ChatInformation::CONNECTION_REFRESH )
  {
#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster::startSwitchboard() - Deleting old switchboard connection." << endl;
#endif
    slotSwitchboardDelete( switchboard );
    switchboard = 0;
  }

  if( switchboard == 0 )
  {
#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster::startSwitchboard() - Creating new switchboard connection." << endl;
#endif
    // Initialize the switchboard connection
    switchboard = new MsnSwitchboardConnection();
    if( ! switchboard->initialize() )
    {
      kdWarning() << "ChatMaster::startSwitchboard() - Couldn't initialize switchboard connection." << endl;
      delete switchboard;
      return 0;
    }

    switchboardConnections_.append( switchboard );

    // Connect the switchboard's signals to the Chat Master.
    connect(switchboard, SIGNAL(            contactJoinedChat(QString, QString)                   ),
            this,        SLOT  (        slotContactJoinedChat(QString)                            ));
    connect(switchboard, SIGNAL(                   gotMessage(const MimeMessage&, const QString&) ),
            this,        SLOT  (               slotGotMessage(const MimeMessage&, const QString&) ));
    connect(switchboard, SIGNAL(                   gotMessage(const P2PMessage&,  const QString&) ),
            this,        SLOT  (               slotGotMessage(const P2PMessage&,  const QString&) ));
    connect(switchboard, SIGNAL(                 gotMsnObject(const QString&,     const QString&) ),
            this,        SLOT  (             slotGotMsnObject(const QString&,     const QString&) ));
    connect(switchboard, SIGNAL(                    readySend()                                   ),
            this,        SLOT  (         slotSwitchboardReady()                                   ));
    connect(switchboard, SIGNAL(        requestNewSwitchboard(QString)                            ),
            this,        SLOT  ( forwardRequestNewSwitchboard(QString)                            ));
    connect(switchboard, SIGNAL(            requestChatWindow(MsnSwitchboardConnection*)          ),
            this,        SLOT  (             createChatWindow(MsnSwitchboardConnection*)          ));
    connect(switchboard, SIGNAL(                     deleteMe(MsnSwitchboardConnection*)          ),
            this,        SLOT  (        slotSwitchboardDelete(MsnSwitchboardConnection*)          ));
  }

  switchboard->connectTo( chatInfo );

  if( chatWindow != 0 )
  {
    chatWindow->setSwitchboardConnection( switchboard );
  }

  // If this switchboard is a background one, made to request a contact's picture,
  // we can remove the pending request from the list now.
  if( chatInfo.getType() == ChatInformation::CONNECTION_BACKGROUND
  &&  pendingDisplayPictures_.contains( chatInfo.getContactHandle() ) )
  {
    pendingDisplayPictures_.remove( chatInfo.getContactHandle() );
  }

  return switchboard;
}



// Start a chat with the information gathered from a switchboard connection
ChatWindow *ChatMaster::createChatWindow( MsnSwitchboardConnection *switchboard )
{
  ChatWindow *chatWindow;

  // If the new chat is a group chat, find existing group chat windows; else find private chats
  QStringList participants = switchboard->getContactsInChat();
  if( participants.count() == 1 )
  {
    chatWindow = getContactChatWindow( switchboard->getFirstContact(), true );
  }
  else
  {
    chatWindow = getContactsChatWindow( participants );
  }

  if( chatWindow != 0 )
  {
#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster::createChatWindow() - Chatwindow with "
              << switchboard->getContactsInChat().join(",") << " already exists, raising it." << endl;
#endif
  }
  else
  {
    // Create object
    chatWindow = new ChatWindow( kapp->mainWidget(), "ChatWindow" );

    // Connect signals
    connect( chatWindow, SIGNAL(                  closing(QObject*)                        ),
             this,       SLOT  (    slotChatWindowClosing(QObject*)                        ));
    connect( chatWindow, SIGNAL(                destroyed(QObject*)                        ),
             this,       SLOT  (  slotChatWindowDestroyed(QObject*)                        ));
    connect( chatWindow, SIGNAL(           newChatMessage(const ChatMessage&, ChatWindow*) ),
             this,       SLOT  (    forwardNewChatMessage(const ChatMessage&, ChatWindow*) ));
    connect( chatWindow, SIGNAL(        setContactAllowed( QString )                       ),
             this,       SLOT  ( forwardSetContactAllowed( QString )                       ) );
    connect( chatWindow, SIGNAL(          setContactAdded( QString, bool )                 ),
             this,       SLOT  (   forwardSetContactAdded( QString, bool )                 ) );
    connect( chatWindow, SIGNAL(        setContactBlocked( QString, bool )                 ),
             this,       SLOT  ( forwardSetContactBlocked( QString, bool )                 ));
    connect( chatWindow, SIGNAL(               appCommand(QString, QString, QString)       ),
             this,       SLOT  (    slotDeliverAppCommand(QString, QString, QString)       ));

    // Connect request signals
    connect( chatWindow, SIGNAL( requestFileTransfer(const QString&, const QString&) ),
             this,       SLOT  (   startFileTransfer(const QString&, const QString&) ));
    connect( chatWindow, SIGNAL(   requestNetMeeting(const QString&)                 ),
             this,       SLOT  (     startNetMeeting(const QString&)                 ));

    // Bail out if the window could not be initialized
    if( ! chatWindow->initialize( switchboard->getFirstContact() ) )
    {
      kdWarning() << "ChatMaster::createChatWindow() - Couldn't initialize the new chat window!" << endl;
      return 0;
    }

    chatWindows_.append( chatWindow );
  }

  // Finally, notify the chat window about the new chat
  chatWindow->setSwitchboardConnection( switchboard );
  chatWindow->startChat();

  // If we've requested a chat window, raise it forcing it open over other KMess' windows;
  // if some contact wants to chat with us, the chat window will open minimized.
  raiseChat( chatWindow, switchboard->getUserStartedChat() );

  return chatWindow;
}



// Start a file transfer with the information from the ChatWindow
void ChatMaster::startFileTransfer( const QString &handle, const QString &filename )
{
  ApplicationList *appList = getApplicationList(handle);
  if(KMESS_NULL(appList)) return;

  // Get the contact properties, see how we can transfer the file.
  const ContactBase *contact = CurrentAccount::instance()->getContactByHandle( handle );
  if( contact != 0 && contact->hasP2PSupport() )
  {
    // The contact supports file transfer over MSNP2P
    P2PApplication *app = new FileTransferP2P(appList, filename);
    startApplication(app);
  }
  else
  {
    // The contact only supports file transfer the old way
    MimeApplication *app = new FileTransfer(handle, filename);
    startApplication(app);
  }
}



// Start a netmeeting invitation
void ChatMaster::startNetMeeting(const QString &handle)
{
  // Get the application list
  ApplicationList *appList = getApplicationList(handle);
  if(KMESS_NULL(appList)) return;

  // Get the contact
  const ContactBase *contact = CurrentAccount::instance()->getContactByHandle( handle );
  if(KMESS_NULL(contact)) return;

  // Create the application
  MimeApplication *app = new GnomeMeeting(handle);
  startApplication(app);
}



// Start a picture transfer to get the contact's msn object.
void ChatMaster::startMsnObjectDownload( const QString &handle, const MsnObject *msnObject, ChatWindow *chatWindow )
{
  // Test input
  if(KMESS_NULL(msnObject)) return;

  // Get the application list
  ApplicationList *appList = getApplicationList(handle);
  if(KMESS_NULL(appList))   return;

  // First check if the actual object is currently being downloaded already.
  // call before cache check, since file can be partially downloaded.
  if( appList->hasMsnObjectTransfer(*msnObject) )
  {
#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster::startMsnObjectDownload() - object is already being downloaded, not sending a second invite." << endl;
#endif
    return;
  }

  // Get the picture filename, perhaps from a cache
  QString objectFileName = PictureTransferP2P::getPictureFileName( *msnObject );
  bool fileExists = QFile::exists( objectFileName );

  // Check if the image can be read.
  if( fileExists
  &&  ( QImage::imageFormat( objectFileName ) == 0 || ! msnObject->verifyFile( objectFileName ) ) )
  {
#ifdef KMESSDEBUG_CHATMASTER
    bool corruptQt = QImage::imageFormat( objectFileName ) == 0;
    kdDebug() << "ChatMaster::startMsnObjectDownload() - Cached MsnObject is corrupt "
              << ( corruptQt ? "(detected by QImage)" : "(detected by MsnObject)" )
              << ", deleting it: '" << objectFileName << "'" << endl;
#endif
    QFile::remove( objectFileName );
    fileExists = false;
  }

  // Avoid downloading again if it exists
  if( fileExists )
  {
#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster::startMsnObjectDownload() - Contact MsnObject is already in cache." << endl;
#endif

    // Already have it, handle processing in a generic way.
    // Don't use slotMsnObjectReceived() because it uses sender() to get the ChatWindow object.
    showMsnObject(handle, msnObject->objectString(), chatWindow);
  }
  else
  {
#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster::startMsnObjectDownload() - Starting MsnObject download (type=" << msnObject->getType() << ")." << endl;
#endif

    // Create and initialize the application.
    P2PApplication *app = new PictureTransferP2P(appList, *msnObject);
    app->setChatWindow( chatWindow );   // for winks, to display in originating chat window.
    connect(app,  SIGNAL(       pictureReceived(const QString&, const MsnObject&)  ),
            this, SLOT  ( slotMsnObjectReceived(const QString&, const MsnObject&)  ));
    startApplication(app);
  }
}



/**
 * Periodically called method for update commands.
 *
 * This method is synchronized with the ping timer of MsnNotificationConnection
 * to avoid multiple timers which reduces power consumption (see www.linuxpowertop.org).
 *
 * Currently this is used to download display pictures in the background,
 * but it can also be used for other events.
 */
01463 void ChatMaster::timedUpdate()
{
  // Handle the queue of display pictures to download, but only if we're not hidden
  if( pendingDisplayPictures_.count() == 0 || CurrentAccount::instance()->getStatus() == "HDN" )
  {
    return;
  }

#ifdef KMESSDEBUG_CHATMASTER
  kdDebug() << "ChatMaster::timedUpdate() - handling timed events." << endl;
#endif

  // See if there are pictures to download.
  // Get 4 pictures at once.
  int requestCount = pendingDisplayPictures_.count();
  requestCount = requestCount > 4 ? 4 : requestCount;
  while( requestCount > 0 && ! pendingDisplayPictures_.isEmpty() )
  {
    // Unshift the first handle of the list.
    QString handle = pendingDisplayPictures_.first();
    pendingDisplayPictures_.remove( handle );

#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster::timedUpdate() - see if the display picture of '" << handle << "' should  be downloaded." << endl;
#endif

    // See if the picture was already downloaded somehow (e.g. user opened a chat)

    // First get the contact.
    // Using getContactList()->.. because it skips the InvitedContact objectss returned by CurrentAccount::getContactByHandle()
    Contact *contact = CurrentAccount::instance()->getContactList()->getContactByHandle( handle );
    if( contact == 0 )
    {
#ifdef KMESSDEBUG_CHATMASTER
      kdDebug() << "ChatMaster::timedUpdate() - contact not found, was it removed?" << endl;
#endif
      continue;
    }

    // See if the contact is still online.
    if( contact->isOffline() )
    {
#ifdef KMESSDEBUG_CHATMASTER
      kdDebug() << "ChatMaster::timedUpdate() - contact is no longer online." << endl;
#endif
      continue;
    }

    // See if the contact is still blocked.
    // Avoid starting a chat which "invites" the contact to chat back.
    if( contact->isBlocked() )
    {
#ifdef KMESSDEBUG_CHATMASTER
      kdDebug() << "ChatMaster::timedUpdate() - contact is blocked." << endl;
#endif
      continue;
    }

    // Get the msn object, can become null now.
    const MsnObject *msnObject = contact->getMsnObject();
    if( msnObject == 0 )
    {
#ifdef KMESSDEBUG_CHATMASTER
      kdDebug() << "ChatMaster::timedUpdate() - MsnObject has been reset." << endl;
#endif
      continue;
    }

    // See if the object already exists in the cache
    QString objectFileName = PictureTransferP2P::getPictureFileName( *msnObject );
    if( QFile::exists( objectFileName )
    &&  QImage::imageFormat( objectFileName ) != 0
    &&  msnObject->verifyFile( objectFileName ) )
    {
#ifdef KMESSDEBUG_CHATMASTER
      kdDebug() << "ChatMaster::timedUpdate() - Picture is already downloaded." << endl;
#endif
      continue;
    }


    // See if the actual object is currently being downloaded (e.g. user just opened a chat).
    // Put as last check because it creates the ApplicationList on demand.


#ifdef KMESSDEBUG_CHATMASTER
    // Made the debug output easier to understand.
    if( ! contact->hasApplicationList() )
    {
      kdDebug() << "ChatMaster::timedUpdate() - Picture is not available, creating application list." << endl;
    }
#endif

    // Get the application list
    ApplicationList *appList = getApplicationList( handle );
    if( KMESS_NULL(appList) ) continue;

    // See if a picture is currently being downloaded.
    if( appList->hasMsnObjectTransfer( *msnObject ) )
    {
#ifdef KMESSDEBUG_CHATMASTER
      kdDebug() << "ChatMaster::timedUpdate() - transfer for the picture is already active." << endl;
#endif
      continue;
    }


    // All tests passed.
    // Download the picture.

#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster::timedUpdate() - Picture is not available, starting switchboard connection to download it." << endl;
#endif

    // Start a chat to download the picture.
    // The download will be initiated up by ChatMaster::slotContactJoinedChat().
    emit requestSwitchboard( handle, ChatInformation::CONNECTION_BACKGROUND );
    requestCount--;
  }
}


#include "chatmaster.moc"

Generated by  Doxygen 1.6.0   Back to index