Logo Search packages:      
Sourcecode: kmess version File versions

applicationlist.cpp

/***************************************************************************
                          applicationlist.cpp -  description
                             -------------------
    begin                : Mon 01 16 2005
    copyright            : (C) 2005 by Diederik van der Boor
    email                : "vdboor" --at-- "codingdomain.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.                                   *
 *                                                                         *
 ***************************************************************************/

// General application classes
#include "applicationlist.h"
#include "mimeapplication.h"
#include "p2papplication.h"

// MIME application classes
#include "filetransfer.h"
#include "gnomemeeting.h"
#include "msnremotedesktop.h"
#include "voiceconversation.h"

// P2P application classes
#include "picturetransferp2p.h"
#include "filetransferp2p.h"
#include "webapplicationp2p.h"
#include "webcamtransferp2p.h"

#include "../mimemessage.h"
#include "../p2pmessage.h"
#include "../msnswitchboardconnection.h"
#include "../extra/directconnectionpool.h"
#include "../extra/msndirectconnection.h"
#include "../../currentaccount.h"
#include "../../kmessdebug.h"
#include "../../msnobject.h"
#include "../../chat/chatmessage.h"
#include "../../contact/contactbase.h"

#include <qregexp.h>

#include <klocale.h>

#ifdef KMESSDEBUG_APPLICATIONLIST
#define KMESSDEBUG_APPLICATIONLIST_GENERAL
#endif


/**
 * @brief The constructor
 *
 * Initialize the application list for the given contact.
 * @param  contactHandle  The contact this application list is created for.
 */
00061 ApplicationList::ApplicationList(const QString &contactHandle)
  : QObject(0, "ApplicationList")
  , contactHandle_(contactHandle)
  , directConnection_(0)
  , directConnectionPool_(0)
  , pendingConnectionInvitation_(false)
{
  
}



/**
 * @brief The destructor.
 */
00076 ApplicationList::~ApplicationList()
{
#ifdef KMESSTEST
  ASSERT( mimeApplications_.isEmpty() );
  ASSERT( p2pApplications_.isEmpty() );
#endif
}


/**
 * @brief Abort all applications, the connection is closing or closed.
 *
 * This is called from contactLeftChat() and contactLeavingChat() to abort the
 * applications that can't operate without a switchboard connection (sometimes thats's only needed usability wise).
 *
 * @param  abortingConnection  Connection that is closing.
 * @param  userInitiated       Indicates whether this action was initiated by the user (closing the chat window),
 *                             or the contact left the chat first.
 * @returns Returns whether the object started to abort applications. Returns false if there is nothing to abort.
 */
00096 bool ApplicationList::abortApplications(const MsnSwitchboardConnection *abortingConnection, bool userInitiated)
{
  // Check if there is nothing to do
  if( mimeApplications_.isEmpty() && p2pApplications_.isEmpty() )
  {
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
    kdDebug() << "ApplicationList::abortApplications: No applications active." << endl;
#endif
    return false;
  }

  // If the contact is still in another private chat, all applications can use that one.
  // If the contact is only in a multi-chat, all applications need to close that don't support this.
  // If the contact left all chats, all applications need to be closed.
  bool hasMultiChats = false;

  // Check how many other chat sessions the contact still participates in.
  const ContactBase *contact = CurrentAccount::instance()->getContactByHandle(contactHandle_);
  if( ! KMESS_NULL(contact) )
  {
    const QPtrList<MsnSwitchboardConnection>   switchboards = contact->getSwitchboardConnections();
    QPtrListIterator<MsnSwitchboardConnection> switchboardIt(switchboards);;

    while( switchboardIt.current() != 0 )
    {
      MsnSwitchboardConnection *connection = switchboardIt.current();
      if( connection != abortingConnection )
      {
        if( connection->isExclusiveChatWithContact( contactHandle_ ) )
        {
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
          kdDebug() << "ApplicationList::abortApplications: Contact is still in another private chat, not terminating applications." << endl;
#endif
          return false;
        }
        else
        {
          hasMultiChats = true;
        }
      }

      ++switchboardIt;
    }
  }


  // Check if any multi chats are found.
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
  if( ! hasMultiChats )
  {
    kdDebug() << "ApplicationList::abortApplications: Contact left all chats, aborting applications." << endl;
  }
  else
  {
    kdDebug() << "ApplicationList::abortApplications: Contact left all private chats, aborting applications that don't support multi-chats." << endl;
  }
#endif

  // When aborting. verify whether the application is not being aborted here already.
  // Also check if the application was already started closing due a packet it received earlier.
  // This prevents nasty issues when a switchboard both reports the contact
  // will leave and actually left, or two switchboard report a contact has left.

  // First abort all mime-based applications.
  QPtrListIterator<MimeApplication> it(mimeApplications_);
  while( it.current() != 0 )
  {
    MimeApplication *app = it.current();
    if( ( abortingApplications_.count() > 0 && ! abortingApplications_.contains( static_cast<Application*>( app ) ) )
    &&  ( app->isPrivateChatRequired() || ! hasMultiChats ) )
    {
      abortingApplications_.append(app);
      if( ! app->isClosing() )
      {
        if( userInitiated )
        {
          app->userAborted();  // requests deletion after cancel message is delivered.
        }
        else
        {
          app->contactAborted();  // requests deletion.
        }
      }
    }
    ++it;
  }

  // Then abort all p2p-based applications.
  QPtrListIterator<P2PApplication> it2(p2pApplications_);
  while( it2.current() != 0 )
  {
    P2PApplication *app = it2.current();
    if( ! abortingApplications_.contains(app)
    &&  ( app->isPrivateChatRequired() || ! hasMultiChats ) )
    {
      abortingApplications_.append(app);
      if( ! app->isClosing() )
      {
        if( userInitiated )
        {
          app->userAborted();  // requests deletion when bye message is delivered.
        }
        else
        {
          app->contactAborted();  // requests deletion.
        }
      }
    }
    ++it2;
  }

  // The application may abort really quick and update the abortingApplications_ list directly from a signal.
  // In this case, the list is already emty, theirby also returning 'false'.
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
  kdDebug() << "ApplicationList::abortApplications: Got " << abortingApplications_.count() << " applications left to abort." << endl;
#endif
  return (! abortingApplications_.isEmpty());
}




/**
 * @brief Add a new MIME application to the list.
 *
 * After the application is added, it can be started with it's <code>start()</code> operation.
 * The putMsg() signal of the application will be connected to the sendMessage() slot,
 * the deleteMe() signal to the slotTerminateApplication() slot.
 *
 * @param application  The configured application instance.
 */
00227 void ApplicationList::addApplication(MimeApplication *application)
{
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
  kdDebug() << "ApplicationList::addApplication: connecting mime application signals." << endl;
#endif

  mimeApplications_.append( application );
  connectApplication( application );

  // The new-style P2P application manually call sendMessage() or put the message at the direct connection link.
  // The old-style MIME applications are directly connected here, so they don't need to have a reference to this object.
  connect( application, SIGNAL(      putMsg(const MimeApplication*, const MimeMessage&) ),
           this,        SLOT  ( sendMessage(const MimeApplication*, const MimeMessage&) ));
}



/**
 * @brief Add a new P2P application to the list.
 *
 * After the application is added, it can be started with it's <code>start()</code> operation.
 * The deleteMe() signal will be connected to the slotTerminateApplication() slot.
 * When an application needs to send a message, the sendMessage() methods determines whether the
 * message can be sent over the direct connection, or a switchboard connection is needed (fireing a putMsg() signal).
 *
 * @param application  The configured application instance.
 */
00254 void ApplicationList::addApplication(P2PApplication *application)
{
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
  kdDebug() << "ApplicationList::addApplication: connecting p2p application signals." << endl;
#endif

  p2pApplications_.append( application );
  connectApplication( application );
}



/**
 * @brief Attempt to establish a direct connection at the given ipaddress/port.
 *
 * It uses the DirectConnectionPool class internally to handle multiple connection attempts.
 * When all connection attempts failed, a connectionFailed() signal will be sent.
 *
 * It's possible multiple P2PApplication instances require a direct connection.
 * Only the first application handles the connectionEstablished() signal.
 * The other applications wait until a connectionAuthorized() or connectionFailed() signal is fired.
 *
 * @param  ipAddress  The IP-Address to connect to.
 * @param  port       The port number to connect to.
 * @return Whether the socket could be created or not.
 *         Returns false if the socket could not be created.
 */
00281 bool ApplicationList::addConnection(const QString &ipAddress, const int port)
{
  // Avoid overwriting the current directConnection_ variable.
  if( directConnection_ != 0 )
  {
    if( ! directConnection_->isConnected() )
    {
      // Still had an old connection which went idle and closed, cleanup.
      if( ! KMESS_NULL(directConnectionPool_) )
      {
        directConnectionPool_->verifyActiveConnection();
      }
      directConnection_ = 0;
    }
    else
    {
      // Already connected, don't allow second connection.
      kdWarning() << "ApplicationList: Refusing connection attempt, a connection has already been made." << endl;
      return 0;
    }
  }

#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
  kdDebug() << "ApplicationList::addConnection: adding new connection attempt." << endl;
#endif

  // Avoid overwriting the current directConnection_ variable.
  if( directConnection_ != 0 )
  {
    kdWarning() << "ApplicationList: Refusing connection attempt, a connection has already been made." << endl;
    return 0;
  }

  // Init direct connection pool
  if( directConnectionPool_ == 0 )
  {
    initializeDirectConnectionPool();
  }

  // Init connection
  MsnDirectConnection *connection = new MsnDirectConnection(contactHandle_);

  // Connect connection
  connect(connection, SIGNAL(                 messageReceived(const QByteArray&) ),
          this,         SLOT(  slotGotDirectConnectionMessage(const QByteArray&) ));

  // Add to pool, and start
  return directConnectionPool_->addConnection(connection, ipAddress, port);
}



/**
 * @brief Attempt to establish a direct connection by listening at a random port.
 *
 * This method operates similar like the addConnection() method.
 *
 * @return The port number the connection is listening at. Returns zero if listening failed.
 */
00340 int ApplicationList::addServerConnection()
{
  // Avoid overwriting the current directConnection_ variable.
  if( directConnection_ != 0 )
  {
    if( ! directConnection_->isConnected() )
    {
      // Still had an old connection which went idle and closed, cleanup.
      if( ! KMESS_NULL(directConnectionPool_) )
      {
        directConnectionPool_->verifyActiveConnection();
      }
      directConnection_ = 0;
    }
    else
    {
      // Already connected, don't allow second connection.
      kdWarning() << "ApplicationList: Refusing server connection attempt, a connection has already been made." << endl;
      return 0;
    }
  }

#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
  kdDebug() << "ApplicationList::addServerConnection: adding new server socket." << endl;
#endif

  // Init direct connection pool
  if( directConnectionPool_ == 0 )
  {
    initializeDirectConnectionPool();
  }

  // Init connection
  MsnDirectConnection *connection = new MsnDirectConnection(contactHandle_);

  // Connect connection
  connect(connection, SIGNAL(                 messageReceived(const QByteArray&) ),
          this,         SLOT(  slotGotDirectConnectionMessage(const QByteArray&) ));

  // Add to pool, and start.
  return directConnectionPool_->addServerConnection(connection);
}



/**
 * @brief Connect the application object signals.
 * @param app The application object instance.
 */
00389 void ApplicationList::connectApplication(Application *app)
{
  connect( app,  SIGNAL(                 deleteMe( Application* ) ),    // Request for removal
           this,   SLOT( slotTerminateApplication( Application* ) ) );
}



/**
 * @brief Abort all applications, the contact is leaving a chat.
 *
 * This method is called by the MsnSwitchboardConnection to inform the contact is about the leave the chat.
 *
 * @param connection     The connection the contact is leaving.
 * @param userInitiated  Indicates whether this action was initiated by the user (closing the chat window),
 *                       or the contact left earlier.
 * @returns Returns whether the object started to abort applications.
 */
00407 bool ApplicationList::contactLeavingChat(const MsnSwitchboardConnection *connection, bool userInitiated)
{
  return abortApplications(connection, userInitiated);
}



/**
 * @brief Abort all applications, the contact left a chat.
 *
 * This method is called by the MsnSwitchboardConnection to inform the contact left the chat.
 *
 * @param userInitiated  Indicates whether this action was initiated by the user (closing the chat window),
 *                       or the contact left earlier.
 * @returns Returns whether the object started to abort applications.
 */
00423 bool ApplicationList::contactLeftChat(bool userInitiated)
{
  return abortApplications(0, userInitiated);
}



/**
 * @brief Create the MIME application instance for the given invitation message.
 *
 * This extracts the Application-GUID field from the message and calls
 * createApplicationByGuid() to create the correct MimeApplication object.
 * When an invitation type is not recognized, it's rejected with sendMimeRejectMessage().
 *
 * @param  message  The MimeMessage sent by the contact.
 * @returns The new application instance.
 */
00440 MimeApplication * ApplicationList::createApplication(const MimeMessage &message)
{
  MimeApplication *app = 0;
  QString appGuid = message.getValue("Application-GUID");

  // If no GUID is found:
  if( appGuid.isEmpty() )
  {
    // Notify the user:
    kdWarning() << "ApplicationList: MIME message can't be handled, "
                << "Application-GUID not found. (message dump follows)\n" << message.getFields() << endl;
    return 0;
  }

  // Determine what application to start based on the Application-GUID
  app = createApplicationByGuid( appGuid );
  if( app == 0 )
  {
    MimeMessage reject;
    // TODO: create reject message.
    sendMimeRejectMessage( message.getValue("Invitation-Cookie"), true);
    return 0;
  }


  // Connect up the application
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
  kdDebug() << "ApplicationList: Connecting MimeApplication signals" << endl;
#endif

  // Auto-add application
  addApplication(app);

  // Inform listeners about the new application (notably ChatMaster)
  emit newApplication(app);
  return app;
}



/**
 * @brief Create the P2P application instance for the given invitation message.
 *
 * This extracts the EUF-GUID field from the message and calls
 * createApplicationByEufGuid() to create the correct P2PApplication object.
 * When an invitation type is not recognized, a temporary
 * application will be initiated to reject and close the session properly.
 *
 * @param  message  The P2PMessage sent by the contact.
 * @returns The new application instance. 
 */
00491 P2PApplication * ApplicationList::createApplication(const P2PMessage &message)
{
#ifdef KMESSTEST
  ASSERT( message.getSessionID() == 0 );
  ASSERT( message.getDataSize()   > 0 );
#endif

#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
  kdDebug() << "ApplicationList: Creating new P2PApplication instance to parse the P2P message." << endl;
#endif

  P2PApplication *app = 0;

  // Extract the SLP data message
  QString slpMessage = QString::fromUtf8( message.getData(), message.getDataSize() );

  if(! slpMessage.startsWith("INVITE"))
  {
    // Not an invite message
    // this belongs to another session, but gotMessage() couldn't find it.
    // Likely a P2PApplication object terminated prematurely.
    QString preamble = slpMessage.left( QMIN(slpMessage.find(":"), 20) );
    kdWarning() << "ApplicationList: P2P message can't be handled, P2PApplication terminated already "
                << "(slp-preamble=" << preamble
                << " contact="      << contactHandle_
                << ")." << endl;
    return 0;
  }


  // Find the EUF-GUID in the data
  QRegExp guidRE("EUF-GUID: (.+)\r\n");
  guidRE.setMinimal(true);
  int guidPos = guidRE.search(slpMessage);

  // If no EUF-GUID is found:
  if( guidPos == -1 )
  {
    // Notify the user:
    kdWarning() << "ApplicationList: P2P message can't be handled, "
                << "EUF-GUID not found. (message dump follows)\n" << slpMessage << endl;
    return 0;
  }

  // Determine what application to start based on the EUF-GUID
  app = createApplicationByEufGuid( guidRE.cap(1) );
  if( app == 0 )
  {
    // Create an application instance which will reject the invitation correctly.
    app = new P2PApplication(this);

    // Application::contactStarted1 rejects correct invitatations automatically,
    // so don't set app->setMode(APP_MODE_ERROR_HANDLER) here..!
  }


  // Connect up the application
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
  kdDebug() << "ApplicationList: Connecting P2PApplication signals" << endl;
#endif

  // Auto-add application
  addApplication(app);

  // Inform listeners about the new application (notably ChatMaster)
  emit newApplication(app);
  return app;
}



/**
 * @brief Factory method to create a MIME application instance for the given Application-GUID.
 * @param  appGuid  The Application-Guid field of the invitation message, identifies the application type to start.
 * @return The application object instance, or null if the Application-GUID is not recognized.
 */
00567 MimeApplication * ApplicationList::createApplicationByGuid(const QString &appGuid)
{
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
  kdDebug() << "ApplicationList: Creating MimeApplication for GUID " << appGuid << endl;
#endif

  if( appGuid == GnomeMeeting::getAppId() )
  {
    return new GnomeMeeting( contactHandle_ );
  }
  else if( appGuid == MsnRemoteDesktop::getAppId() )
  {
    return new MsnRemoteDesktop( contactHandle_ );
  }
  else if( appGuid == FileTransfer::getAppId() )
  {
    return new FileTransfer( contactHandle_ );
  }
  else if( appGuid == VoiceConversation::getAppId() )
  {
    // TODO: check if there is already an active audio conversation.
    // i18n("The contact is inviting you to start an audio conversation, but you can't have more then one audio conversation at the same time.")) );
    return new VoiceConversation( contactHandle_ );
  }
  else
  {
    return 0;
  }
}



/**
 * @brief Factory method to create a P2P application instance for the given EUF-GUID.
 * @param  eufGuid  The EUF-Guid field of the invitation message, identifies the application type to start.
 * @return The application object instance, or null if the EUF-GUID is not recognized.
 */
00604 P2PApplication * ApplicationList::createApplicationByEufGuid(const QString &eufGuid)
{
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
  kdDebug() << "ApplicationList: Creating P2PApplication for GUID " << eufGuid << endl;
#endif

  if( eufGuid == PictureTransferP2P::getAppId() )
  {
    return new PictureTransferP2P( this );
  }
  else if( eufGuid == FileTransferP2P::getAppId() )
  {
    return new FileTransferP2P( this );
  }
  else if( eufGuid == WebApplicationP2P::getAppId() )
  {
    return new WebApplicationP2P( this );
  }
  else if( eufGuid == WebcamTransferP2P::getPullAppId() )
  {
    return new WebcamTransferP2P( this );
  }
  else if( eufGuid == WebcamTransferP2P::getPushAppId() )
  {
    return new WebcamTransferP2P( this );
  }
  else
  {
    return 0;
  }
}



/**
 * @brief Find the P2P application which should handle the ACK message.
 * @param    ackMessage  The P2P ACK message.
 * @returns  The application instance, or null if the object can't be found.
 */
00643 P2PApplication * ApplicationList::getApplicationByAckData(const P2PMessage &ackMessage) const
{
  unsigned long ackUniqueID  = ackMessage.getAckUniqueID();
  QPtrListIterator<P2PApplication> p2pIterator(p2pApplications_);
  while( p2pIterator.current() != 0 )
  {
    // The application may have multiple messages unacked, see whether the ACK matches.
    if( p2pIterator.current()->hasUnAckedMessage(ackMessage) )
    {
      // Test whether the match was only partial (error displayed here because P2PApplication
      // calls hasUnAckedMessage() later again, causing the message to appear twice)
      if( ackUniqueID == 0 )
      {
        // HACK: added for GAIM 1.5 (does not return UniqueID)
        kdWarning() << "P2PApplication: Got P2P message with matching ackMessageID and no ackUniqueID set "
                    << "(delivering anyway, contact=" << contactHandle_ << ")!" << endl;
      }

      return p2pIterator.current();
    }

    ++p2pIterator;
  }

  return 0;
}



/**
 * @brief Find the P2P application which handles the session for the given Call-ID.
 * @param    callID  The CallID value of the application session.
 * @returns  The application instance, or null if the object can't be found.
 */
00677 P2PApplication * ApplicationList::getApplicationByCallId(const QString &callID) const
{
  QPtrListIterator<P2PApplication> p2pIterator(p2pApplications_);
  while( p2pIterator.current() != 0 )
  {
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
    kdDebug() << "ApplicationList: Demultiplexing P2P message"
              << "    (CallID " << p2pIterator.current()->getCallID() << " == " << callID  << " ?)" << endl;
#endif

    if( p2pIterator.current()->getCallID() == callID )
    {
      // Found it, return reference
      return p2pIterator.current();
    }

    ++p2pIterator;
  }

  return 0;
}



/**
 * @brief Find the application  with uses the given cookie.
 *
 * This is used to deliver accept/cancel commands from the chat window.
 * If the cookie exists, the method either returns a MimeApplication or P2PApplication instance.
 *
 * @param   cookie  The cookie string of the application.
 * @returns The application instance, or null if the object can't be found.
 */
00710 Application * ApplicationList::getApplicationByCookie(const QString &cookie) const
{
  // First see if there is a p2p application with the cookie.
  QPtrListIterator<P2PApplication> p2pIterator(p2pApplications_);
  while ( p2pIterator.current() != 0 )
  {
    if ( p2pIterator.current()->getCookie() == cookie )
    {
      // Found it, return reference.
      return p2pIterator.current();
    }

    ++p2pIterator;
  }

  // Also look for a mime application with the cookie.
  QPtrListIterator<MimeApplication> mimeIterator(mimeApplications_);
  while ( mimeIterator.current() != 0 )
  {
    if ( mimeIterator.current()->getCookie() == cookie )
    {
      // Found it, return reference.
      return mimeIterator.current();
    }

    ++mimeIterator;
  }

  return 0;
}



/**
 * @brief Find the P2P application which handled the previous message fragment from the contact.
 * @param    messageID  The identifier of the last received message.
 * @returns  The application instance, or null if the object can't be found.
 */
00748 P2PApplication * ApplicationList::getApplicationByLastMessage(unsigned long messageID) const
{
  QPtrListIterator<P2PApplication> p2pIterator(p2pApplications_);
  while( p2pIterator.current() != 0 )
  {
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
    unsigned long debugMsgId = p2pIterator.current()->getLastContactMessageID();
    kdDebug() << "ApplicationList: Demultiplexing P2P message"
              << "    (fragment MessageID " << debugMsgId << " == " << messageID << " ?)" << endl;
#endif

    if(p2pIterator.current()->getLastContactMessageID() == messageID)
    {
      // HACK: Not tested for "getLastContactAckMessageID() == ackMessageID" to support broken implementations.

      // Found it, save reference
      return p2pIterator.current();
    }

    ++p2pIterator;
  }

  return 0;
}



/**
 * @brief Find the P2P application which authorizes the transfer for the given nonce.
 * @param    nonce  The Nonce value of the application session.
 * @returns  The application instance, or null if the object can't be found.
 */
00780 P2PApplication * ApplicationList::getApplicationByNonce(const QString &nonce) const
{
  QPtrListIterator<P2PApplication> p2pIterator(p2pApplications_);
  while( p2pIterator.current() != 0 )
  {
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
    kdDebug() << "ApplicationList: Demultiplexing P2P message"
              << "    (Nonce " << p2pIterator.current()->getNonce() << " == " << nonce << " ?)" << endl;
#endif

    if( p2pIterator.current()->getNonce() == nonce )
    {
      // Found it, return reference
      return p2pIterator.current();
    }

    ++p2pIterator;
  }

  return 0;
}



/**
 * @brief Find the P2P application identified with the given session ID.
 * @param    sessionID  The identifier of the application.
 * @returns  The application instance, or null if the object can't be found.
 */
00809 P2PApplication * ApplicationList::getApplicationBySessionId(unsigned long sessionID) const
{
  QPtrListIterator<P2PApplication> p2pIterator(p2pApplications_);
  while( p2pIterator.current() != 0 )
  {
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
    kdDebug() << "ApplicationList: Demultiplexing P2P message"
              << "    (SessionID " << p2pIterator.current()->getSessionID() << " == " << sessionID << " ?)" << endl;
#endif
    if( p2pIterator.current()->getSessionID() == sessionID )
    {
      // Found the session
      return p2pIterator.current();
    }

    ++p2pIterator;
  }

  return 0;
}



/**
 * @brief Return the handle of the contact this class manages.
 * @returns The contact handle.
 */
00836 const QString & ApplicationList::getContactHandle() const
{
  return contactHandle_;
}



/**
 * @brief Return the direct connection, if available.
 * @returns The direct connection object.
 */
00847 MsnDirectConnection * ApplicationList::getDirectConnection() const
{
  return directConnection_;
}



/**
 * @brief Deliver a received MIME message to the correct application instance.
 *
 * This method is called when a message was received by the MsnSwitchboardConnection class.
 * It examines the various fields of the message to deliver the message to the correct MimeApplication instance.
 * If the message contains an invitation, a new application instance will be created with createApplication().
 * A warning will be displayed at the console when an application instance could not be found.
 *
 * @param  message  The received mime message.
 */
00864 void ApplicationList::gotMessage(const MimeMessage &message)
{
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
  kdDebug() << "ApplicationList: Parsing Mime appliation message." << endl;
  message.print();
#endif
  Application *app = 0;

  // If this is an invite message, an application need to be created
  if( message.getValue("Invitation-Command") == "INVITE" )
  {
    // Create the application based on the type
    app = createApplication( message );

    // Check if Application could not be created for some reason
    if( app == 0 )
    {
      return;
    }
  }
  else
  {
    // Find the application based on the app cookie
    app = getApplicationByCookie( message.getValue("Invitation-Cookie") );
    if( app == 0 )
    {
      kdWarning() << "ApplicationList: Unable to handle MIME message, "
                  << "Invitation-Cookie not found (contact=" << contactHandle_ << ")." << endl;
      return;
    }
  }

  // Last check before casting the object.
  if( ! app->inherits("MimeApplication") )
  {
    kdWarning() << "ApplicationList::gotMessage: found application is not a mime application!" << endl;
    return;
  }

  // Deliver the message
  static_cast<MimeApplication*>(app)->gotMessage( message );
}



/**
 * @brief Deliver a received P2P message to the correct application instance.
 *
 * This method is called when a message was received by
 * the MsnSwitchboardConnection class (via the ChatMaster) or MsnDirectConnection class.
 * It examines the various fields of the message to deliver the message to the correct P2PApplication instance.
 * If the message contains an invitation, a new application instance will be created with createApplication().
 * A warning will be displayed at the console when an application instance could not be found.
 *
 * @param  message  The received p2p message.
 */
00920 void ApplicationList::gotMessage(const P2PMessage &message)
{
  // Parse the message
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
  kdDebug() << "ApplicationList: Parsing P2P:"
            << " SID="         << message.getSessionID()
            << " MID="         << message.getMessageID()
            << " AckID="       << message.getAckMessageID()
            << " ACKUID="      << message.getAckUniqueID()
            << " flags=0x"     << QString::number(message.getFlags(), 16)
            << " size="        << message.getDataSize()
            << " no.sessions=" << p2pApplications_.count() << endl;
#endif

  P2PApplication *p2pApplication = 0;
  unsigned long sessionID = message.getSessionID();


  // Session ID is 0 if clients negotiate (for both INVITE and BYE messages!)
  // Otherwise it's set.
  if( sessionID != 0 )
  {
    // The message has a SessionID set
    p2pApplication = getApplicationBySessionId( sessionID );

    // Notify the user if the message was invalid (send error-response later)
    if(p2pApplication == 0)
    {
      kdWarning() << "ApplicationList: Unable to handle P2P message, Session-ID not found "
                  << "(flags=0x" << QString::number(message.getFlags(), 16)
                  << " size="    << message.getDataSize()
                  << " contact=" << contactHandle_
                  << ")." << endl;
    }
  }
  else if( message.getFlags() != 0 && message.getDataSize() == 0 )
  {
    // It's some kind of low-level control message, typically without data/payload.
    // Both the ACK-Message-ID and ACK-Unique-ID fields
    // should resolve the orginal session it was sent from.
    // This can also be a connection handshake, even though it shouldn't appear at the switchboard.
    // That occurs when the connection was closed before that packet could be sent.
    p2pApplication = getApplicationByAckData(message);

    // No existing session found..?
    if(p2pApplication == 0)
    {
      // NOTE: if flags==64 (0x40), it it's possible this is actually a bug from MSN7.0.
      kdWarning() << "ApplicationList: P2P message can't be handled, ACK-Message-ID not found "
                  << "(flags=0x" << QString::number(message.getFlags(), 16)
                  << " size="    << message.getDataSize()
                  << " contact=" << contactHandle_
                  << ")." << endl;
      return;
    }
  }
  else if( message.getFlags() == 0 && message.getDataSize() == 0 && message.getAckUniqueID() == 0 )
  {
    // HACK: added for bot Bot2k3 4.1 (sends first ack without UniqueID, flags or any other context)
    kdWarning() << "SwitchBoard P2P message has no ACK fields set at all "
                << "(assuming base identifier message, contact=" << contactHandle_ << ")!" << endl;
    return;
  }
  else if( message.getFlags() == 0
       &&  message.getDataSize() > 0)  // HACK: added size constraint for Bot2k3 4.1 (sends an empty message with a zero flag)
  {
    // It's a negotiation/MSNSLP message
    if( message.getDataOffset() > 0 )
    {
      // This is another fragment of the SLP message.
      // Find the application which received the previous fragment too.
      p2pApplication = getApplicationByLastMessage( message.getMessageID() );
    }
    else
    {
      // No offset, start of SLP message
      // HACK: This assumes that the Call-ID will be found in the first part.

      // Initialize CallID regexp
      QRegExp callRE("Call-ID: (.+)\r\n"); // match everything because MSN6 accepts anything.
      callRE.setMinimal(true);

      QString slpMessage = QString::fromUtf8( message.getData(), message.getDataSize() );
      int     callPos    = callRE.search(slpMessage);

      if( callPos == -1 )
      {
        kdWarning() << "ApplicationList: Unable to handle P2P message, "
                    << "CallID not found. (message dump follows, size=" << message.getDataSize() << " contact=" << contactHandle_ << ")" << endl;
        kdDebug() << slpMessage << endl;
      }
      else
      {
        // first try to find an existing session. (for 200 OK and BYE messages)
        p2pApplication = getApplicationByCallId( callRE.cap(1) );

        // No existing session found..?
        if( p2pApplication == 0 )
        {
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
          kdDebug() << "ApplicationList: No existing P2P session found for the MSNSLP message, "
                    << "must be a new session invitation." << endl;
#endif

          // Initialize an application class to handle the invitation.
          p2pApplication = createApplication( message );
        }
      }
    }
  }
  else
  {
    // Didn't reconize the flag type.
    kdWarning() << "ApplicationList: P2P message can't be handled, unknown flag type found "
                << "(flags=0x" << QString::number(message.getFlags(), 16)
                << " size="    << message.getDataSize()
                << " contact=" << contactHandle_
                << ")." << endl;
    if( message.getFlags() == 0 )
    {
      // HACK: added for broken clients who sent an empty message with a zero flag (Bot2K3 v4.1)
      return;
    }
  }


  // If the application was found, deliver the message
  if( p2pApplication != 0 )
  {
    // Deliver the message
    p2pApplication->gotMessage( message );
  }
  else
  {
    // If we didn't find the application, the message had bad fields
    // or we don't support the invitation type:

    if( message.getFlags() != 0 && message.getDataSize() == 0 )
    {
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
      kdDebug() << "ApplicationList: Not sending a P2P Error response for error-message "
                << "(flags=0x" << QString::number(message.getFlags(), 16)
                << " size="    << message.getDataSize()
                << " contact=" << contactHandle_ << ")." << endl;
#endif
    }
    else
    {
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
    kdDebug() << "ApplicationList: Creating new P2PApplication instance to send error response." << endl;
#endif

      // Create a default application to send the correct error response (also for unknown invitation types)
      // Application auto-deletes itself.
      p2pApplication = new P2PApplication(this);
      p2pApplication->setMode( Application::APP_MODE_ERROR_HANDLER );
      p2pApplication->gotMessage( message );
    }
  }
}



/**
 * @brief Return whether there is an active direct connection.
 *
 * If the direct connection is no longer connected, it returns false.
 * Methods like sendMessage() automatically clean up a disconnected instance.
 *
 * @returns  The direct connection if it's available *and* connected.
 */
01091 bool ApplicationList::hasDirectConnection() const
{
  return ( directConnection_ != 0 && directConnection_->isConnected() );
}



/**
 * @brief Return whether an MSN object transfer for the given msn object is already running.
 *
 * This is called by ChatMaster to avoid multiple invitations for the same MsnObject (display picture, wink, emoticon).
 *
 * @param msnObject  The requested msn object.
 * @return Whether the msn object is being transferred already.
 */
01106 bool ApplicationList::hasMsnObjectTransfer( const MsnObject &msnObject ) const
{
  QPtrListIterator<P2PApplication> it( p2pApplications_ );
  P2PApplication *app;
  PictureTransferP2P *transferApp;
  while( it.current() != 0 )
  {
    app = it.current();
    if( app->isA("PictureTransferP2P") )
    {
      transferApp = static_cast<PictureTransferP2P*>( app );
      if( transferApp->getMsnObject().getDataHash() == msnObject.getDataHash() )
      {
        return true;
      }
    }

    ++it;
  }

  return false;
}



/**
 * @brief Return whether the transfer invitation has been sent.
 * @return  Whether there is a pending connection invitation.
 */
01135 bool ApplicationList::hasPendingConnectionInvitation() const
{
  return pendingConnectionInvitation_;
}



/**
 * @brief Return whether there are pending connection attempts.
 * @returns  Returns true when there are pending connection attempts.
 */
01146 bool ApplicationList::hasPendingConnections() const
{
  return (directConnectionPool_ != 0 && directConnectionPool_->hasPendingConnections());
}



/**
 * @brief Create the direct connection pool.
 *
 * This pool handles all connection attempts and returns
 * the connection that could be established.
 */
01159 void ApplicationList::initializeDirectConnectionPool()
{
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
  kdDebug() << "ApplicationList: Initializing direct connection pool." << endl;
#endif
#ifdef KMESSTEST
  ASSERT( directConnectionPool_ == 0 );
#endif

  if( directConnectionPool_ == 0 )
  {
    // The direct connection pool handles all connection attempts,
    // making it appear as if we only need to handle one connection.
    directConnectionPool_ = new DirectConnectionPool();

    connect(directConnectionPool_, SIGNAL(      activeConnectionAuthorized()  ),    // The connection was authorized
            this,                    SLOT(  slotActiveConnectionAuthorized()  ));
    connect(directConnectionPool_, SIGNAL(          activeConnectionClosed()  ),    // The active connection was closed
            this,                    SLOT(      slotActiveConnectionClosed()  ));
    connect(directConnectionPool_, SIGNAL(           connectionEstablished()  ),    // The connection was established
            this,                    SLOT(       slotConnectionEstablished()  ));
    connect(directConnectionPool_, SIGNAL(            allConnectionsFailed()  ),    // All connection attempts failed.
            this,                    SLOT(        slotAllConnectionsFailed()  ));
  }
}



/**
 * @brief Pause all applications, avoid flooding the switchboard message queue.
 *
 * This is called when the send queue of all switchboards is full.
 * Applications are paused to prevent flooding of the queue.
 * The resumeApplications() method is called when
 * a switchboard is ready to send more messages.
 */
01195 void ApplicationList::pauseApplications()
{
  // Only check P2Papplications, mime applications don't send many messages.

  QPtrListIterator<P2PApplication> p2pIterator(p2pApplications_);
  while ( p2pIterator.current() != 0 )
  {
    p2pIterator.current()->pauseTransfer();
    ++p2pIterator;
  }
}



/**
 * @brief Resume all applications, allowing messages to be sent again.
 *
 * This method is called when a MsnSwitchboardConnection is ready to send more messages.
 * @param isPrivateChat  Whether the available switchboard is a private chat, otherwise only multi-chat capable applications may resume.
 */
01215 void ApplicationList::resumeApplications(bool isPrivateChat)
{
  // Only check P2Papplications, mime applications don't send many messages.

  QPtrListIterator<P2PApplication> p2pIterator(p2pApplications_);
  while ( p2pIterator.current() != 0 )
  {
    if( isPrivateChat || ! p2pIterator.current()->isPrivateChatRequired() )
    {
      p2pIterator.current()->resumeTransfer();
    }
    ++p2pIterator;
  }
}



/**
 * @brief Send a message for a MIME application.
 * @param  source       The MimeApplication responsable for sending the message.
 * @param  messageData  The message body.
 */
01237 void ApplicationList::sendMessage(const MimeApplication *source, const MimeMessage &messageData)
{
  QString messageBody = messageData.getFields();
#ifdef KMESSTEST
  // Check whether a QByteArray is passed. gcc is able to create the cast
  // because MimeMessage() has a constructor which accepts a QByteArray.
  ASSERT( messageBody[0].isLetter() );
  ASSERT( messageData.getBinaryBody().size() == 0 );
#endif

  // Deliver the message over the switchboard in a mime message
  MimeMessage messageWrapper;
  messageWrapper.addField("MIME-Version", "1.0");
  messageWrapper.addField("Content-Type", "text/x-msmsgsinvite; charset=UTF-8");
  messageWrapper.setBody( messageBody );

  emit putMsg( messageWrapper, contactHandle_, (source == 0 || source->isPrivateChatRequired()) );
}



/**
 * @brief Send a P2P message over one of the available connections/bridges.
 *
 * If a direct connection is available, it will be used to transfer the message.
 * Otherwise a putMsg() signal is fired to the ChatMaster, which will deliver
 * the message to an MsnSwitchboardConnection. Before the P2PMessage can be handled
 * by a switchboard it's wrapped in a MimeMessage;
 * the switchboard sends all messages as MIME messages.
 *
 * @param source       The P2PApplication responsable for sending the message.
 * @param header       The message headers (48 bytes).
 * @param messageData  The message body / payload. This field is optional.
 * @param footerCode   The footer code (4 bytes), appended to messages which are sent over the switchboard.
 */
01272 void ApplicationList::sendMessage(const P2PApplication *source, const QByteArray &header, const QByteArray &messageData, uint footerCode)
{
  // Verify whether a direct connection exists to send the message.
  // It needs to be connected and authorized.
  bool hasDirectConnection = false;
  if( directConnection_ != 0 )
  {
    if( ! directConnection_->isConnected() )
    {
      if( ! KMESS_NULL(directConnectionPool_) )
      {
        directConnectionPool_->verifyActiveConnection();  // deletes closed connection.
        directConnection_ = 0;
      }
    }
    else
    {
      hasDirectConnection = directConnection_->isAuthorized();
    }
  }


  // Deliver over a direct connection or switchboard session.
  if(hasDirectConnection)
  {
    // Deliver the message over the direct connection.
    // The footer will not be appended to the message.
    // Concatenate the message.
    uint  rawSize = (header.size() + messageData.size());   // header + body
    char *rawData = new char[rawSize];                      // create buffer
    memcpy(rawData, header.data(), header.size());          // copy header

    if( messageData.size() > 0 )
    {
      // Copy body
      memcpy(rawData + header.size(), messageData.data(), messageData.size());
    }

    // Wrap *rawData in a QByteArray, and sent over the direct connection
    QByteArray p2pMessage;
    p2pMessage.assign(rawData, rawSize);  // auto deletes *rawData
    directConnection_->sendMessage(p2pMessage);
  }
  else
  {
    // Sending message over switchboard. Encapsulate in a MimeMessage.

    // At the end of the message we need to add an footer of 4 bytes.
    // This is little endian format, so we can't use P2PMessage::insertBytes()
    //
    // Possible values:
    //  0x00 session
    //  0x01 for display/emoticon
    //  0x02 filetransfer, or start menu code

    char footer[4];
    footer[0] = (char) ((footerCode >> 24) & 0xFF);
    footer[1] = (char) ((footerCode >> 16) & 0xFF);
    footer[2] = (char) ((footerCode >>  8) & 0xFF);
    footer[3] = (char) ( footerCode        & 0xFF);

    // Deliver the P2P message over the switchboard in a mime message
    MimeMessage messageWrapper;
    messageWrapper.addField("MIME-Version", "1.0");
    messageWrapper.addField("Content-Type", "application/x-msnmsgrp2p");
    messageWrapper.addField("P2P-Dest",     getContactHandle());
    messageWrapper.setBinaryBody(header, messageData, footer);

    emit putMsg( messageWrapper, contactHandle_, (source == 0 || source->isPrivateChatRequired()) );
  }
}



/**
 * @brief Reject an invitation for a mime-application.
 * @param invitationCookie  The cookie used in the invitation message.
 * @param notInstalled      Whether the invitation was rejected because it is not supported by KMess.
 */
01351 void ApplicationList::sendMimeRejectMessage( const QString &invitationCookie, bool notInstalled )
{
  // Create mime message
  MimeMessage message;
  message.addField( "Invitation-Command", "CANCEL"         );
  message.addField( "Invitation-Cookie",  invitationCookie );
  message.addField( "Cancel-Code",        (notInstalled ? "REJECT_NOT_INSTALLED" : "CANCEL") );

  // Forward to send
  sendMessage(0, message);
}



/**
 * @brief Set whether the transfer invitation has been sent.
 *
 * This property is set by the P2PApplication class, to prevent
 * other P2PApplication classes from sending the same invitation.
 * The other P2PApplication classes wait until the first class establishes
 * the connection, aborts it, or the connection attempts failed.
 *
 * The state is automatically reset when a connection is established
 * or all connection attempts failed. If a class sets the state back to false
 * while a connection was pending, a connectionFailed() signal will be
 * fired so other P2P applications also continue to transfer data over the switchboard.
 *
 * @param state  True if the transfer invitation has been sent, false when it was redrawn.
 */
01380 void ApplicationList::setPendingConnectionInvitation(bool state)
{
  // Detect first
  bool failed = ( ! state && pendingConnectionInvitation_ );

  // set state
  pendingConnectionInvitation_ = state;

  // Emulate the connectionFailed() signal. The connection could not be made,
  // but it wasn't even attempted.
  if( failed )
  {
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
    kdDebug() << "ApplicationList: Pending connection invitation was aborted by P2PApplication, "
              << "informing application no direct connection is possible." << endl;
#endif

    emit connectionFailed();
  }
}



/**
 * @brief The direct connection is authorized.
 *
 * At this point, the direct connection can be re-used by other P2P applications.
 */
01408 void ApplicationList::slotActiveConnectionAuthorized()
{
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
  kdDebug() << "ApplicationList: Direct connection with '" << contactHandle_ << "' authorized." << endl;
#endif
#ifdef KMESSTEST
  ASSERT( directConnection_     != 0 );
#endif

  // Inform all awaiting applications they can start to use the connection
  emit connectionAuthorized();
}



/**
 * @brief The direct connection was closed.
 *
 * It resets the internal state and fires a connectionFailed() or connectionClosed() signal to other classes.
 */
01428 void ApplicationList::slotActiveConnectionClosed()
{
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
  kdDebug() << "ApplicationList: The direct connection with '" << contactHandle_ << "' was closed." << endl;
#endif

  // Reset state variable.
  pendingConnectionInvitation_ = false;

  // Detect whether the connection was already set here.
  if( directConnection_ == 0 )
  {
    // This happens when the nonce was invalid in slotGotDirectConnectionMessage().
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
    kdDebug() << "ApplicationList: The current connection was closed prematurely, signaling that connection failed instead." << endl;
#endif

    emit connectionFailed();
  }
  else
  {
    // Also forward to our listeners.
    emit connectionClosed();
  }


  // reset the pointer, will be removed by the DirectConnectionPool later
  directConnection_ = 0;


  // If the pool was still active, it can be closed now.
  if( directConnectionPool_ != 0 )
  {
    directConnectionPool_->deleteLater();
    directConnectionPool_ = 0;
  }
}



/**
 * @brief All direct connection attempts failed.
 *
 * This resets the internal state and fires the connectionFailed() signal.
 */
01473 void ApplicationList::slotAllConnectionsFailed()
{
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
  kdDebug() << "ApplicationList: The direct connection with '" << contactHandle_ << "' could not be made." << endl;
#endif
#ifdef KMESSTEST
  ASSERT( directConnectionPool_ != 0 );
  ASSERT( ! directConnectionPool_->hasActiveConnection()   );
  ASSERT( ! directConnectionPool_->hasPendingConnections() );
#endif

#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
  if( directConnection_ == 0 )
  {
    kdDebug() << "ApplicationList: No remaining direct connections left" << endl;
  }
  else
  {
    kdDebug() << "ApplicationList: The current connection was closed prematurely" << endl;
  }
#endif

  // Reset state variable.
  pendingConnectionInvitation_ = false;

  // Also forward to our listeners.
  // Currently we have no local reference to unset, but this may change
  emit connectionFailed();

  // If the pool was still active, it can be closed now.
  if( directConnectionPool_ != 0 )
  {
    directConnectionPool_->deleteLater();
    directConnectionPool_ = 0;
  }
}



/**
 * @brief The direct connection is established.
 *
 * This sets the internal state and fires a connectionEstablished() signal.
 */
01517 void ApplicationList::slotConnectionEstablished()
{
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
  kdDebug() << "ApplicationList: Direct connection with '" << contactHandle_ << "' established." << endl;
#endif
#ifdef KMESSTEST
  ASSERT( directConnection_     == 0 );
  ASSERT( directConnectionPool_ != 0 );
#endif

  // Reset state variable.
  pendingConnectionInvitation_ = false;

  // Get active connection.
  directConnection_ = static_cast<MsnDirectConnection*>( directConnectionPool_->getActiveConnection() );
  if(KMESS_NULL(directConnection_)) return;

  // Inform the requested application it can start to initialize the connection
  emit connectionEstablished();
}



/**
 * @brief A message was received from the direct connection.
 *
 * The message will be verified and forwarded to the correct P2PApplication instance (using gotMessage()).
 * If it's a handshake message containing a Nonce, it returns the message to the application
 * which generated the Nonce. If no application was found, the connection will be dropped
 * to indicate the Nonce was invalid.
 *
 * @param  messageData  The received message data.
 */
01550 void ApplicationList::slotGotDirectConnectionMessage(const QByteArray &messageData)
{
#ifdef KMESSTEST
  ASSERT( directConnection_ != 0 );
#endif

  // Last check to know this is really a P2P message.
  if(messageData.size() < 48)
  {
    kdWarning() << "ApplicationList: Received an unknown message from the "
                << " direct-connection-link (message dump follows).\n" << messageData.data() << endl;
    return;
  }

  // Parse the message
  P2PMessage message(messageData);
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
  kdDebug() << "ApplicationList: Received data message from the direct connection link"
            << " (flags=0x" << QString::number(message.getFlags(), 16) << ")." << endl;
#endif


  // The nonce/handshake message doesn't have ack fields, so parse it here.
  if( message.isConnectionHandshake() )
  {
    P2PApplication *app = getApplicationByNonce( message.getNonce() );

    if( app == 0 )
    {
      // Invalid nonce! Drop connection
      kdWarning() << "P2PApplication: Contact sent invalid nonce or session was already terminated, closing direct connection." << endl;
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication: Closing connection, moving transfer to switchboard." << endl;
#endif
#ifdef KMESSTEST
      ASSERT( sender() != 0 );
      ASSERT( static_cast<const MsnDirectConnection*>(sender()) == directConnectionPool_->getActiveConnection() );
#endif

      // Close the connection.
      directConnection_ = 0;
      directConnectionPool_->getActiveConnection()->closeConnection();  // DirectConnectionPool deletes it.
      directConnectionPool_->deleteLater();
      directConnectionPool_ = 0;
    }
    else
    {
      // Nonce is valid.
      app->gotMessage( message );
    }
  }
  else
  {
    // Normal message
    // Deliver the message to the correct p2p application.
    gotMessage(message);
  }
}



/**
 * @brief An application was finished and requested removal.
 * @param  application  The application object instance.
 */
01615 void ApplicationList::slotTerminateApplication(Application *application)
{
  // Update the internal lists.
  if( application->qt_cast("MimeApplication") != 0 )
  {
    // It's a mime application
    if( ! mimeApplications_.removeRef( static_cast<MimeApplication*>(application) ) )
    {
      kdWarning() << "ApplicationList::slotTerminateApplication: Could not remove MimeApplication"
                  << " (type=" << application->className() << " contact=" << contactHandle_ << ")!" << endl;
    }
  }
  else if( application->qt_cast("P2PApplication") != 0 )
  {
    // Remove from collection
    if( ! p2pApplications_.removeRef( static_cast<P2PApplication*>(application) ) )
    {
      kdWarning() << "ApplicationList::slotTerminateApplication: Could not remove P2PApplication"
                  << " (type=" << application->className() << " contact=" << contactHandle_ << ")!" << endl;
    }
  }
  else
  {
    // Warn when there is a problem deleting the object.
    kdWarning() << "ApplicationList::slotTerminateApplication: Can't determine type of application "
                << " (type=" << application->className() << " contact=" << contactHandle_ << ")!" << endl;
  }


  // Check whether this application was scheduelled to be aborted by contactLeftChat()
  // When all applications aborted, signal so that the switchboard connection can be terminated.
  // This is using a QPtrList so the process is not disturbed by newly added applications or apps left active.
  if( ! abortingApplications_.isEmpty() )
  {
    if( abortingApplications_.removeRef(application) )
    {
      if( ! abortingApplications_.isEmpty() )
      {
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
        kdDebug() << "ApplicationList::slotTerminateApplication: Still " << abortingApplications_.count() << " other applications left to abort..." << endl;
#endif
      }
      else
      {
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
        kdDebug() << "ApplicationList::slotTerminateApplication: Signalling that applications of '" << contactHandle_ << "' have been aborted." << endl;
#endif
        emit applicationsAborted(contactHandle_);
      }
    }
    else
    {
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
      kdDebug() << "ApplicationList: Application was not part of the scheduelled applications to abort." << endl;
#endif
    }
  }


#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
  kdDebug() << "ApplicationList: Removing application." << endl;
#endif

  // Remove the application
  application->deleteLater();
}



#include "applicationlist.moc"

Generated by  Doxygen 1.6.0   Back to index