2201 lines
71 KiB
C++
2201 lines
71 KiB
C++
/***************************************************************************
|
|
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. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "applicationlist.h"
|
|
|
|
#include "../../contact/contactbase.h"
|
|
#include "../../contact/msnobject.h"
|
|
#include "../../currentaccount.h"
|
|
#include "../../kmessdebug.h"
|
|
#include "../extra/directconnectionpool.h"
|
|
#include "../extra/msndirectconnection.h"
|
|
#include "../chatmessage.h"
|
|
#include "../mimemessage.h"
|
|
#include "../msnswitchboardconnection.h"
|
|
#include "../p2pmessage.h"
|
|
#include "filetransfer.h"
|
|
#include "filetransferp2p.h"
|
|
#include "inktransferp2p.h"
|
|
#include "mimeapplication.h"
|
|
#include "msnobjecttransferp2p.h"
|
|
#include "p2papplication.h"
|
|
#include "unknownapplication.h"
|
|
|
|
#include <QRegExp>
|
|
|
|
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST
|
|
#define KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
#endif
|
|
|
|
|
|
#define CEIL( val ) ( (int) (val) + 1)
|
|
|
|
|
|
|
|
/**
|
|
* @brief The constructor
|
|
*
|
|
* Initialize the application list for the given contact.
|
|
* @param contactHandle The contact this application list is created for.
|
|
*/
|
|
ApplicationList::ApplicationList(const QString &contactHandle)
|
|
: QObject()
|
|
, addingConnections_(false)
|
|
, contactHandle_(contactHandle.toLower())
|
|
, directConnection_(0)
|
|
, directConnectionPool_(0)
|
|
, pauseApplications_(false)
|
|
, pendingConnectionInvitation_(false)
|
|
{
|
|
setObjectName("ApplicationList[" + contactHandle + "]");
|
|
|
|
// Configure timer
|
|
connect( &sbReadyWriteTester_, SIGNAL(timeout()), this, SLOT(slotSwitchboardTestReadyWrite()) );
|
|
sbReadyWriteTester_.setSingleShot( true );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief The destructor.
|
|
*
|
|
* This method cleans up the direct connection and connection pool.
|
|
* All MimeApplication and P2PApplication objects should be removed already.
|
|
* If an application object still exists, it will be deleted directly.
|
|
*/
|
|
ApplicationList::~ApplicationList()
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "Starting removal. [handle=" << contactHandle_ << "]";
|
|
#endif
|
|
|
|
// See if there are remaining MIME applications
|
|
if( ! mimeApplications_.isEmpty() )
|
|
{
|
|
kmWarning() << "still have " << mimeApplications_.count() <<
|
|
" active MimeApplication objects for " << contactHandle_ << "!";
|
|
|
|
// Remove all mime applications
|
|
qDeleteAll( mimeApplications_ );
|
|
mimeApplications_.clear();
|
|
}
|
|
|
|
// See if there are remaining P2P applications
|
|
if( ! p2pApplications_.isEmpty() )
|
|
{
|
|
kmWarning() << "still have " << p2pApplications_.count() <<
|
|
" active P2PApplication objects for " << contactHandle_ << "!";
|
|
|
|
// Remove all p2p applications
|
|
qDeleteAll( p2pApplications_ );
|
|
p2pApplications_.clear();
|
|
}
|
|
|
|
// Delete the connections
|
|
directConnection_ = 0; // deleted by connection pool
|
|
delete directConnectionPool_;
|
|
directConnectionPool_ = 0;
|
|
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "DESTROYED. [handle=" << contactHandle_ << "]";
|
|
#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).
|
|
*
|
|
* If applications need to be aborted, this method returns true.
|
|
* The applicationsAborted() signal will be fired when the aborting is completed.
|
|
*
|
|
* @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.
|
|
*/
|
|
bool ApplicationList::abortApplications(const MsnSwitchboardConnection *abortingConnection, bool userInitiated)
|
|
{
|
|
// Check if there is nothing to do
|
|
if( mimeApplications_.isEmpty() && p2pApplications_.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "No applications active.";
|
|
#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 QList<const MsnSwitchboardConnection*> switchboards = contact->getSwitchboardConnections();
|
|
foreach( const MsnSwitchboardConnection *connection, switchboards )
|
|
{
|
|
if( connection != abortingConnection )
|
|
{
|
|
if( connection->isExclusiveChatWithContact( contactHandle_ ) )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "Contact is still in another private chat, not terminating applications.";
|
|
#endif
|
|
return (! abortingApplications_.isEmpty()); // in case the function was called before
|
|
}
|
|
else
|
|
{
|
|
hasMultiChats = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Check if any multi chats are found.
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
if( ! hasMultiChats )
|
|
{
|
|
kmDebug() << "Contact left all chats, aborting applications.";
|
|
}
|
|
else
|
|
{
|
|
kmDebug() << "Contact left all private chats, aborting applications that don't support multi-chats.";
|
|
}
|
|
#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.
|
|
foreach( MimeApplication *app, mimeApplications_ )
|
|
{
|
|
if( ! abortingApplications_.isEmpty() && ! 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.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Then abort all p2p-based applications.
|
|
foreach( P2PApplication *app, p2pApplications_ )
|
|
{
|
|
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.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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
|
|
kmDebug() << "Got " << abortingApplications_.count() << " applications left to abort.";
|
|
#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.
|
|
*/
|
|
void ApplicationList::addApplication(MimeApplication *application)
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "connecting mime application signals.";
|
|
#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&) ));
|
|
|
|
// Inform listeners about the new application (notably ChatMaster)
|
|
emit newApplication( application );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @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.
|
|
*/
|
|
void ApplicationList::addApplication(P2PApplication *application)
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "connecting p2p application signals.";
|
|
#endif
|
|
|
|
p2pApplications_.append( application );
|
|
connectApplication( application );
|
|
|
|
// Inform listeners about the new application (notably ChatMaster)
|
|
emit newApplication( 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.
|
|
*/
|
|
bool ApplicationList::addConnection(const QString &ipAddress, const quint16 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.
|
|
kmWarning() << "Refusing connection attempt, a connection has already been made "
|
|
"(contact=" << contactHandle_ << " action=return).";
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "adding new connection attempt.";
|
|
#endif
|
|
|
|
// Avoid overwriting the current directConnection_ variable.
|
|
if( directConnection_ != 0 )
|
|
{
|
|
kmWarning() << "Refusing connection attempt, a connection has already been made "
|
|
"(contact=" << contactHandle_ << " action=return).";
|
|
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.
|
|
*/
|
|
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.
|
|
kmWarning() << "Refusing server connection attempt, a connection has already been made "
|
|
"(contact=" << contactHandle_ << " action=return).";
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "adding new server socket.";
|
|
#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.
|
|
*/
|
|
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.
|
|
*/
|
|
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.
|
|
*/
|
|
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.
|
|
*/
|
|
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:
|
|
kmWarning() << "MIME message can't be handled, "
|
|
<< "Application-GUID not found. (message dump follows)\n" << message.getFields();
|
|
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
|
|
kmDebug() << "Connecting MimeApplication signals";
|
|
#endif
|
|
|
|
// Auto-add application
|
|
addApplication(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.
|
|
*/
|
|
P2PApplication * ApplicationList::createApplication(const P2PMessage &message)
|
|
{
|
|
#ifdef KMESSTEST
|
|
KMESS_ASSERT( message.getSessionID() == 0 );
|
|
KMESS_ASSERT( message.getDataSize() > 0 );
|
|
#endif
|
|
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "Creating new P2PApplication instance to parse the P2P message.";
|
|
#endif
|
|
|
|
P2PApplication *app = 0;
|
|
|
|
// Extract the SLP data message
|
|
QString slpMessage( QString::fromUtf8( message.getData(), (int) 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.indexOf(":"), 20) ) );
|
|
kmWarning() << "P2P message can't be handled, detected SLP message but no INVITE. "
|
|
"P2PApplication already terminated? "
|
|
"(slp-preamble=" << preamble <<
|
|
" contact=" << contactHandle_ <<
|
|
// TODO: get session ID from SLP body if available, it's not set in the headers.
|
|
").";
|
|
return 0;
|
|
}
|
|
|
|
|
|
// Find the EUF-GUID in the data
|
|
QRegExp guidRE("EUF-GUID: (.+)\r\n");
|
|
guidRE.setMinimal(true);
|
|
int guidPos = guidRE.indexIn(slpMessage);
|
|
|
|
// If no EUF-GUID is found:
|
|
if( guidPos == -1 )
|
|
{
|
|
// Notify the user:
|
|
kmWarning() << "P2P message can't be handled, "
|
|
<< "EUF-GUID not found. (message dump follows)\n" << slpMessage;
|
|
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
|
|
kmDebug() << "Connecting P2PApplication signals";
|
|
#endif
|
|
|
|
// Auto-add application
|
|
addApplication(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.
|
|
*/
|
|
MimeApplication * ApplicationList::createApplicationByGuid(const QString &appGuid)
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "Creating MimeApplication for GUID" << appGuid;
|
|
#endif
|
|
|
|
if( appGuid == FileTransfer::getAppId() )
|
|
{
|
|
return new FileTransfer( contactHandle_ );
|
|
}
|
|
else
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "No MimeApplication exists for this GUID!";
|
|
#endif
|
|
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.
|
|
*/
|
|
P2PApplication * ApplicationList::createApplicationByEufGuid(const QString &eufGuid)
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "Creating P2PApplication for GUID" << eufGuid;
|
|
#endif
|
|
|
|
if( eufGuid == MsnObjectTransferP2P::getAppId() )
|
|
{
|
|
return new MsnObjectTransferP2P( this );
|
|
}
|
|
else if( eufGuid == FileTransferP2P::getAppId() )
|
|
{
|
|
return new FileTransferP2P( this );
|
|
}
|
|
else if( eufGuid == UnknownApplication::getGamesAppId()
|
|
|| eufGuid == UnknownApplication::getMeetingAppId()
|
|
|| eufGuid == UnknownApplication::getRemoteDesktopAppId()
|
|
|| eufGuid == UnknownApplication::getWebcamPullAppId()
|
|
|| eufGuid == UnknownApplication::getWebcamPushAppId()
|
|
|| eufGuid == UnknownApplication::getVoiceAppId() )
|
|
{
|
|
return new UnknownApplication( this, eufGuid );
|
|
}
|
|
else
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "No P2PApplication exists for this GUID!";
|
|
#endif
|
|
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.
|
|
*/
|
|
P2PApplication * ApplicationList::getApplicationByAckData(const P2PMessage &ackMessage) const
|
|
{
|
|
unsigned long ackUniqueID = ackMessage.getAckUniqueID();
|
|
foreach( P2PApplication *app, p2pApplications_ )
|
|
{
|
|
// The application may have multiple messages unacked, see whether the ACK matches.
|
|
if( app->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)
|
|
kmWarning() << "Got P2P message with matching ackSessionID and no ackUniqueID set "
|
|
<< "(delivering anyway, contact=" << contactHandle_ << ")!";
|
|
}
|
|
|
|
return app;
|
|
}
|
|
}
|
|
|
|
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.
|
|
*/
|
|
P2PApplication * ApplicationList::getApplicationByCallId(const QString &callID) const
|
|
{
|
|
foreach( P2PApplication *app, p2pApplications_ )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "Demultiplexing P2P message"
|
|
<< " (CallID " << app->getCallID() << " == " << callID << " ?)";
|
|
#endif
|
|
|
|
if( app->getCallID() == callID )
|
|
{
|
|
// Found it, return reference
|
|
return app;
|
|
}
|
|
}
|
|
|
|
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.
|
|
*/
|
|
Application * ApplicationList::getApplicationByCookie(const QString &cookie) const
|
|
{
|
|
// First see if there is a p2p application with the cookie.
|
|
foreach( P2PApplication *app, p2pApplications_ )
|
|
{
|
|
if( app->getCookie() == cookie )
|
|
{
|
|
// Found it, return reference.
|
|
return app;
|
|
}
|
|
}
|
|
|
|
// Also look for a mime application with the cookie.
|
|
foreach( MimeApplication *app, mimeApplications_ )
|
|
{
|
|
if ( app->getCookie() == cookie )
|
|
{
|
|
// Found it, return reference.
|
|
return app;
|
|
}
|
|
}
|
|
|
|
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.
|
|
*/
|
|
P2PApplication * ApplicationList::getApplicationByLastMessage(unsigned long messageID) const
|
|
{
|
|
foreach( P2PApplication *app, p2pApplications_ )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
unsigned long debugMsgId = app->getLastContactMessageID();
|
|
kmDebug() << "Demultiplexing P2P message"
|
|
<< " (fragment MessageID " << debugMsgId << " == " << messageID << " ?)";
|
|
#endif
|
|
|
|
if( app->getLastContactMessageID() == messageID )
|
|
{
|
|
// HACK: Not tested for "getLastContactAckMessageID() == ackMessageID" to support broken implementations.
|
|
|
|
// Found it, save reference
|
|
return app;
|
|
}
|
|
}
|
|
|
|
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.
|
|
*/
|
|
P2PApplication * ApplicationList::getApplicationByNonce(const QString &nonce) const
|
|
{
|
|
foreach( P2PApplication *app, p2pApplications_ )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "Demultiplexing P2P message"
|
|
<< " (Nonce " << app->getNonce() << " == " << nonce << " ?)";
|
|
#endif
|
|
|
|
if( app->getNonce() == nonce )
|
|
{
|
|
// Found it, return reference
|
|
return app;
|
|
}
|
|
}
|
|
|
|
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.
|
|
*/
|
|
P2PApplication * ApplicationList::getApplicationBySessionId(unsigned long sessionID) const
|
|
{
|
|
foreach( P2PApplication *app, p2pApplications_ )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "Demultiplexing P2P message"
|
|
<< " (SessionID " << app->getSessionID() << " == " << sessionID << " ?)";
|
|
#endif
|
|
if( app->getSessionID() == sessionID )
|
|
{
|
|
// Found the session
|
|
return app;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief Return the handle of the contact this class manages.
|
|
* @returns The contact handle.
|
|
*/
|
|
const QString & ApplicationList::getContactHandle() const
|
|
{
|
|
return contactHandle_;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief Return the direct connection, if available.
|
|
* @returns The direct connection object.
|
|
*/
|
|
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.
|
|
*/
|
|
void ApplicationList::gotMessage(const MimeMessage &message)
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "Parsing Mime appliation message.";
|
|
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 )
|
|
{
|
|
kmWarning() << "Unable to handle MIME message, "
|
|
<< "Invitation-Cookie not found (contact=" << contactHandle_ << ").";
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Last check before casting the object.
|
|
if( ! app->inherits("MimeApplication") )
|
|
{
|
|
kmWarning() << "found application is not a mime application!";
|
|
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.
|
|
*/
|
|
void ApplicationList::gotMessage(const P2PMessage &message)
|
|
{
|
|
// Parse the message
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "Parsing received P2P message:"
|
|
<< " sid=" << message.getSessionID()
|
|
<< " mid=" << message.getMessageID()
|
|
<< " ackSid=" << message.getAckSessionID()
|
|
<< " ackUid=" << message.getAckUniqueID()
|
|
<< " flags=0x" << QString::number(message.getFlags(), 16)
|
|
<< " size=" << message.getDataSize()
|
|
<< " offset=" << message.getDataOffset() // also useful for debugging!
|
|
<< " total=" << message.getTotalSize()
|
|
<< " no.sessions=" << p2pApplications_.count();
|
|
#endif
|
|
|
|
P2PApplication *p2pApplication = 0;
|
|
unsigned long sessionID = message.getSessionID();
|
|
QString slpMessage;
|
|
|
|
|
|
// 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 );
|
|
|
|
// If there is no session for this ID, there are still some special cases.
|
|
if(p2pApplication == 0)
|
|
{
|
|
switch( sessionID )
|
|
{
|
|
case InkTransferP2P::SESSION_ID:
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "Found reserved session ID for Ink transfers, creating InkTransferP2P instance.";
|
|
#endif
|
|
// Ink messages have a fixed session ID.
|
|
p2pApplication = new InkTransferP2P( this );
|
|
addApplication( p2pApplication );
|
|
break;
|
|
|
|
// MSN 6 also used this system for Winks.
|
|
|
|
default:
|
|
// Normal P2P session -> notify the user if the message was invalid. (send error-response later)
|
|
kmWarning() << "Unable to handle P2P message, Session-ID " << sessionID << " not found.";
|
|
}
|
|
}
|
|
}
|
|
else if( message.getFlags() != 0
|
|
&& message.getFlags() != P2PMessage::MSN_FLAG_OBJECT_DATA // WLM2009 uses 0x1000000 instead of 0x00.
|
|
&& message.getDataSize() == 0 )
|
|
{
|
|
// 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.
|
|
if( message.isConnectionHandshake() )
|
|
{
|
|
kmWarning() << "Received a direct connection handshake message at the switchboard "
|
|
"(contact=" << contactHandle_ << " action=return).";
|
|
return;
|
|
}
|
|
|
|
// 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.
|
|
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.
|
|
kmWarning() << "P2P message can't be handled,"
|
|
" no match for ackSessionID " << message.getAckSessionID() <<
|
|
" or ackUniqueID " << message.getAckUniqueID() << " found.";
|
|
}
|
|
}
|
|
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)
|
|
kmWarning() << "SwitchBoard P2P message has no ACK fields set at all, assuming base identifier message!";
|
|
}
|
|
else if( message.isSlpData() )
|
|
{
|
|
// 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
|
|
// Initialize Call and session ID regexps
|
|
QRegExp callRE("Call-ID: (.+)\r\n"); // match everything because MSN6 accepts anything.
|
|
callRE.setMinimal(true);
|
|
|
|
// Find the call ID
|
|
// Note this assumes that a Call-ID will be found in the first fragment.
|
|
slpMessage = QString::fromUtf8( message.getData(), (int)message.getDataSize() );
|
|
int callPos = callRE.indexIn( slpMessage );
|
|
|
|
if( callPos == -1 )
|
|
{
|
|
kmWarning() << "Unable to handle P2P message, CallID field not found.";
|
|
}
|
|
else
|
|
{
|
|
QString callID( callRE.cap(1) );
|
|
|
|
// See if it's a null-guid.
|
|
// This happens for the out-of-band SLP "application/x-msnmsgr-transdestaddrupdate" and "application/x-msnmsgr-transudpswitch" messages.
|
|
if( callID == "{00000000-0000-0000-0000-000000000000}" )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "Found null Call-ID, using Session-ID to deliver the message.";
|
|
#endif
|
|
|
|
// HACK use the SessionID from the SLP body to route the message.
|
|
// This is a bit like reading the "HTTP Host" field to route an IP packet, but MSNP2P leaves us with no other choice.
|
|
QRegExp sessionRE("SessionID: ([0-9]+)\r\n");
|
|
sessionRE.setMinimal( true );
|
|
long sessionPos = sessionRE.indexIn( slpMessage );
|
|
if( sessionPos == -1 )
|
|
{
|
|
kmWarning() << "Unable to handle P2P message, found null-guid CallID but no SessionID instead.";
|
|
}
|
|
else
|
|
{
|
|
unsigned long sessionID = sessionRE.cap(1).toLong();
|
|
if( sessionID != 0 )
|
|
{
|
|
p2pApplication = getApplicationBySessionId( sessionID );
|
|
}
|
|
else
|
|
{
|
|
// Also observed in some cases.
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "Also found zero Session-ID, will create an empty application instance to handle the message.";
|
|
#endif
|
|
|
|
// Create it here so there won't be any message dumps.
|
|
// We already know what those look like.
|
|
p2pApplication = new P2PApplication(this);
|
|
p2pApplication->setMode( Application::APP_MODE_ERROR_HANDLER );
|
|
addApplication( p2pApplication );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Normal call-id, normal operation.
|
|
// first try to find an existing session searching by Call ID. (for 200 OK and BYE messages)
|
|
p2pApplication = getApplicationByCallId( callRE.cap(1) );
|
|
|
|
// No existing applications have been found
|
|
// Initialize an application class to handle the invitation.
|
|
if( p2pApplication == 0 )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "No existing P2P session found for the MSNSLP message, "
|
|
<< "must be a new session invitation.";
|
|
#endif
|
|
|
|
p2pApplication = createApplication( message );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Didn't reconize the flag type.
|
|
kmWarning() << "P2P message can't be handled, unknown flag type found.";
|
|
if( message.getFlags() == 0 )
|
|
{
|
|
// HACK: added for broken clients who sent an empty message with a zero flag (Bot2K3 v4.1)
|
|
}
|
|
}
|
|
|
|
|
|
// If the application was found, deliver the message
|
|
if( p2pApplication != 0 )
|
|
{
|
|
// Deliver the message
|
|
p2pApplication->gotMessage( message );
|
|
}
|
|
else
|
|
{
|
|
// Force logging all unknown messages, to help improving the protocol knowledge
|
|
// This is not hidden, because this information is essential for debugging user problems.
|
|
kmWarning() << "A message could not be delivered to an existing P2P application "
|
|
"(see previous warnings for details,"
|
|
" sid=" << message.getSessionID() <<
|
|
" flags=0x" << QString::number(message.getFlags(), 16) <<
|
|
" size=" << message.getDataSize() <<
|
|
" contact=" << contactHandle_ << ").";
|
|
|
|
// Also dump the SLP message if it's there.
|
|
if( ! slpMessage.isEmpty() && message.getSessionID() == 0 && message.getDataSize() > 0 )
|
|
{
|
|
kmDebug() << "Dumping SLP message payload:\n" << slpMessage << "\n";
|
|
}
|
|
|
|
// 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.getFlags() != P2PMessage::MSN_FLAG_OBJECT_DATA // WLM2009 uses 0x1000000 instead of 0x00.
|
|
&& message.getDataSize() == 0 )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "Not sending a P2P Error response for error message.";
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
kmDebug() << "Creating temporary P2PApplication instance to send error response.";
|
|
|
|
// 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 );
|
|
addApplication( p2pApplication );
|
|
p2pApplication->gotMessage( message );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Return whether the direct connection is connected and authorized.
|
|
* If a direct connection is not authorized, it's not able to send the data transfer.
|
|
*/
|
|
bool ApplicationList::hasAuthorizedDirectConnection() const
|
|
{
|
|
return ( directConnection_ != 0 && directConnection_->isAuthorized() && directConnection_->isConnected() );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief Return whether there are applications which need to send data.
|
|
*/
|
|
bool ApplicationList::hasDataSendingApplications() const
|
|
{
|
|
return ! dataSendingApplications_.isEmpty();
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @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.
|
|
*
|
|
* Note that the connection might not be authorized. Such connection
|
|
* can only be used to send the direct connection handshake.
|
|
* use hasAuthorizedDirectConnection() if the connection needs to
|
|
* be able to transfer data too.
|
|
*
|
|
* @returns The direct connection if it's available *and* connected.
|
|
*/
|
|
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.
|
|
*/
|
|
bool ApplicationList::hasMsnObjectTransfer( const MsnObject &msnObject ) const
|
|
{
|
|
foreach( P2PApplication *app, p2pApplications_ )
|
|
{
|
|
MsnObjectTransferP2P *transferApp = qobject_cast<MsnObjectTransferP2P*>( app );
|
|
if( transferApp != 0 )
|
|
{
|
|
if( transferApp->getMsnObject().getDataHash() == msnObject.getDataHash() )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief Return whether the transfer invitation has been sent.
|
|
* @return Whether there is a pending connection invitation.
|
|
*/
|
|
bool ApplicationList::hasPendingConnectionInvitation() const
|
|
{
|
|
return pendingConnectionInvitation_;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief Return whether there are pending connection attempts.
|
|
* @returns Returns true when there are pending connection attempts.
|
|
*/
|
|
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.
|
|
*/
|
|
void ApplicationList::initializeDirectConnectionPool()
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "Initializing direct connection pool.";
|
|
#endif
|
|
#ifdef KMESSTEST
|
|
KMESS_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 Return whether the application list does not have any applications.
|
|
* Note that there could also be a direct connection.
|
|
* @return Whether there are no MimeApplication or P2PAppliation objects left.
|
|
*/
|
|
bool ApplicationList::isEmpty() const
|
|
{
|
|
return ( mimeApplications_.isEmpty() && p2pApplications_.isEmpty() );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @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.
|
|
*/
|
|
void ApplicationList::pauseApplications()
|
|
{
|
|
// This only affects P2Papplications now, mime applications don't send many messages.
|
|
pauseApplications_ = true; // setting this causes slotSwitchboardTestReadyWrite() to halt.
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief Register an application which is sending a file.
|
|
*
|
|
* This causes the application to receive requests to send new parts,
|
|
* so the file transfer speed remains optimal for low and high speed network links.
|
|
*/
|
|
void ApplicationList::registerDataSendingApplication( P2PApplicationBase *application )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "adding application to the list of active outgoing transfers.";
|
|
#endif
|
|
|
|
// See if it's the first app.
|
|
bool isFirstApp = ( dataSendingApplications_.isEmpty() );
|
|
|
|
// Add to list.
|
|
dataSendingApplications_.append( application );
|
|
|
|
|
|
// Make sure the application is polled frequently for the next data packets.
|
|
// For the switchboard: locally, frequent calls are made to slotSwitchboardTestReadyWrite().
|
|
// For the direct connection: each time the socket is ready, slotConnectionReadyWrite() is called.
|
|
if( ! hasAuthorizedDirectConnection() )
|
|
{
|
|
if( ! sbReadyWriteTester_.isActive() )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "no direct connection available, starting switchboard write handler instead.";
|
|
#endif
|
|
|
|
// Start the write handler for the switchboard.
|
|
sbReadyWriteTester_.start( 10 );
|
|
}
|
|
else
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "no direct connection available, but switchboard write handler is already active.";
|
|
#endif
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Attach the the write handler of the direct connection.
|
|
// This is only needed once for the first application.
|
|
// Still check if the handler is connected already, avoid race conditions with slotSwitchboardTestReadyWrite()
|
|
if( ! directConnection_->isWriteHandlerConnected() )
|
|
{
|
|
if( isFirstApp )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "first application added, "
|
|
<< "starting connection write handler.";
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
kmWarning() << "there are already applications, but no write handler connected yet.";
|
|
}
|
|
|
|
// Connect the write handler.
|
|
directConnection_->connectWriteHandler( this, SLOT(slotConnectionReadyWrite()) );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @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.
|
|
*/
|
|
void ApplicationList::resumeApplications(bool /*isPrivateChat*/)
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "resuming applications.";
|
|
#endif
|
|
#ifdef KMESSTEST
|
|
KMESS_ASSERT( pauseApplications_ );
|
|
#endif
|
|
|
|
// Allow slotSwitchboardTestReadyWrite() again.
|
|
pauseApplications_ = false;
|
|
|
|
// See if there are still applications that need to write data.
|
|
if( dataSendingApplications_.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "no action taken because no appliations need to transfer data.";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// Restart the handler
|
|
if( ! hasAuthorizedDirectConnection() )
|
|
{
|
|
if( sbReadyWriteTester_.isActive() )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "switchboard write handler is already active, "
|
|
<< "no special action needed.";
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "restarting switchboard write handler.";
|
|
#endif
|
|
|
|
// No direct connection (as expected), restart switchboard write handler.
|
|
sbReadyWriteTester_.start( 10 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// A direct connection is available.
|
|
// See if the direct connection has a write handler which triggers slotConnectionReadyWrite()
|
|
if( directConnection_->isWriteHandlerConnected() )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "direct connection is available with write handler, "
|
|
<< "no special action needed.";
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "direct connection is available, "
|
|
<< "reconnecting write handler..";
|
|
#endif
|
|
|
|
// Connect the write handler to resume!
|
|
directConnection_->connectWriteHandler( this, SLOT(slotConnectionReadyWrite()) );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief Send a message for a MIME application.
|
|
* @param source The MimeApplication responsable for sending the message.
|
|
* @param messageData The message body.
|
|
*/
|
|
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.
|
|
KMESS_ASSERT( messageBody[0].isLetter() );
|
|
KMESS_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.
|
|
* It's possible this connection is congested, and would block. In that case
|
|
* false is returned.
|
|
*
|
|
* If a direct connection is not available, the message will be delivered
|
|
* to a MsnSwitchboardConnection instance by the ChatMaster (connected to the putMsg() signal).
|
|
* Before the P2PMessage can be handled by a switchboard it's wrapped in a MimeMessage,
|
|
* as the switchboard sends all messages in 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.
|
|
* @return Whether the message can be sent. If the socket would block, false is returned.
|
|
* This only occurs for direct connections connections.
|
|
*/
|
|
bool ApplicationList::sendMessage(const P2PApplicationBase *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.
|
|
if( directConnection_ != 0 )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "there is a direct connection object, "
|
|
"test if the socket is still connected and not timed out.";
|
|
#endif
|
|
|
|
if( ! KMESS_NULL(directConnectionPool_) )
|
|
{
|
|
// verify connection, deletes closed connection.
|
|
if( ! directConnectionPool_->verifyActiveConnection() )
|
|
{
|
|
// not valid, also unset here.
|
|
directConnection_ = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Deliver over a direct connection or switchboard session.
|
|
// Now also test if the connection is authorized, before sending data over it.
|
|
if( directConnection_ != 0 && directConnection_->isAuthorized() )
|
|
{
|
|
// Deliver the message over the direct connection.
|
|
// The footer will not be appended to the message.
|
|
QByteArray p2pMessage = header + messageData;
|
|
bool success = directConnection_->sendMessage( p2pMessage );
|
|
if( success )
|
|
{
|
|
return true;
|
|
}
|
|
else if( directConnection_->hasTemporaryWriteError() )
|
|
{
|
|
kmWarning() << "message could not be fully sent, "
|
|
"but the remaining part is in the send buffer.";
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
#ifdef KMESSTEST
|
|
KMESS_ASSERT( directConnection_->hasLastWriteFailed() );
|
|
#endif
|
|
|
|
kmWarning() << "P2P message could not be sent over the direct connection, "
|
|
"using switchboard instead (contact=" << contactHandle_ << ").";
|
|
|
|
// NOTE: The direct connection will still appear as connected.
|
|
// Next events are not processed yet. That's why there is a check for hasLastWriteFailed() below.
|
|
}
|
|
|
|
// NOTE: This method does not return false anymore on socket write errors.
|
|
// Instead, it relies on the DirectConnectionBase class to send the remaining data if needed.
|
|
}
|
|
|
|
// 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()) );
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @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.
|
|
*/
|
|
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 an application is adding connections, don't send "all failed" signal.
|
|
*/
|
|
void ApplicationList::setAddingConnections(bool state)
|
|
{
|
|
addingConnections_ = state;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @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.
|
|
*/
|
|
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
|
|
kmDebug() << "Pending connection invitation was aborted by P2PApplication, "
|
|
<< "informing application no direct connection is possible.";
|
|
#endif
|
|
|
|
emit connectionFailed();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief The direct connection is authorized.
|
|
*
|
|
* At this point, the direct connection can be re-used by other P2P applications.
|
|
*/
|
|
void ApplicationList::slotActiveConnectionAuthorized()
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "Direct connection with '" << contactHandle_ << "' authorized.";
|
|
#endif
|
|
#ifdef KMESSTEST
|
|
KMESS_ASSERT( directConnection_ != 0 );
|
|
#endif
|
|
|
|
// Inform *all* awaiting applications they can start to use the connection.
|
|
// Each application will start their data transfer now by calling registerDataSendingApplication()
|
|
emit connectionAuthorized();
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief The direct connection was closed.
|
|
*
|
|
* It resets the internal state and fires a connectionFailed() or connectionClosed() signal to other classes.
|
|
*/
|
|
void ApplicationList::slotActiveConnectionClosed()
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "The direct connection with '" << contactHandle_ << "' was closed.";
|
|
#endif
|
|
|
|
// Reset state variable.
|
|
pendingConnectionInvitation_ = false;
|
|
|
|
// Detect whether the connection was already set here.
|
|
bool hadConnectionBefore = ( directConnection_ != 0 );
|
|
|
|
// Reset the pointers first.
|
|
// No deletion needed, the connection 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;
|
|
}
|
|
|
|
// Notify the listeners
|
|
if( ! hadConnectionBefore )
|
|
{
|
|
// Active connection closed, but no pointer known.
|
|
// It must be reset in slotGotDirectConnectionMessage() when the nonce was invalid.
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "The current connection was closed prematurely, "
|
|
<< "signaling instead that the connection failed.";
|
|
#endif
|
|
|
|
// Notify the connection failed.
|
|
// Applications start their transfer over the switchboard.
|
|
emit connectionFailed();
|
|
}
|
|
else
|
|
{
|
|
// Also forward to our listeners.
|
|
emit connectionClosed();
|
|
|
|
// If there are still data sending applications, resume transfer over the switchboard.
|
|
if( ! dataSendingApplications_.isEmpty() )
|
|
{
|
|
// Build a list of sessions for debugging
|
|
QString activeSessions;
|
|
foreach( P2PApplicationBase *app, dataSendingApplications_ )
|
|
{
|
|
if( ! activeSessions.isEmpty() )
|
|
{
|
|
activeSessions += ",";
|
|
}
|
|
activeSessions += QString::number( app->getSessionID() );
|
|
}
|
|
|
|
// Give users some insight why transfer becomes slow.
|
|
kmWarning() << "The direct connection closed "
|
|
"while there are active file transfers over the connection. "
|
|
"Switching sessions to slow indirect transfer "
|
|
"(sessions=" << activeSessions <<
|
|
" contact=" << contactHandle_ << ").";
|
|
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "direct connection closed, "
|
|
<< "starting switchboard write handler to continue transfers.";
|
|
#endif
|
|
|
|
// No direct connection (as expected), restart switchboard write handler.
|
|
sbReadyWriteTester_.start( 10 );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief All direct connection attempts failed.
|
|
*
|
|
* This resets the internal state and fires the connectionFailed() signal.
|
|
*/
|
|
void ApplicationList::slotAllConnectionsFailed()
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "The direct connection with '" << contactHandle_ << "' could not be made.";
|
|
#endif
|
|
#ifdef KMESSTEST
|
|
KMESS_ASSERT( directConnectionPool_ != 0 );
|
|
KMESS_ASSERT( ! directConnectionPool_->hasActiveConnection() );
|
|
KMESS_ASSERT( ! directConnectionPool_->hasPendingConnections() );
|
|
#endif
|
|
|
|
// It's possible a connection fails directly before all addConnection() calls are made.
|
|
// Don't notify applications to revert the transfer.
|
|
// This should also avoid hangs at the "connect()" call.
|
|
if( directConnection_ == 0 && addingConnections_ )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "No connections left, but still adding connections. Ignoring event.";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
if( directConnection_ == 0 )
|
|
{
|
|
kmDebug() << "No remaining direct connections left";
|
|
}
|
|
else
|
|
{
|
|
kmDebug() << "The current connection was closed prematurely";
|
|
}
|
|
#endif
|
|
|
|
// Reset state variable.
|
|
pendingConnectionInvitation_ = false;
|
|
directConnection_ = 0; // somehow it could be != 0 with timeout events.
|
|
|
|
// If the pool was still active, it can be closed now.
|
|
if( directConnectionPool_ != 0 )
|
|
{
|
|
directConnectionPool_->deleteLater();
|
|
directConnectionPool_ = 0;
|
|
}
|
|
|
|
// Also forward to our listeners.
|
|
// This will cause the application to initiate the transfer.
|
|
// The messages will be sent over the switchboard instead as fallback.
|
|
emit connectionFailed();
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief The direct connection is established.
|
|
*
|
|
* This sets the internal state and fires a connectionEstablished() signal.
|
|
*/
|
|
void ApplicationList::slotConnectionEstablished()
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "Direct connection with '" << contactHandle_ << "' established.";
|
|
#endif
|
|
#ifdef KMESSTEST
|
|
KMESS_ASSERT( directConnection_ == 0 );
|
|
KMESS_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.
|
|
// This application will authenticate the connection itself,
|
|
// and slotActiveConnectionAuthorized() will be called afterwards.
|
|
// If the authentication fails, slotAllConnectionsFailed() will be called instead.
|
|
emit connectionEstablished();
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief The direct connection is ready to send more data.
|
|
*/
|
|
void ApplicationList::slotConnectionReadyWrite()
|
|
{
|
|
if( KMESS_NULL(directConnection_) ) return;
|
|
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "The direct connection is ready to send more data, "
|
|
<< "notifying applications.";
|
|
#endif
|
|
|
|
// Disconnect the event handler if there are no longer applications that need to transfer data.
|
|
if( dataSendingApplications_.isEmpty() || p2pApplications_.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "no active outgoing transfers left, "
|
|
<< "disconnecting write handler.";
|
|
#endif
|
|
|
|
directConnection_->disconnectWriteHandler( this, SLOT(slotConnectionReadyWrite()) );
|
|
return;
|
|
}
|
|
|
|
// Determine the number of preferred fragments to send.
|
|
// By default 3 for a direct connection, unless there are more parallel transfers.
|
|
int preferredFragments = CEIL( qMin( 3, 4 / dataSendingApplications_.count() ) );
|
|
|
|
// Start notifying.
|
|
bool writeSuccess = true;
|
|
foreach( P2PApplicationBase *app, dataSendingApplications_ )
|
|
{
|
|
// Tell the application to send the next file parts.
|
|
writeSuccess = app->sendNextDataParts( preferredFragments );
|
|
|
|
// Avoid freezes, don't call the app again if will fail every time.
|
|
if( ! writeSuccess )
|
|
{
|
|
unregisterDataSendingApplication( app );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @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.
|
|
*/
|
|
void ApplicationList::slotGotDirectConnectionMessage(const QByteArray &messageData)
|
|
{
|
|
#ifdef KMESSTEST
|
|
KMESS_ASSERT( directConnection_ != 0 );
|
|
#endif
|
|
|
|
// Last check to know this is really a P2P message.
|
|
if(messageData.size() < 48)
|
|
{
|
|
kmWarning() << "Received an unknown message from the "
|
|
<< " direct-connection-link (message dump follows).\n" << messageData.data();
|
|
return;
|
|
}
|
|
|
|
// Parse the message
|
|
P2PMessage message(messageData);
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "Received data message from the direct connection link"
|
|
<< " (flags=0x" << QString::number(message.getFlags(), 16) << ").";
|
|
#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
|
|
kmWarning() << "Contact sent invalid nonce or session was already terminated, "
|
|
<< "closing direct connection.";
|
|
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
|
|
kmDebug() << "Closing connection, moving transfer to switchboard.";
|
|
#endif
|
|
#ifdef KMESSTEST
|
|
KMESS_ASSERT( sender() != 0 );
|
|
KMESS_ASSERT( static_cast<const MsnDirectConnection*>(sender()) == directConnectionPool_->getActiveConnection() );
|
|
#endif
|
|
|
|
// Close the connection.
|
|
directConnection_ = 0; // used for slotConnectionClosed()
|
|
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 The switchboard is ready to send more data.
|
|
*
|
|
* This method works very much like slotConnectionReadyWrite(),
|
|
* except the source of the signal differs.
|
|
*/
|
|
void ApplicationList::slotSwitchboardTestReadyWrite()
|
|
{
|
|
// Test if the switchboard requested a pause,
|
|
// meaning it's no longer ready.
|
|
if( pauseApplications_ )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "switchboard requested pause, "
|
|
<< "postponing action.";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// Disconnect the event handler if there are no longer applications that need to transfer data.
|
|
if( dataSendingApplications_.isEmpty() || p2pApplications_.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "no active outgoing transfers left, "
|
|
<< "disconnecting write handler.";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "a switchboard is ready to send more data, "
|
|
<< "notifying applications.";
|
|
#endif
|
|
|
|
// Start notifying.
|
|
bool writeSuccess = true;
|
|
foreach( P2PApplicationBase *app, dataSendingApplications_ )
|
|
{
|
|
// Tell the application to send the next file parts.
|
|
writeSuccess = app->sendNextDataParts( 1 );
|
|
|
|
// Avoid freezes, don't call the app again if will fail every time.
|
|
if( ! writeSuccess )
|
|
{
|
|
unregisterDataSendingApplication( app );
|
|
}
|
|
}
|
|
|
|
// Find out if the applications are done now.
|
|
if( dataSendingApplications_.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "all applications are done.";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// See if we can switch to a Direct connection instead.
|
|
//
|
|
// The "last write failed" check is required, because isConnected() still returns true,
|
|
// even if writeBlock() failed with a "broken pipe" error, and used the SB instead. The stack becomes:
|
|
// this->slotSwitchboardTestReadyWrite() -> p2papp->sendNextDataParts() -> this->sendMessage() -> dc->writeBlock()
|
|
//
|
|
// Also look out for direct connections which did have a temporary write error,
|
|
// but have the remaining message buffered to send later.
|
|
bool switchToDc = ( hasAuthorizedDirectConnection()
|
|
&& ( ! directConnection_->hasLastWriteFailed() || directConnection_->hasTemporaryWriteError() ) );
|
|
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
if( hasAuthorizedDirectConnection() )
|
|
{
|
|
if( directConnection_->hasLastWriteFailed() )
|
|
{
|
|
kmDebug() << "A direct connection became available, "
|
|
"but had a error while writing. Will continue to use the switchboard.";
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Applications are not done with the transfer yet.
|
|
// Find out how to make sure sendNextDataParts() is called again.
|
|
if( ! switchToDc )
|
|
{
|
|
// Continue with normal behavour
|
|
// Currently this method triggers itself,
|
|
// until the switchboards are busy and "pausedApplications_" is set.
|
|
if( ! pauseApplications_ )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "scheduling next call.";
|
|
#endif
|
|
|
|
// Instead of receiving a signal from the switchboard,
|
|
// fake one right now. This is currently good enough.
|
|
sbReadyWriteTester_.start( 200 );
|
|
}
|
|
else
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "switchboard requested to pause the transfer, "
|
|
<< "postponing next action.";
|
|
#endif
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "switching to direct connection transfer.";
|
|
#endif
|
|
|
|
// There appears to be a direct connection!
|
|
// It's no longer needed to transfer the data over the switchboard
|
|
// so find out if the write handler is not connected yet.
|
|
if( ! directConnection_->isWriteHandlerConnected() )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "no write handler connected yet, "
|
|
<< "connecting write handler to direct connection.";
|
|
#endif
|
|
directConnection_->connectWriteHandler( this, SLOT(slotConnectionReadyWrite()) );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief An application was finished and requested removal.
|
|
* @param application The application object instance.
|
|
*/
|
|
void ApplicationList::slotTerminateApplication(Application *application)
|
|
{
|
|
MimeApplication *mimeApp = qobject_cast<MimeApplication*>( application );
|
|
P2PApplicationBase *p2pApp = qobject_cast<P2PApplicationBase*>( application );
|
|
|
|
// Update the internal lists.
|
|
if( mimeApp != 0 )
|
|
{
|
|
// It's a mime application
|
|
if( mimeApplications_.removeAll( mimeApp ) == 0 )
|
|
{
|
|
kmWarning() << "Could not remove MimeApplication"
|
|
<< " (type=" << mimeApp->metaObject()->className() << " contact=" << contactHandle_ << ")!";
|
|
}
|
|
}
|
|
else if( p2pApp != 0 )
|
|
{
|
|
// Remove from data sending apps too!
|
|
// Use the same unregister function so the write handler will also be disconnected.
|
|
if( dataSendingApplications_.contains( p2pApp ) )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "application is terminating while sending data, unregistering as data sending application first.";
|
|
#endif
|
|
|
|
unregisterDataSendingApplication( p2pApp );
|
|
}
|
|
|
|
// Remove from collection
|
|
if( p2pApplications_.removeAll( static_cast<P2PApplication*>( p2pApp ) ) == 0 )
|
|
{
|
|
kmWarning() << "Could not remove P2PApplication"
|
|
" (type=" << p2pApp->metaObject()->className() << " contact=" << contactHandle_ << ")!";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Warn when there is a problem deleting the object.
|
|
kmWarning() << "Can't determine type of application "
|
|
" (type=" << application->metaObject()->className() << " contact=" << contactHandle_ << ")!";
|
|
}
|
|
|
|
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "Removing application.";
|
|
#endif
|
|
|
|
// Remove the application
|
|
application->deleteLater();
|
|
|
|
|
|
// 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_.removeAll( application ) )
|
|
{
|
|
if( ! abortingApplications_.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "Still " << abortingApplications_.count() << " other applications left to abort...";
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "Signalling that applications of '" << contactHandle_ << "' have been aborted.";
|
|
#endif
|
|
|
|
// This may cause listeners to delete this object..
|
|
emit applicationsAborted(contactHandle_);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "Application was not part of the scheduelled applications to abort.";
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Remove the registration of an application which sends a file.
|
|
*/
|
|
void ApplicationList::unregisterDataSendingApplication( P2PApplicationBase *application )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "removing application from the list of active outgoing transfers.";
|
|
#endif
|
|
|
|
bool found = dataSendingApplications_.removeAll( application ) > 0;
|
|
if( ! found )
|
|
{
|
|
kmWarning() << "application not found in list.";
|
|
}
|
|
|
|
// Stop write handler is all applications stopped sending a file.
|
|
if( dataSendingApplications_.isEmpty() && directConnection_ != 0 )
|
|
{
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
kmDebug() << "no applications remaining, disabling connection write handler.";
|
|
#endif
|
|
|
|
directConnection_->disconnectWriteHandler( this, SLOT(slotConnectionReadyWrite()) );
|
|
}
|
|
#ifdef KMESSDEBUG_APPLICATIONLIST_GENERAL
|
|
else
|
|
{
|
|
// Debug why the write handler was not disabled yet.
|
|
if( directConnection_ != 0 )
|
|
{
|
|
kmDebug() << "connection write handler not disabled, there are still " << dataSendingApplications_.count() << " applications sending file data.";
|
|
}
|
|
else
|
|
{
|
|
kmDebug() << "connection write handler not disabled, no direct connection active already.";
|
|
}
|
|
}
|
|
#endif
|
|
}
|