Logo Search packages:      
Sourcecode: kmess version File versions

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 "../kmess.h"
#include "../kmessdebug.h"
#include "../msnobject.h"

#include "../contact/contact.h"
#include "../contact/contactlist.h"
#include "../network/chatinformation.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 <klocale.h>

#include <qfile.h>


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



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

#ifdef KMESSDEBUG_CHATMASTER
  kdDebug() << "DESTROYED ChatMaster " << endl;
#endif
}



// Create and initialize a new chat window.
ChatWindow * ChatMaster::createChatWindow()
{
  // Create object
  ChatWindow *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(                  newChat(const ContactBase*, QString, ChatWindow*) ),
           this,       SLOT  (           forwardNewChat(const ContactBase*, QString, ChatWindow*) ));
  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() )
  {
    kdWarning() << "ChatMaster: Couldn't initialize the new chat window!" << endl;
    return 0;
  }

  // Get the switchboard connection, attach more signals
  // We need to know when the contact is available in one of the chat conversations.
  MsnSwitchboardConnection *connection = chatWindow->getSwitchboardConnection();
  if(KMESS_NULL(connection)) return 0;

  connect(connection, SIGNAL(     contactJoinedChat(QString, QString)                   ),
          this,         SLOT( slotContactJoinedChat(QString)                            ));
  connect(connection, SIGNAL(            gotMessage(const MimeMessage&, const QString&) ),
          this,         SLOT(        slotGotMessage(const MimeMessage&, const QString&) ));
  connect(connection, SIGNAL(            gotMessage(const P2PMessage&,  const QString&) ),
          this,         SLOT(        slotGotMessage(const P2PMessage&,  const QString&) ));
  connect(connection, SIGNAL(          gotMsnObject(const QString&,     const QString&) ),
          this,         SLOT(      slotGotMsnObject(const QString&,     const QString&) ));
  connect(connection, SIGNAL(             readySend()                                   ),
          this,         SLOT(  slotSwitchboardReady()                                   ));
  connect(connection, SIGNAL(           requestChat(QString)                            ),
          this,         SLOT(    forwardRequestChat(QString)                            ));

  // append to list and return.
  chatWindows_.append( chatWindow );
  return chatWindow;
}



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

    // Go through all the chat windows
    for ( ChatWindow *chatWindow = chatWindows_.first(); chatWindow; chatWindow = chatWindows_.next() )
    {
      // Close+delete the chat window
      // (note that the window also has the WDestructiveClose flag set)
      chatWindow->close(true);
    }

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



// Forward a new chat signal from a chat window
void ChatMaster::forwardNewChat(const ContactBase *contact, QString message, ChatWindow *chatWindow)
{
#ifdef KMESSTEST
  ASSERT( chatWindow != 0 );
#endif
#ifdef KMESSDEBUG_CHATMASTER
  kdDebug() << "ChatMaster: Forwarding the new chat signal." << endl;
#endif
  emit newChat( contact, message, chatWindow );
}



// Forward a request chat signal from a chat window
void ChatMaster::forwardRequestChat(QString handle)
{
#ifdef KMESSDEBUG_CHATMASTER
  kdDebug() << "ChatMaster: Forwarding the request chat signal." << endl;
#endif

  emit requestChat( 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: 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 connection where we're having an conversation with the given contact.
MsnSwitchboardConnection * ChatMaster::getContactSwitchboardConnection(const QString &handle, bool privateChat)
{
  ContactBase *contact = CurrentAccount::instance()->getContactByHandle(handle);
  if(KMESS_NULL(contact)) return 0;

  // 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 )
  {
    MsnSwitchboardConnection *connection = it.current();
    if( connection->isExclusiveChatWithContact(handle) )
    {
      return connection;  // best candidate found
    }
    else if( ! privateChat && connection->isContactInChat(handle) )
    {
      result = connection;  // keep as option.
    }

    ++it;
  }

  return result;
}



// Initialize the class
bool ChatMaster::initialize()
{
  if ( initialized_ )
  {
    kdDebug() << "ChatMaster 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 );
}



// 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.
  closingChatWindows_.append( static_cast<ChatWindow*>(chatWindow) );
}



// 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
  chatWindows_        .remove( static_cast<ChatWindow*>(chatWindow) );
  closingChatWindows_ .remove( static_cast<ChatWindow*>(chatWindow) );
}



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

  connect( application,  SIGNAL(               appMessage(QString)       ),    // Informative message
           this,           SLOT(       slotShowAppMessage(QString)       ) );
  connect( application,  SIGNAL(          appEventMessage(QString)       ),    // Activity message
           this,           SLOT(  slotShowAppEventMessage(QString)       ) );
  connect( application,  SIGNAL(          fileTransferred(QString)       ),    // File transferred
           this,           SLOT(  slotShowFileTransferred(QString)       ) );
  connect( application,  SIGNAL(            systemMessage(QString)       ),    // Error message
           this,           SLOT(    slotShowSystemMessage(QString)       ) );
}



// 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: Adding message for '" << handle << "' to the queue." << 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 );
}



/**
 * @brief Send all pending chat messages for the contact.
 * @param handle      The contact to send messages for.
 * @param chatWindow  The chat window to deliver the messages to.
 */
00381 void ChatMaster::sendPendingChatMessages( const QString &handle, ChatWindow *chatWindow )
{
  QPair<QString,ChatMessage> *pendingChatMessage;

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

    // Send the message
    chatWindow->receivedMessage( pendingChatMessage->second );

    // Remove from queue
    pendingChatMessages_.remove( pendingChatMessage );  // updates current()
    kdDebug() << "sent message, old=" << pendingChatMessage << " new=" << pendingChatMessages_.current() << endl;
    pendingChatMessage = pendingChatMessages_.current();
  }
}



/**
 * @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.
 */
00413 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 submitted by an Application or Offline-IM from MsnNotificationConnection).
void ChatMaster::showChatMessage(const ChatMessage &message)
{
#ifdef KMESSDEBUG_CHATMASTER
  kdDebug() << "ChatMaster::showChatMessage: Delivering application chat message to chat window." << endl;
#endif
  const QString &handle = message.getContactHandle();

  // Deliver to the chat window is it's found,
  ChatWindow *chatWindow = getContactChatWindow(handle, true);
  if( chatWindow == 0 )
  {
#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster: No chat window available to display message, creating new chat window." << endl;
#endif

    // Since this method is only called for Offline-IM messages or notification/application messages,
    // it's not helping to request a switchboard connection first.
    chatWindow = createChatWindow();
    if( chatWindow == 0 )
    {
      return;   // failed to initialize.
    }

    // Pretend the contact joined already and display the message
    chatWindow->startOfflineChat( handle );
    chatWindow->receivedMessage( message );
  }
  else
  {
    if( closingChatWindows_.contains(chatWindow) )
    {
#ifdef KMESSDEBUG_CHATMASTER
      kdDebug() << "ChatMaster: Chat window is closing, suppressing message '" << message.getBody() << "'." << endl;
#endif
      if( message.isNormalMessage() )
      {
        // Don't expect this, but you'd never know...
        kdWarning() << "Chat window is closing, suppressing message '" << message.getBody() << "' from '" << handle << "'." << endl;
      }
      return;
    }

    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 );
      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 )
{
#ifdef KMESSDEBUG_CHATMASTER
  kdDebug() << "ChatMaster: Contact changed MSN object, checking if contact is present in a chat session." << endl;
#endif

  // If the contact no longer likes to display an msn object, ignore the event.
  if( contact->getMsnObject() == 0 )
  {
    // NOTE: remove the current image from the contact as well?
    return;
  }

  // TODO: as additional option, also start the picture transfer if contact is not in chat (and create a new switchboard for it).
  //       the chat needs to be started in the background, slotContactJoinedChat() does the rest.
  if( isContactInChat(contact->getHandle()) )
  {
    // Automatically desides which switchboard is the best to use,
    // this may change during the transfer.
    startMsnObjectDownload( contact->getHandle(), contact->getMsnObject(), 0 );
  }
}



// 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;

  // Get chat window
  ChatWindow *chatWindow = getChatWindowBySwitchboard( connection );
  if(KMESS_NULL(connection)) return;

  // Send any pending messages, if any
  if( pendingChatMessages_.count() > 0 || 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() << "ChatMaster: Contact '" << handle << "' joined multi-chat, not sending pending messages." << endl;
#endif
    }
    else
    {
#ifdef KMESSDEBUG_CHATMASTER
      kdDebug() << "ChatMaster: Contact '" << handle << "' joined chat, checking for pending messages." << endl;
#endif

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

      // Display all pending chat messages for the contact.
      if( pendingChatMessages_.count() > 0 )
      {
        sendPendingChatMessages( handle, chatWindow );
      }
    }
  }


  // 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.
 */
00673 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: 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.
 */
00719 void ChatMaster::slotDeliverMimeMessage(const MimeMessage &message, const QString &handle, bool privateChatRequired)
{
#ifdef KMESSDEBUG_CHATMASTER
  kdDebug() << "ChatMaster::slotDeliverMimeMessage: Delivering application message 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: 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: No switchboard available to deliver message,"
              << " requesting chat and queueing message " << (pendingMimeMessages_.count() + 1) << "." << endl;
#endif

    queueMessage( message, handle, privateChatRequired );
    emit requestChat(handle);
  }
}



// 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: 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) )
    {
      kdWarning() << "ChatMaster::slotMsnObjectReceived: Original chat window not found for received MSNObject "
          << "(objecttype=" << msnObject.getType()
          << " contact="    << handle << ")." << endl;
      chatWindow = getContactChatWindow(handle, true);
    }
  }

  showMsnObject(handle, msnObject, chatWindow);
}



// The application wants to show an application message.
void ChatMaster::slotShowAppMessage(QString message)
{
  // Get object which sent the message
  if(KMESS_NULL(sender())) return;
  Application *application = static_cast<Application*>( const_cast<QObject*>( sender() ) );

  showChatMessage( ChatMessage(ChatMessage::TYPE_APPLICATION, message, application->getContactHandle()) );
}



// The application wants to show an application event message.
void ChatMaster::slotShowAppEventMessage(QString message)
{
  // Get object which sent the message
  if(KMESS_NULL(sender())) return;
  Application *application = static_cast<Application*>( const_cast<QObject*>( sender() ) );

  showChatMessage( ChatMessage(ChatMessage::TYPE_NOTIFICATION, message, application->getContactHandle()) );
}



// The application indicated a file was transferred
void ChatMaster::slotShowFileTransferred(QString filename)
{
  // Get object which sent the message
  if(KMESS_NULL(sender())) return;
  Application *application = static_cast<Application*>( const_cast<QObject*>( sender() ) );

  // Get filename
  QString shortName = filename.right( filename.length() - filename.findRev("/") - 1 );
  QString fileURL   = "<a href=\"file:" + filename + "\">" + shortName + "</a>";

  showChatMessage( ChatMessage(ChatMessage::TYPE_APPLICATION, i18n("Successfully transferred file: %1")
                  .arg("<span class=\"filename completedFilename\">" + fileURL + "</span>"), application->getContactHandle()) );
}



// The application wants to show an system message
void ChatMaster::slotShowSystemMessage(QString message)
{
  // Get object which sent the message
  if(KMESS_NULL(sender())) return;
  Application *application = static_cast<Application*>( const_cast<QObject*>( sender() ) );

  showChatMessage( ChatMessage(ChatMessage::TYPE_SYSTEM, message, application->getContactHandle()) );
}



// 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( pendingChatMessages_.count() == 0 && pendingMimeMessages_.count() == 0 )
  {
#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster: 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: A switchboard is ready to send more messages, " << pendingMimeMessages_.count() << " messages pending." << endl;
#endif

  // A switchboard is 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);
  }

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



// 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 the chat with the information gathered from the Notification connection
void ChatMaster::startChat( const ChatInformation &chatInfo )
{
  // Look through the chat windows for an exclusive chat with the contact given
  ChatWindow *chatWindow = getContactChatWindow(chatInfo.getContactHandle(), true);

  // Create a new chat window if it doesn't exist yet
  if( chatWindow == 0 )
  {
#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster::startChat: Creating a new chat window"
              << " for chat with " << chatInfo.getContactHandle() << endl;
#endif

    chatWindow = createChatWindow();
    if( chatWindow == 0 )
    {
      return;   // failed to initialize.
    }
  }
  else
  {
    // Happens when connection was lost and we're reconnecting.
#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster::startChat: Chatwindow already exists,"
              << " informing about new chat with " << chatInfo.getContactHandle() << endl;
#endif
  }

  chatWindow->startChat( chatInfo );
}



// 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 );
  if( QFile::exists(objectFileName) )
  {
#ifdef KMESSDEBUG_CHATMASTER
    kdDebug() << "ChatMaster: 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: 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);
  }
}


#include "chatmaster.moc"

Generated by  Doxygen 1.6.0   Back to index