1911 lines
58 KiB
C++
1911 lines
58 KiB
C++
/***************************************************************************
|
|
chatmaster.cpp - description
|
|
-------------------
|
|
begin : Sat Jan 18 2003
|
|
copyright : (C) 2003 by Mike K. Bennett
|
|
email : mkb137b@hotmail.com
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "chatmaster.h"
|
|
|
|
#include "../contact/contact.h"
|
|
#include "../contact/contactextension.h"
|
|
#include "../contact/contactlist.h"
|
|
#include "../contact/msnobject.h"
|
|
#include "../network/applications/applicationlist.h"
|
|
#include "../network/applications/mimeapplication.h"
|
|
#include "../network/applications/msnobjecttransferp2p.h"
|
|
#include "../network/applications/p2papplication.h"
|
|
#include "../network/applications/filetransfer.h"
|
|
#include "../network/applications/filetransferp2p.h"
|
|
#include "../network/applications/gnomemeeting.h"
|
|
#include "../network/applications/inktransferp2p.h"
|
|
#include "../network/chatmessage.h"
|
|
#include "../network/msnswitchboardconnection.h"
|
|
#include "../currentaccount.h"
|
|
#include "../kmessapplication.h"
|
|
#include "../kmessdebug.h"
|
|
#include "../utils/kmessconfig.h"
|
|
#include "chat.h"
|
|
#include "chatwindow.h"
|
|
|
|
#include <QFile>
|
|
#include <QImageReader>
|
|
|
|
#include <KWindowSystem>
|
|
|
|
|
|
|
|
// The constructor
|
|
ChatMaster::ChatMaster(QObject *parent)
|
|
: QObject(parent),
|
|
initialized_(false)
|
|
{
|
|
setObjectName("ChatMaster");
|
|
}
|
|
|
|
|
|
|
|
// The destructor
|
|
ChatMaster::~ChatMaster()
|
|
{
|
|
disconnected();
|
|
|
|
// Delete all open windows
|
|
qDeleteAll( chatWindows_ );
|
|
chatWindows_.clear();
|
|
|
|
// Cleanup pending messages
|
|
qDeleteAll( pendingMimeMessages_ );
|
|
pendingMimeMessages_.clear();
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "DESTROYED.";
|
|
#endif
|
|
}
|
|
|
|
|
|
// The connection has been established
|
|
void ChatMaster::connected()
|
|
{
|
|
// There are no chats to reactivate
|
|
if( chats_.isEmpty() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Trying reactivation of" << chats_.count() << "open chats.";
|
|
#endif
|
|
|
|
QList<ChatWindow*> windowsList;
|
|
|
|
// Go through all the chats and reactivate them
|
|
foreach( Chat *chat, chats_ )
|
|
{
|
|
QStringList participants = chat->getParticipants();
|
|
|
|
// Do not re-enable group chats, as they need a new invitation to work again
|
|
if( participants.count() > 1 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Only reactivate the chat if this contact is in the connected account's list
|
|
if( CurrentAccount::instance()->getContactByHandle( participants.first() ) == 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Create a new switchboard, setting it as empty but with an hint about
|
|
// who to call if the user wants to chat again.
|
|
MsnSwitchboardConnection *switchboard = createSwitchboardConnection( 0, participants.first() );
|
|
if( switchboard )
|
|
{
|
|
chat->setSwitchboardConnection( switchboard );
|
|
chat->setEnabled( true );
|
|
|
|
// Mark this chat's window for reactivation
|
|
if( ! windowsList.contains( chat->getChatWindow() ) )
|
|
{
|
|
windowsList.append( chat->getChatWindow() );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Go through all the windows and reactivate them
|
|
foreach( ChatWindow *window, windowsList )
|
|
{
|
|
window->setEnabled( true );
|
|
}
|
|
}
|
|
|
|
|
|
// The user has disconnected, so close all open switchboard connections and disable the chats
|
|
void ChatMaster::disconnected()
|
|
{
|
|
if( ! switchboardConnections_.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Closing" << switchboardConnections_.count() << "open switchboards.";
|
|
#endif
|
|
|
|
// Cleanup switchboard connections.
|
|
qDeleteAll( switchboardConnections_ );
|
|
switchboardConnections_.clear();
|
|
}
|
|
|
|
// Don't waste time disabling the chats when exiting.
|
|
KMessApplication *kmessApp = static_cast<KMessApplication*>( kapp );
|
|
if( kmessApp->quitSelected() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( ! chatWindows_.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Disabling" << chatWindows_.count() << "open chat windows.";
|
|
#endif
|
|
|
|
// Go through all the chats
|
|
foreach( ChatWindow *window, chatWindows_ )
|
|
{
|
|
// Disable the chats so they can't receive user input anymore
|
|
window->setEnabled( false );
|
|
}
|
|
}
|
|
|
|
if( ! chats_.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Closing" << chats_.count() << "open chats.";
|
|
#endif
|
|
|
|
// Go through all the chats and disable them
|
|
foreach( Chat *chat, chats_ )
|
|
{
|
|
chat->setEnabled( false );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Forward a new switchboard server request signal coming from an existing switchboard connection
|
|
void ChatMaster::forwardRequestNewSwitchboard( QString handle )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Forwarding the new switchboard request signal for "
|
|
<< handle << "." << endl;
|
|
#endif
|
|
|
|
emit requestSwitchboard( handle, ChatInformation::CONNECTION_REFRESH );
|
|
}
|
|
|
|
|
|
|
|
// Forward a request to add or remove a contact from the contactlist, which comes from the ContactFrame.
|
|
void ChatMaster::forwardSetContactAdded( QString handle, bool isAdded )
|
|
{
|
|
if( isAdded )
|
|
{
|
|
emit addContact( handle );
|
|
}
|
|
else
|
|
{
|
|
emit removeContact( handle, false );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Forward a request to block or unblock a contact, which comes from the ContactFrame.
|
|
void ChatMaster::forwardSetContactBlocked( QString handle, bool isBlocked )
|
|
{
|
|
if( isBlocked )
|
|
{
|
|
emit blockContact( handle );
|
|
}
|
|
else
|
|
{
|
|
emit unblockContact( handle );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Return the application list for a given contact
|
|
ApplicationList * ChatMaster::getApplicationList(const QString &handle)
|
|
{
|
|
ApplicationList *appList = 0;
|
|
|
|
// Get the contact.
|
|
ContactBase *contact = CurrentAccount::instance()->getContactByHandle(handle);
|
|
if(KMESS_NULL(contact)) return 0;
|
|
|
|
// Get the application list
|
|
if( contact->hasApplicationList() )
|
|
{
|
|
appList = contact->getApplicationList();
|
|
}
|
|
else
|
|
{
|
|
// Create object if it wasn't created yet for this contact.
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Creating application list for contact " << handle;
|
|
#endif
|
|
appList = contact->createApplicationList();
|
|
|
|
// Connect signals from the applist
|
|
connect( appList, SIGNAL( newApplication(Application*) ), // A new application was created.
|
|
this, SLOT( slotConnectApplication(Application*) ));
|
|
connect( appList, SIGNAL( putMsg(const MimeMessage&, const QString&, bool) ), // Request to send message
|
|
this, SLOT( slotDeliverMimeMessage(const MimeMessage&, const QString&, bool) ));
|
|
}
|
|
|
|
return appList;
|
|
}
|
|
|
|
|
|
|
|
// Return the chat tab which uses the given switchboard connection
|
|
Chat *ChatMaster::getChatBySwitchboard( const MsnSwitchboardConnection *connection )
|
|
{
|
|
// Look through the chat windows for an exclusive chat with the given contact
|
|
foreach( Chat *chat, chats_ )
|
|
{
|
|
if( chat->getSwitchboardConnection() == connection )
|
|
{
|
|
return chat;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
// Return the chat window which uses the given switchboard connection
|
|
ChatWindow * ChatMaster::getChatWindowBySwitchboard(const MsnSwitchboardConnection *connection)
|
|
{
|
|
Chat *chat = getChatBySwitchboard( connection );
|
|
if( chat != 0 )
|
|
{
|
|
return chat->getChatWindow();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
// Return the chat where we're having an conversation with the given contact.
|
|
Chat *ChatMaster::getContactsChat( const QString &handle, bool privateChat )
|
|
{
|
|
Chat *result = 0;
|
|
|
|
// Look through the chats for an exclusive one with the contact given
|
|
foreach( Chat *chat, chats_ )
|
|
{
|
|
if( chat->isExclusiveChatWithContact( handle ) )
|
|
{
|
|
return chat; // best candidate found
|
|
}
|
|
else if( ! privateChat && chat->isContactInChat( handle ) )
|
|
{
|
|
result = chat; // keep it as a second choice
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
// Return the chat where we're having a conversation with the given contacts.
|
|
// The privateChat parameter is ignored.
|
|
Chat *ChatMaster::getContactsChat( const QStringList &handles, bool privateChat )
|
|
{
|
|
// Do nothing if there are no open chats or if the list is empty
|
|
if( handles.isEmpty() || chats_.isEmpty() )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// If the list only has one contact, just call the single handle version of this method.
|
|
if( handles.count() == 1 )
|
|
{
|
|
return getContactsChat( handles.first(), privateChat );
|
|
}
|
|
|
|
Chat *result = 0;
|
|
|
|
// Look through the chats for a group chat with all the contacts of the list
|
|
foreach( Chat *chat, chats_ )
|
|
{
|
|
bool found = true;
|
|
|
|
// Check every contact in the list to see if they're all present in this chat
|
|
for( QStringList::ConstIterator it = handles.begin(); it != handles.end(); ++it )
|
|
{
|
|
if( ! chat->isContactInChat( *it ) )
|
|
{
|
|
found = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( found )
|
|
{
|
|
result = chat;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
// Return the chat connection where we're having an conversation with the given contact.
|
|
MsnSwitchboardConnection *ChatMaster::getContactSwitchboardConnection( const QString &handle, bool doRequirePrivateChat )
|
|
{
|
|
foreach( MsnSwitchboardConnection *connection, switchboardConnections_ )
|
|
{
|
|
if( ! connection->isContactInChat( handle ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// If the wanted contact is in this chat, it's 99% sure this is the one we want, except when we want a private chat, but this is not
|
|
if( ! connection->isExclusiveChatWithContact( handle ) && doRequirePrivateChat )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
return connection;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
// Initialize the class
|
|
bool ChatMaster::initialize()
|
|
{
|
|
if ( initialized_ )
|
|
{
|
|
kDebug() << "Already initialized.";
|
|
return false;
|
|
}
|
|
|
|
// Connect the contact.
|
|
CurrentAccount *currentAccount = CurrentAccount::instance();
|
|
const ContactList *contactList = currentAccount->getContactList();
|
|
connect( contactList, SIGNAL( contactChangedMsnObject(Contact*) ),
|
|
this, SLOT ( slotContactChangedMsnObject(Contact*) ) );
|
|
|
|
initialized_ = true;
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
// Check whether the contact is in any of the existing chat windows.
|
|
bool ChatMaster::isContactInChat( const QString &handle )
|
|
{
|
|
// Find the chat where we have an exclusive chat with the given contact
|
|
return ( getContactsChat( handle, false ) != 0 );
|
|
}
|
|
|
|
|
|
|
|
// Verify if a new chat can be opened and requests a switchboard
|
|
void ChatMaster::requestChat( QString handle )
|
|
{
|
|
ChatInformation::ConnectionType connectionType;
|
|
|
|
// Look through the chats for an exclusive chat with the given contact
|
|
Chat *chat = getContactsChat( handle, true );
|
|
|
|
// No need to open another chat, notify about the presence of the existing one.
|
|
if( chat != 0 )
|
|
{
|
|
raiseChat( chat, true );
|
|
return;
|
|
}
|
|
|
|
// Get the contact, see if it's online
|
|
CurrentAccount *currentAccount = CurrentAccount::instance();
|
|
ContactBase *contact = currentAccount->getContactByHandle( handle );
|
|
|
|
if( ( contact && contact->getStatus() == STATUS_OFFLINE )
|
|
|| ( currentAccount->getStatus() == STATUS_INVISIBLE ) )
|
|
{
|
|
// Either we are invisible, or the contact is offline or invisible.
|
|
// We'll start an offline chat.
|
|
connectionType = ChatInformation::CONNECTION_OFFLINE;
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Starting an offline chat with " << handle;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
// Start a normal chat with an online contact
|
|
connectionType = ChatInformation::CONNECTION_CHAT;
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Requesting a switchboard to chat with " << handle;
|
|
#endif
|
|
}
|
|
|
|
MsnSwitchboardConnection *switchboard = getContactSwitchboardConnection( handle, true );
|
|
if( switchboard != 0 )
|
|
{
|
|
createChat( switchboard );
|
|
return;
|
|
}
|
|
|
|
emit requestSwitchboard( handle, connectionType );
|
|
}
|
|
|
|
|
|
|
|
// A chat is closing
|
|
void ChatMaster::slotChatClosing( QObject *chatObject )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Marking chat as closing.";
|
|
#endif
|
|
|
|
// Make sure this chat window don't receive new messages.
|
|
Chat *chat = static_cast<Chat*>( chatObject );
|
|
closingChats_.append( chat );
|
|
|
|
// The contains check avoids deleting references to old switchboards which may be outdated and don't exist anymore.
|
|
MsnSwitchboardConnection *switchboard = chat->getSwitchboardConnection();
|
|
if( switchboard && switchboardConnections_.contains( switchboard ) )
|
|
{
|
|
switchboardConnections_.removeAll( switchboard );
|
|
delete switchboard;
|
|
}
|
|
}
|
|
|
|
|
|
// A chat was destroyed
|
|
void ChatMaster::slotChatDestroyed( QObject *chatObject )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Removing chat from list.";
|
|
#endif
|
|
|
|
// Remove from the list
|
|
Chat *chat = static_cast<Chat*>( chatObject );
|
|
|
|
chats_ .removeAll( chat );
|
|
closingChats_.removeAll( chat );
|
|
}
|
|
|
|
|
|
// A chat window was destroyed
|
|
void ChatMaster::slotChatWindowDestroyed(QObject *chatWindow)
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Removing chat window from list.";
|
|
#endif
|
|
|
|
// Remove from the list
|
|
ChatWindow *window = static_cast<ChatWindow*>( chatWindow );
|
|
|
|
chatWindows_.removeAll( window );
|
|
}
|
|
|
|
|
|
|
|
// A new application was created for a contact.
|
|
void ChatMaster::slotConnectApplication(Application *application)
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Connecting application signals.";
|
|
#endif
|
|
|
|
connect( application, SIGNAL( applicationMessage(const ChatMessage&) ),
|
|
this, SLOT( showSpecialMessage(const ChatMessage&) ) );
|
|
connect( application, SIGNAL( updateApplicationMessage(const QString&,const QString&) ),
|
|
this, SIGNAL( updateApplicationMessage(const QString&,const QString&) ) );
|
|
|
|
// Special connections for applications which were started dynamically to handle an incoming data stream.
|
|
if( application->getMode() == Application::APP_MODE_DATACAST )
|
|
{
|
|
if( qobject_cast<InkTransferP2P*>( application ) != 0 )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Connecting additional signals for InkTransferP2P.";
|
|
#endif
|
|
|
|
connect( application, SIGNAL( inkReceived(const QString&,const QString&) ),
|
|
this, SLOT( slotGotInkMessage(const QString&,const QString&) ));
|
|
}
|
|
else
|
|
{
|
|
kWarning() << "Unknown datacast application: " << application->metaObject()->className();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Append the message to the queue, waiting to be delivered
|
|
void ChatMaster::queueMessage( const MimeMessage &message, const QString &handle, bool privateChatRequired )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Adding message for '" << handle << "' to the queue "
|
|
<< "(size=" << ( pendingMimeMessages_.count() + 1 ) << ")." << endl;
|
|
#endif
|
|
// NOTE: Store these pending in ApplicationList class instead?
|
|
|
|
PendingMimeMessage *pending = new PendingMimeMessage;
|
|
pending->handle = handle;
|
|
pending->message = message;
|
|
pending->privateChatRequired = privateChatRequired;
|
|
|
|
// No active connection found.
|
|
// Request a new chat with the contact, keep the message pending.
|
|
pendingMimeMessages_.append( pending );
|
|
}
|
|
|
|
|
|
|
|
// Raise an existing chat window. Forcing a raise is reasonable only when the user requests to, since it makes the window
|
|
// to grab keyboard focus and be raised on top of other windows.
|
|
void ChatMaster::raiseChat( Chat *chat, bool force )
|
|
{
|
|
// Check if the pointer is valid
|
|
if( chat == 0 || ! chats_.contains( chat ) )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Invalid raising request received; chat is probably already closed.";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
ChatWindow *chatWindow = chat->getChatWindow();
|
|
if( chatWindow == 0 || ! chatWindows_.contains( chatWindow ) )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Invalid raising request received; chat is probably already closed.";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// Making windows raise and get focus interrupts the user... it's better to make them appear minimized,
|
|
// and let them just flash in the taskbar.
|
|
|
|
if( chatWindow->isVisible() && chatWindow->hasFocus() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Window has focus already.";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// Set the window on the current desktop
|
|
KWindowSystem::setOnDesktop( chatWindow->winId(), KWindowSystem::currentDesktop() );
|
|
|
|
// Activate the selected tab
|
|
chatWindow->setCurrentChat( chat );
|
|
|
|
if( ! force )
|
|
{
|
|
// Required for new chats
|
|
chatWindow->showMinimized();
|
|
return;
|
|
}
|
|
|
|
// Notify about the new window and force it to show and receive focus. Only use upon user request.
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Forcing window raise.";
|
|
#endif
|
|
|
|
// Show the chat's window
|
|
if( chatWindow->isMinimized() )
|
|
{
|
|
chatWindow->showNormal();
|
|
}
|
|
else
|
|
{
|
|
chatWindow->show();
|
|
}
|
|
|
|
// Set the keyboard focus to the chat window
|
|
chatWindow->raise();
|
|
KWindowSystem::activateWindow( chatWindow->winId() );
|
|
KApplication::setActiveWindow( chatWindow );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief Send all pending mime messages for the contact.
|
|
* @param handle The contact to send messages for.
|
|
* @param connection The switchboard connection that can be used to send the messages.
|
|
*/
|
|
void ChatMaster::sendPendingMimeMessages( const QString &handle, MsnSwitchboardConnection *connection )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Sending pending messages for contact " << handle << ", if any.";
|
|
#endif
|
|
|
|
QList<PendingMimeMessage*> sentMessages;
|
|
|
|
bool isPrivateChat = (connection->getContactsInChat().size() < 2);
|
|
|
|
foreach( PendingMimeMessage *pendingMimeMessage, pendingMimeMessages_ )
|
|
{
|
|
// Ignore messages for other contacts
|
|
if( pendingMimeMessage->handle != handle )
|
|
{
|
|
// Get next message
|
|
continue;
|
|
}
|
|
|
|
// Ignore messages that can only be sent on a private chat.
|
|
if( pendingMimeMessage->privateChatRequired && ! isPrivateChat )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Switchboard is not a private chat, keeping message in queue.";
|
|
#endif
|
|
|
|
// Get next message
|
|
continue;
|
|
}
|
|
|
|
// Check when the connection is busy again
|
|
if( connection->isBusy() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Switchboard is busy again, keeping remaining messages in the queue.";
|
|
#endif
|
|
goto cleanup_return;
|
|
}
|
|
|
|
// Check whether the contact is still in the chat.
|
|
if( ! connection->isConnected() || ! connection->getContactsInChat().contains( handle ) )
|
|
{
|
|
// getContactsInChat() may be empty, don't re-invite the last contact to deviver the message
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Switchboard is not connected or contact left chat, keeping remaining messages in the queue.";
|
|
#endif
|
|
goto cleanup_return;
|
|
}
|
|
|
|
// Send the message
|
|
connection->sendApplicationMessage( pendingMimeMessage->message );
|
|
sentMessages.append( pendingMimeMessage );
|
|
}
|
|
|
|
|
|
cleanup_return:
|
|
// Do deletion out of the loop, just to be sure.
|
|
foreach( PendingMimeMessage *sentMessage, sentMessages )
|
|
{
|
|
pendingMimeMessages_.removeAll( sentMessage );
|
|
delete sentMessage;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Deliver a chat message to the chat window (can be an Application message or an Offline-IM from MsnNotificationConnection).
|
|
void ChatMaster::showSpecialMessage( const ChatMessage &message, const MsnSwitchboardConnection *switchboardChat )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Delivering application/ink/OIM chat message to chat window.";
|
|
#endif
|
|
|
|
const QString &handle = message.getContactHandle();
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Message info, Contact: " << handle << " - Message body: '" << message.getBody() << "' - "
|
|
"Message type: " << message.getType() << " - Message class: " << message.getContentsClass() << "." << endl;
|
|
#endif
|
|
|
|
Chat *chat;
|
|
if( switchboardChat != 0 )
|
|
{
|
|
chat = getChatBySwitchboard( switchboardChat );
|
|
}
|
|
else
|
|
{
|
|
chat = getContactsChat( handle, true );
|
|
}
|
|
|
|
// Deliver the message to the contact's chat window
|
|
if( chat != 0 )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Delivering message.";
|
|
#endif
|
|
|
|
// The chat window is currently closing
|
|
if( closingChats_.contains( chat ) )
|
|
{
|
|
if( message.isNormalMessage() )
|
|
{
|
|
// TODO: Sometimes, you close a window when the other contact sends a new message, and you lose it.
|
|
// Maybe it's possible to avoid the loss, for example with a notification balloon to notify of the message.
|
|
kWarning() << "Chat window is closing, suppressing message '" << message.getBody() << "' from '" << handle << "'.";
|
|
}
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
else
|
|
{
|
|
kDebug() << "Chat window is closing, suppressing message.";
|
|
}
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
chat->receivedMessage( message );
|
|
return;
|
|
}
|
|
|
|
// No window is available - we should check if it's needed to create a new window, or if the message
|
|
// can be safely ignored.
|
|
switch( message.getContentsClass() )
|
|
{
|
|
case ChatMessage::CONTENT_MESSAGE:
|
|
case ChatMessage::CONTENT_MESSAGE_INK:
|
|
case ChatMessage::CONTENT_NOTIFICATION_NUDGE:
|
|
case ChatMessage::CONTENT_NOTIFICATION_WINK:
|
|
case ChatMessage::CONTENT_SYSTEM_NOTICE:
|
|
case ChatMessage::CONTENT_SYSTEM_ERROR:
|
|
case ChatMessage::CONTENT_APP_INVITE:
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Message is important, delivering it.";
|
|
#endif
|
|
break;
|
|
|
|
default:
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Message is not important, will be ignored.";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
|
|
// Check if a switchboard is present
|
|
MsnSwitchboardConnection *switchboard = getContactSwitchboardConnection( handle, true );
|
|
if( switchboard == 0 )
|
|
{
|
|
// Create a new offline switchboard connection to deliver the message. It can be converted to an
|
|
// online switchboard one if needed.
|
|
switchboard = startSwitchboard( ChatInformation( 0, handle, 0, ChatInformation::CONNECTION_OFFLINE ) );
|
|
if( switchboard == 0 )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Could not create an offline switchboard.";
|
|
#endif
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Create a new chat window for the message
|
|
chat = createChat( switchboard );
|
|
if( chat == 0 )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Could not create a chat window.";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// Deliver the message to the new chat
|
|
chat->receivedMessage( message );
|
|
}
|
|
|
|
|
|
|
|
// Display an MSN Object that was received.
|
|
void ChatMaster::showMsnObject( const QString &handle, const MsnObject &msnObject, Chat *chat )
|
|
{
|
|
QString fileName;
|
|
QString emoticonCode;
|
|
|
|
// Get the contact
|
|
// Note I don't use msnObject.getCreator, because I don't want to trust the other client!
|
|
ContactBase *contact = 0;
|
|
Contact *msnContact = 0;
|
|
Chat *chatUpdate = 0;
|
|
|
|
// Get msn object filename
|
|
fileName = KMessConfig::instance()->getMsnObjectFileName( msnObject );
|
|
|
|
// Handle the picture
|
|
switch( msnObject.getType() )
|
|
{
|
|
case MsnObject::DISPLAYPIC:
|
|
// Get the real MSN contact from the MSN contact list.
|
|
// Picture is stored in the ContactExtension because it's not an official property of the contact (not stored server-side).
|
|
msnContact = CurrentAccount::instance()->getContactList()->getContactByHandle(handle);
|
|
if(KMESS_NULL(msnContact)) return;
|
|
msnContact->getExtension()->setContactPicturePath( fileName );
|
|
pendingDisplayPictures_.removeAll( handle );
|
|
break;
|
|
|
|
case MsnObject::WINK:
|
|
// Show the Wink in the chat window where the original datacast message came from.
|
|
if(KMESS_NULL(chat)) return;
|
|
chat->showWink( handle, fileName, msnObject.getFriendly() );
|
|
break;
|
|
|
|
case MsnObject::EMOTICON:
|
|
// Inform the contact that a custom emoticon can be parsed.
|
|
contact = CurrentAccount::instance()->getContactByHandle(handle);
|
|
if(KMESS_NULL(contact)) return;
|
|
emoticonCode = contact->getEmoticonCode(msnObject.getDataHash()); // always put before addEmoticonFile()!
|
|
contact->addEmoticonFile( msnObject.getDataHash(), fileName );
|
|
|
|
// Update all chats where the contact is present
|
|
foreach( chatUpdate, chats_ )
|
|
{
|
|
if( chatUpdate->isContactInChat( handle ) )
|
|
{
|
|
chatUpdate->updateCustomEmoticon( handle, emoticonCode );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MsnObject::BACKGROUND:
|
|
// handle background transfers
|
|
|
|
case MsnObject::VOICECLIP:
|
|
// handle voicelip transfers
|
|
|
|
default:
|
|
kWarning() << "Unable to handle MsnObject pictures of type '" << msnObject.getType() << "'.";
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Determine what to do when a contact changed it's picture.
|
|
void ChatMaster::slotContactChangedMsnObject( Contact *contact )
|
|
{
|
|
QString handle = contact->getHandle();
|
|
|
|
// If the contact no longer likes to display an msn object, remove it
|
|
if( contact->getMsnObject() == 0 )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Contact '" << handle << "' removed its MSN object";
|
|
#endif
|
|
|
|
contact->getExtension()->setContactPicturePath( QString::null );
|
|
return;
|
|
}
|
|
|
|
MsnObject msnObject( *contact->getMsnObject() );
|
|
|
|
// See if the file already exists.
|
|
QString objectFileName = KMessConfig::instance()->getMsnObjectFileName( msnObject );
|
|
if( QFile::exists( objectFileName ) )
|
|
{
|
|
bool imageInvalid = QImageReader::imageFormat( objectFileName ).isEmpty();
|
|
if( ! imageInvalid && msnObject.verifyFile( objectFileName ) )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Picture for '" << handle << "' is already downloaded.";
|
|
#endif
|
|
|
|
contact->getExtension()->setContactPicturePath( objectFileName );
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Picture for '" << handle
|
|
<< "' is already downloaded, but corrupt "
|
|
<< ( imageInvalid ? "(detected by QImageReader)" : "(detected by MsnObject)" )
|
|
<< ", deleting it: '" << objectFileName << "'" << endl;
|
|
#endif
|
|
QFile::remove( objectFileName );
|
|
|
|
// If the file was set as picture. reset right away.
|
|
if( contact->getExtension()->getContactPicturePath() == objectFileName )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kWarning() << "Corrupt picture was already set, "
|
|
<< "resetting contact picture." << endl;
|
|
#endif
|
|
contact->getExtension()->setContactPicturePath( QString::null );
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the contact is active in a chat,
|
|
// download the new picture straight away.
|
|
MsnSwitchboardConnection *switchboard = getContactSwitchboardConnection( handle, true );
|
|
if( switchboard != 0 )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Contact '" << handle << "' changed its MSN object.";
|
|
#endif
|
|
|
|
// Automatically decides which switchboard is the best to use,
|
|
// this may change during the transfer.
|
|
startMsnObjectDownload( handle, &msnObject, 0 );
|
|
return;
|
|
}
|
|
|
|
|
|
// Queue a request to download it later in the background.
|
|
// That method will check for the prerequisites to download it,
|
|
// since that may change between this point and the timedUpdate() call.
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Contact '" << handle << "' changed its MSN object, queueing a request for it.";
|
|
#endif
|
|
|
|
// Request the contact's msnobject
|
|
if( ! pendingDisplayPictures_.contains( handle ) )
|
|
{
|
|
pendingDisplayPictures_.append( handle );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// A contact joined to one of our switchboard sessions.
|
|
void ChatMaster::slotContactJoinedChat(QString handle)
|
|
{
|
|
// Get switchboard connection from slot sender
|
|
MsnSwitchboardConnection *connection = static_cast<MsnSwitchboardConnection*>( const_cast<QObject*>( sender() ) );
|
|
if(KMESS_NULL(connection)) return;
|
|
|
|
// Send any pending messages, if any
|
|
if( pendingMimeMessages_.count() > 0 )
|
|
{
|
|
// Find the chat window where the contact is the only participant.
|
|
if( connection->getContactsInChat().size() > 1 )
|
|
{
|
|
// Contact joined chat conversation with multiple partipipants, ignore this.
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Contact '" << handle << "' joined multi-chat, not sending pending messages.";
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Contact '" << handle << "' joined chat, checking for pending messages.";
|
|
#endif
|
|
|
|
// Send the messages
|
|
if( pendingMimeMessages_.count() > 0 )
|
|
{
|
|
sendPendingMimeMessages( handle, connection );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Check whether the contact picture is outdated.
|
|
// Use getContactByHandle() from the MSN contact list to get the msn object.
|
|
const Contact *contact = CurrentAccount::instance()->getContactList()->getContactByHandle( handle );
|
|
if( contact != 0 && contact->hasP2PSupport() )
|
|
{
|
|
if( contact->getMsnObject() != 0 )
|
|
{
|
|
startMsnObjectDownload( contact->getHandle(), contact->getMsnObject(), 0 );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief Deliver an application command to the correct application.
|
|
*/
|
|
void ChatMaster::slotDeliverAppCommand(QString cookie, QString handle, QString command)
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Delivering application command.";
|
|
#endif
|
|
|
|
Application *app = 0;
|
|
|
|
// Get the contact.
|
|
ContactBase *contact = CurrentAccount::instance()->getContactByHandle(handle);
|
|
if(KMESS_NULL(contact)) return;
|
|
|
|
// Get the application list
|
|
if( contact->hasApplicationList() )
|
|
{
|
|
app = contact->getApplicationList()->getApplicationByCookie(cookie);
|
|
}
|
|
|
|
// App not found.
|
|
if( app == 0 )
|
|
{
|
|
kWarning() << "Application not found to deliver user command (contact=" << contact << ", command=" << command << ").";
|
|
return;
|
|
}
|
|
|
|
// Deliver the 'accept' or 'cancel' command.
|
|
app->gotCommand( command );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief Deliver a message from an Application to the switchboard connection.
|
|
* @param message The Mime message to deliver. The message can contain a normal MIME or binary P2P payload.
|
|
* @param handle The contact the message should be delivered to.
|
|
* @param privateChatRequired Whether a private chat is required to deliver the message. If not, a multi-chat will be used when available.
|
|
*
|
|
* This method automatically determines which switchboard is should use to deliver the message.
|
|
* When the contact is not present in any of the existing switchboard connections, a new connection request will be made.
|
|
* The message will be queued for delivery to the switchboard so it can be delivered once it's available.
|
|
* Conversations with multiple contacts are avoided because of the networking overhead;
|
|
* since the switchboard is a broadcast channel, each participant receives the message.
|
|
*
|
|
* Unlike the direct connection link, the switchboard is only capable of transferring mime messages.
|
|
* To transfer the P2P data, the P2PApplication has to wrap the message as MimeMessage payload.
|
|
*/
|
|
void ChatMaster::slotDeliverMimeMessage(const MimeMessage &message, const QString &handle, bool privateChatRequired)
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Delivering application message for '" << handle << "' to switchboard.";
|
|
#endif
|
|
#ifdef KMESSTEST
|
|
QString contentType = message.getValue("Content-Type");
|
|
KMESS_ASSERT( contentType == "application/x-msnmsgrp2p"
|
|
|| contentType == "text/x-msmsgsinvite"
|
|
|| contentType.section(';', 0, 0) == "text/x-msmsgsinvite" ); // for ; charset= suffix
|
|
if( contentType == "application/x-msnmsgrp2p" )
|
|
{
|
|
KMESS_ASSERT( message.getValue("P2P-Dest") == handle );
|
|
}
|
|
#endif
|
|
|
|
MsnSwitchboardConnection *connection = getContactSwitchboardConnection(handle, privateChatRequired);
|
|
|
|
if( connection != 0 )
|
|
{
|
|
if( connection->isBusy() )
|
|
{
|
|
// Connection has unacked messages, keep waiting until the switchboard is ready to send
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Switchboard is currently busy, queueing message until the switchboard is ready to send.";
|
|
#endif
|
|
queueMessage( message, handle, privateChatRequired );
|
|
|
|
// Pause application so it won't continue to send much more messages (see P2PApplication::slotSendData()).
|
|
ApplicationList *appList = getApplicationList(handle);
|
|
if( appList != 0 )
|
|
{
|
|
appList->pauseApplications();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Deliver the message
|
|
connection->sendApplicationMessage(message);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "No switchboard available to deliver message,"
|
|
<< " requesting chat and queueing message " << (pendingMimeMessages_.count() + 1) << "." << endl;
|
|
#endif
|
|
|
|
emit requestSwitchboard( handle, ChatInformation::CONNECTION_BACKGROUND );
|
|
queueMessage( message, handle, privateChatRequired );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Parse an gif ink message
|
|
void ChatMaster::slotGotInkMessage( const QString &inkData, const QString &contactHandle )
|
|
{
|
|
#ifdef KMESSDEBUG_SWITCHBOARD_GENERAL
|
|
kDebug() << "Parsing ink message";
|
|
#endif
|
|
|
|
// Validate
|
|
QRegExp inkChars("([^A-Za-z0-9:+/=])" );
|
|
if( ! inkData.startsWith("base64:") || inkChars.indexIn( inkData ) != -1 )
|
|
{
|
|
kWarning() << "Received invalid ink message from" << contactHandle << ", found char 0x" << QString::number( inkChars.cap(1)[0].unicode(), 16 ) << "at position" << inkChars.pos(1) << "in: " << inkData;
|
|
return;
|
|
}
|
|
|
|
// Use the ink data as inline image.
|
|
QString inkUrlData = QString( inkData ).replace( "base64:", "base64," );
|
|
|
|
// Get the contact data
|
|
QString friendlyName;
|
|
QString contactPicture;
|
|
const ContactBase *contact = CurrentAccount::instance()->getContactByHandle( contactHandle );
|
|
if( contact != 0 )
|
|
{
|
|
friendlyName = contact->getFriendlyName();
|
|
contactPicture = contact->getContactPicturePath();
|
|
}
|
|
|
|
// Cast the sender object to determinate if the ink is recevied from p2p or switchboard.
|
|
// WLM sends the ink (gif) by p2p protocol for private chat and by mime for multi-chat
|
|
// Other clients send all by mime message, so it's importat to determinate what switchboard
|
|
// is used to grep the correct chat window to showing the message
|
|
MsnSwitchboardConnection *switchboard = qobject_cast<MsnSwitchboardConnection *>( sender() );
|
|
|
|
// Send the message as HTML to the chat window.
|
|
// Using data url scheme for embedded images (src="data:image/gif;base64,fdsfsdfsd")
|
|
showSpecialMessage( ChatMessage( ChatMessage::TYPE_INCOMING,
|
|
ChatMessage::CONTENT_MESSAGE_INK,
|
|
true, "<img src=\"data:image/gif;" + Qt::escape( inkUrlData ) + "\">",
|
|
contactHandle,
|
|
friendlyName,
|
|
contactPicture ), switchboard );
|
|
}
|
|
|
|
|
|
|
|
// The switchboard received a Mime message
|
|
void ChatMaster::slotGotMessage(const MimeMessage &message, const QString &handle)
|
|
{
|
|
ApplicationList *appList = getApplicationList(handle);
|
|
if( appList != 0 )
|
|
{
|
|
appList->gotMessage(message);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// The switchboard received a P2P message
|
|
void ChatMaster::slotGotMessage(const P2PMessage &message, const QString &handle)
|
|
{
|
|
ApplicationList *appList = getApplicationList(handle);
|
|
if( appList != 0 )
|
|
{
|
|
appList->gotMessage(message);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// The switchboard received an msn object
|
|
void ChatMaster::slotGotMsnObject(const QString &msnObjectData, const QString &handle)
|
|
{
|
|
// Get the contact.
|
|
ContactBase *contact = CurrentAccount::instance()->getContactByHandle(handle);
|
|
if(KMESS_NULL(contact)) return;
|
|
|
|
// Get the switchboard connection where the emoticon/wink/voiceclip was posted
|
|
const MsnSwitchboardConnection *connection = static_cast<const MsnSwitchboardConnection*>( sender() );
|
|
Chat *chat = 0;
|
|
if( connection != 0 )
|
|
{
|
|
chat = getChatBySwitchboard( connection );
|
|
}
|
|
|
|
// Parse the msn object
|
|
MsnObject msnObject(msnObjectData);
|
|
|
|
// Get the contact name,
|
|
QString friendlyName = Qt::escape( CurrentAccount::instance()->getContactFriendlyNameByHandle( handle ) );
|
|
|
|
// Determine the statusmessage to display.
|
|
QString statusMessage;
|
|
MsnObject::MsnObjectType objectType = msnObject.getType();
|
|
if( objectType == MsnObject::WINK )
|
|
{
|
|
statusMessage = i18n( "%1 is sending a wink: %2", friendlyName, msnObject.getFriendly() );
|
|
}
|
|
|
|
// Show the status message
|
|
if( ! statusMessage.isEmpty() )
|
|
{
|
|
if( ! KMESS_NULL(connection) && ! KMESS_NULL(chat) )
|
|
{
|
|
chat->getChatWindow()->showStatusMessage( statusMessage );
|
|
}
|
|
}
|
|
|
|
startMsnObjectDownload( handle, &msnObject, chat );
|
|
}
|
|
|
|
|
|
|
|
// A msn object (picture, wink, emoticon) was received for the contact.
|
|
void ChatMaster::slotMsnObjectReceived(const QString &handle, const MsnObject &msnObject)
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Received msn object from: " << handle << ".";
|
|
#endif
|
|
|
|
Chat *chat = 0;
|
|
|
|
// Get sender of this signal, slotGotMsnObject() assigned this to the p2papp.
|
|
const P2PApplication *application = static_cast<const P2PApplication*>( sender() );
|
|
if( application != 0 )
|
|
{
|
|
chat = application->getChat();
|
|
if( chat != 0 && ! chats_.contains( chat ) )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Original chat not found for received MSNObject "
|
|
<< "(objecttype=" << msnObject.getType()
|
|
<< " contact=" << handle << ")." << endl;
|
|
#endif
|
|
chat = getContactsChat( handle, true );
|
|
}
|
|
}
|
|
|
|
showMsnObject( handle, msnObject, chat );
|
|
}
|
|
|
|
|
|
|
|
// The switchboard is ready to send more messages.
|
|
void ChatMaster::slotSwitchboardReady()
|
|
{
|
|
// No need to send pending messages or resume an application (a message is always queued before the app is paused).
|
|
if( pendingMimeMessages_.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "A switchboard is ready to send more messages, no messages pending.";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// Get the connection
|
|
MsnSwitchboardConnection *connection = static_cast<MsnSwitchboardConnection*>( const_cast<QObject*>( sender() ) );
|
|
if(KMESS_NULL(connection)) return;
|
|
|
|
// Get the contacts
|
|
const QStringList &contacts = connection->getContactsInChat();
|
|
bool isPrivateChat = (contacts.size() < 2);
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "A switchboard is ready to send more messages, " << pendingMimeMessages_.count() << " messages pending.";
|
|
#endif
|
|
|
|
// Send all pending messages
|
|
sendPendingMimeMessages( contacts[0], connection );
|
|
|
|
// See if the switchboard is still ready.
|
|
// Don't use connection->isBusy() as it will break with
|
|
// the test above when exactly all messages are sent before the connection became busy.
|
|
if( ! pendingMimeMessages_.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Switchboard is busy again, not notifying applications.";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// A switchboard is still available.
|
|
// If the contact applications were paused, resume them now.
|
|
ContactBase *contact = CurrentAccount::instance()->getContactByHandle( contacts[0] );
|
|
if( contact != 0 && contact->hasApplicationList() )
|
|
{
|
|
contact->getApplicationList()->resumeApplications(isPrivateChat);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Delete an existing switchboard
|
|
void ChatMaster::slotSwitchboardDelete( MsnSwitchboardConnection *closing, bool deleteObject )
|
|
{
|
|
// Remove from collection
|
|
switchboardConnections_.removeAll( closing );
|
|
|
|
// Do not delete the object: this method can be called as slot from the switchboard's destructor
|
|
if( ! deleteObject )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Disconnect and destroy
|
|
disconnect( closing, 0, this, 0 );
|
|
closing->deleteLater();
|
|
}
|
|
|
|
|
|
|
|
// Configure and start the Mime application object.
|
|
void ChatMaster::startApplication( MimeApplication *application )
|
|
{
|
|
// Get the application list.
|
|
ApplicationList *appList = getApplicationList( application->getContactHandle() );
|
|
if(KMESS_NULL(appList)) return;
|
|
|
|
// Add to list
|
|
appList->addApplication( application ); // requires MimeApplication/P2PApplication type.
|
|
|
|
// Start application
|
|
application->start();
|
|
}
|
|
|
|
|
|
|
|
// Configure and start the P2P application object.
|
|
void ChatMaster::startApplication( P2PApplication *application )
|
|
{
|
|
// Get the application list.
|
|
ApplicationList *appList = getApplicationList( application->getContactHandle() );
|
|
if(KMESS_NULL(appList)) return;
|
|
|
|
// Add to list.
|
|
appList->addApplication( application ); // requires MimeApplication/P2PApplication type.
|
|
|
|
// Start application
|
|
application->start();
|
|
}
|
|
|
|
|
|
|
|
// Start a connection with the information gathered from the Notification connection
|
|
MsnSwitchboardConnection *ChatMaster::startSwitchboard( const ChatInformation &chatInfo )
|
|
{
|
|
MsnSwitchboardConnection *switchboard = getContactSwitchboardConnection( chatInfo.getContactHandle(), true );
|
|
Chat *chat = getChatBySwitchboard( switchboard );
|
|
|
|
if( switchboard != 0 && chatInfo.getType() != ChatInformation::CONNECTION_REFRESH )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Deleting old switchboard connection.";
|
|
#endif
|
|
slotSwitchboardDelete( switchboard, true );
|
|
switchboard = 0;
|
|
}
|
|
|
|
if( switchboard == 0 )
|
|
{
|
|
if( chat == 0 )
|
|
{
|
|
// Create a new switchboard connection
|
|
switchboard = createSwitchboardConnection();
|
|
}
|
|
else
|
|
{
|
|
// Replace the old switchboard connection with a new one
|
|
switchboard = createSwitchboardConnection( chat->getSwitchboardConnection() );
|
|
}
|
|
|
|
if( ! switchboard )
|
|
{
|
|
kWarning() << "Unable to register new switchboard, not starting it up.";
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
switchboard->start( chatInfo );
|
|
|
|
if( chat != 0 )
|
|
{
|
|
chat->setSwitchboardConnection( switchboard );
|
|
}
|
|
|
|
// If this switchboard is a background one, made to request a contact's picture,
|
|
// we can remove the pending request from the list now.
|
|
if( chatInfo.getType() == ChatInformation::CONNECTION_BACKGROUND
|
|
&& pendingDisplayPictures_.contains( chatInfo.getContactHandle() ) )
|
|
{
|
|
pendingDisplayPictures_.removeAll( chatInfo.getContactHandle() );
|
|
}
|
|
|
|
return switchboard;
|
|
}
|
|
|
|
|
|
|
|
// Start a chat with the information gathered from a switchboard connection
|
|
Chat *ChatMaster::createChat( MsnSwitchboardConnection *switchboard )
|
|
{
|
|
#ifdef KMESSTEST
|
|
KMESS_ASSERT( switchboard != 0 );
|
|
#endif
|
|
|
|
Chat *newChat;
|
|
ChatWindow *newChatWindow;
|
|
|
|
// If the new chat is a group chat, find existing group chat windows; else find private chats
|
|
newChat = getContactsChat( switchboard->getContactsInChat(), true );
|
|
|
|
if( newChat != 0 )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "A chat with contacts"
|
|
<< switchboard->getContactsInChat().join(",") << "already exists, raising it." << endl;
|
|
#endif
|
|
|
|
// If we've requested a chat window, raise it forcing it open over other KMess' windows;
|
|
// if some contact wants to chat with us, the chat window will open minimized.
|
|
raiseChat( newChat, switchboard->getUserStartedChat() );
|
|
|
|
return newChat;
|
|
}
|
|
|
|
// Create the chat widget
|
|
newChat = new Chat();
|
|
|
|
if( newChat == 0 )
|
|
{
|
|
kDebug() << "Couldn't create a new chat tab.";
|
|
return 0;
|
|
}
|
|
|
|
// Initialize it
|
|
if( ! newChat->initialize( switchboard ) )
|
|
{
|
|
kDebug() << "Couldn't initialize the chat tab.";
|
|
return 0;
|
|
}
|
|
|
|
// Select the chat window where to open the new tab, or create a new one
|
|
newChatWindow = createChatWindow( newChat );
|
|
|
|
// Create the first tab in the chat
|
|
newChat = newChatWindow->addChatTab( newChat );
|
|
if( newChat == 0 )
|
|
{
|
|
kWarning() << "Couldn't add the initial chat.";
|
|
return 0;
|
|
}
|
|
|
|
// Connect the chat's request signals
|
|
connect( newChat, SIGNAL( gotChatMessage(const ChatMessage&,Chat*) ),
|
|
this, SIGNAL( newChatMessage(const ChatMessage&,Chat*) ) );
|
|
connect( newChat, SIGNAL( setContactAllowed(QString) ),
|
|
this, SIGNAL( allowContact(QString) ) );
|
|
connect( newChat, SIGNAL( setContactAdded(QString,bool) ),
|
|
this, SLOT ( forwardSetContactAdded(QString,bool) ) );
|
|
connect( newChat, SIGNAL( setContactBlocked(QString,bool) ),
|
|
this, SLOT ( forwardSetContactBlocked(QString,bool) ) );
|
|
connect( newChat, SIGNAL( appCommand(QString,QString,QString) ),
|
|
this, SLOT ( slotDeliverAppCommand(QString,QString,QString) ) );
|
|
connect( newChat, SIGNAL( requestFileTransfer(const QString&,const QString&) ),
|
|
this, SLOT ( startFileTransfer(const QString&,const QString&) ) );
|
|
connect( newChat, SIGNAL( requestNetMeeting(const QString&) ),
|
|
this, SLOT ( startNetMeeting(const QString&) ) );
|
|
connect( newChat, SIGNAL( closing(QObject*) ),
|
|
this, SLOT ( slotChatClosing(QObject*) ) );
|
|
connect( newChat, SIGNAL( destroyed(QObject*) ),
|
|
this, SLOT ( slotChatDestroyed(QObject*) ) );
|
|
connect( this, SIGNAL( updateApplicationMessage(const QString&,const QString&) ),
|
|
newChat, SIGNAL( updateApplicationMessage(const QString&,const QString&) ) );
|
|
|
|
// Add the new chat to our list
|
|
chats_.append( newChat );
|
|
|
|
// Finally, start the new chat
|
|
newChat->startChat();
|
|
|
|
// If we've requested a chat window, raise it forcing it open over other KMess' windows;
|
|
// if some contact wants to chat with us, the chat window will open minimized.
|
|
raiseChat( newChat, switchboard->getUserStartedChat() );
|
|
|
|
return newChat;
|
|
}
|
|
|
|
|
|
|
|
// Create a new chat window or retrieve an existing one if tabbed chatting is enabled
|
|
ChatWindow *ChatMaster::createChatWindow( Chat *chat )
|
|
{
|
|
ChatWindow *window = 0;
|
|
CurrentAccount *currentAccount = CurrentAccount::instance();
|
|
|
|
if( ! chatWindows_.empty() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "At least one chat window is already open: checking the tabbed chatting settings.";
|
|
#endif
|
|
|
|
if( currentAccount->getTabbedChatMode() == 0 ) // Always group in tabs
|
|
{
|
|
// Pick the last open chat window
|
|
window = chatWindows_.last();
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Always group in tabs. Picking the last created window.";
|
|
#endif
|
|
}
|
|
else if( currentAccount->getTabbedChatMode() == 1 ) // Group tabs by initial contact's group
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Group in tabs by contact group. Choosing a window.";
|
|
#endif
|
|
QString handle( chat->getSwitchboardConnection()->getFirstContact() );
|
|
Contact *contact = currentAccount->getContactList()->getContactByHandle( handle );
|
|
|
|
if( contact && ! contact->getGroupIds().isEmpty() )
|
|
{
|
|
QString group = contact->getGroupIds().first();
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Searching chats for one whose first contact is in the same group as the new one's.";
|
|
#endif
|
|
foreach( Chat *chatItem, chats_ )
|
|
{
|
|
QString handle( chatItem->getSwitchboardConnection()->getFirstContact() );
|
|
Contact *contact = currentAccount->getContactList()->getContactByHandle( handle );
|
|
|
|
if( contact && ! contact->getGroupIds().isEmpty()
|
|
&& contact->getGroupIds().first() == group )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Match found!";
|
|
#endif
|
|
window = chatItem->getChatWindow();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Found a window to use, return that
|
|
if( window != 0 )
|
|
{
|
|
return window;
|
|
}
|
|
|
|
// Create the new chat window
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "No existing chat window, creating new.";
|
|
#endif
|
|
// Create the window: it should have no parent, so it will be displayed in the taskbar for Windows.
|
|
window = new ChatWindow();
|
|
|
|
// Bail out if the window could not be initialized
|
|
if( ! window->initialize() )
|
|
{
|
|
kWarning() << "Couldn't initialize the new chat window!";
|
|
return 0;
|
|
}
|
|
|
|
// Connect its signals
|
|
connect( window, SIGNAL( destroyed(QObject*) ),
|
|
this, SLOT ( slotChatWindowDestroyed(QObject*) ) );
|
|
|
|
// Add the new chat window to our list
|
|
chatWindows_.append( window );
|
|
|
|
return window;
|
|
}
|
|
|
|
|
|
|
|
// Create and register a new switchboard
|
|
MsnSwitchboardConnection *ChatMaster::createSwitchboardConnection( MsnSwitchboardConnection *replace, QString handle )
|
|
{
|
|
MsnSwitchboardConnection *switchboard;
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Creating a new switchboard connection.";
|
|
#endif
|
|
|
|
// Initialize the switchboard connection
|
|
if( replace != 0 )
|
|
{
|
|
switchboard = new MsnSwitchboardConnection( *replace );
|
|
}
|
|
else
|
|
{
|
|
switchboard = new MsnSwitchboardConnection();
|
|
}
|
|
if( ! switchboard || ! switchboard->initialize( handle ) )
|
|
{
|
|
kWarning() << "Couldn't initialize switchboard connection.";
|
|
delete switchboard;
|
|
return 0;
|
|
}
|
|
|
|
switchboardConnections_.append( switchboard );
|
|
|
|
// Connect the switchboard's signals to the Chat Master.
|
|
connect(switchboard, SIGNAL( contactJoinedChat(QString, QString) ),
|
|
this, SLOT ( slotContactJoinedChat(QString) ));
|
|
connect(switchboard, SIGNAL( gotMessage(const MimeMessage&, const QString&) ),
|
|
this, SLOT ( slotGotMessage(const MimeMessage&, const QString&) ));
|
|
connect(switchboard, SIGNAL( gotMessage(const P2PMessage&, const QString&) ),
|
|
this, SLOT ( slotGotMessage(const P2PMessage&, const QString&) ));
|
|
connect(switchboard, SIGNAL( gotInkMessage(const QString&, const QString&) ),
|
|
this, SLOT ( slotGotInkMessage(const QString&, const QString&) ));
|
|
connect(switchboard, SIGNAL( gotMsnObject(const QString&, const QString&) ),
|
|
this, SLOT ( slotGotMsnObject(const QString&, const QString&) ));
|
|
connect(switchboard, SIGNAL( readySend() ),
|
|
this, SLOT ( slotSwitchboardReady() ));
|
|
connect(switchboard, SIGNAL( requestNewSwitchboard(QString) ),
|
|
this, SLOT ( forwardRequestNewSwitchboard(QString) ));
|
|
connect(switchboard, SIGNAL( requestChatWindow(MsnSwitchboardConnection*) ),
|
|
this, SLOT ( createChat(MsnSwitchboardConnection*) ));
|
|
connect(switchboard, SIGNAL( deleteMe(MsnSwitchboardConnection*) ),
|
|
this, SLOT ( slotSwitchboardDelete(MsnSwitchboardConnection*) ));
|
|
|
|
return switchboard;
|
|
}
|
|
|
|
|
|
|
|
// Start a file transfer with the information from the Chat
|
|
void ChatMaster::startFileTransfer( const QString &handle, const QString &filename )
|
|
{
|
|
ApplicationList *appList = getApplicationList(handle);
|
|
if(KMESS_NULL(appList)) return;
|
|
|
|
// Get the contact properties, see how we can transfer the file.
|
|
const ContactBase *contact = CurrentAccount::instance()->getContactByHandle( handle );
|
|
|
|
// If the contact is offline (that is, not online nor invisible), refuse to start the file transfer
|
|
MsnSwitchboardConnection *connection = getContactSwitchboardConnection( handle, true );
|
|
if( ! connection || ( contact && contact->isOffline() && connection->isInactive() ) )
|
|
{
|
|
kWarning() << "Attempted to start a file transfer with an offline contact!";
|
|
return;
|
|
}
|
|
|
|
if( contact != 0 && contact->hasP2PSupport() )
|
|
{
|
|
// The contact supports file transfer over MSNP2P
|
|
P2PApplication *app = new FileTransferP2P(appList, filename);
|
|
startApplication(app);
|
|
}
|
|
else
|
|
{
|
|
// This should be so rare that it justifies a warning message.
|
|
if( contact == 0 )
|
|
{
|
|
kWarning() << "Contact" << handle << "object not found, using old MIME invitations because P2P might not be supported.";
|
|
}
|
|
else
|
|
{
|
|
kWarning() << "Contact" << handle << "does not support P2P transfers, using old MIME invitations instead.";
|
|
}
|
|
|
|
// The contact only supports file transfer the old way
|
|
MimeApplication *app = new FileTransfer(handle, filename);
|
|
startApplication(app);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Start a netmeeting invitation
|
|
void ChatMaster::startNetMeeting(const QString &handle)
|
|
{
|
|
// Get the application list
|
|
ApplicationList *appList = getApplicationList(handle);
|
|
if(KMESS_NULL(appList)) return;
|
|
|
|
// Get the contact
|
|
const ContactBase *contact = CurrentAccount::instance()->getContactByHandle( handle );
|
|
if(KMESS_NULL(contact)) return;
|
|
|
|
// Create the application
|
|
MimeApplication *app = new GnomeMeeting(handle);
|
|
startApplication(app);
|
|
}
|
|
|
|
|
|
|
|
// Start a msnobject transfer to get the contact's msn object.
|
|
void ChatMaster::startMsnObjectDownload( const QString &handle, const MsnObject *msnObject, Chat *chat )
|
|
{
|
|
// Test input
|
|
if(KMESS_NULL(msnObject)) return;
|
|
|
|
// Get the application list
|
|
ApplicationList *appList = getApplicationList(handle);
|
|
if(KMESS_NULL(appList)) return;
|
|
|
|
// First check if the actual object is currently being downloaded already.
|
|
// call before cache check, since file can be partially downloaded.
|
|
if( appList->hasMsnObjectTransfer(*msnObject) )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "object is already being downloaded, not sending a second invite.";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// Get the picture filename, perhaps from a cache
|
|
QString objectFileName = KMessConfig::instance()->getMsnObjectFileName( *msnObject );
|
|
bool fileExists = QFile::exists( objectFileName );
|
|
|
|
// Check if the image can be read.
|
|
if( fileExists )
|
|
{
|
|
bool imageCorrupt = QImageReader::imageFormat( objectFileName ).isEmpty();
|
|
if( imageCorrupt || ! msnObject->verifyFile( objectFileName ) )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Cached MsnObject is corrupt "
|
|
<< ( imageCorrupt ? "(detected by QImage)" : "(detected by MsnObject)" )
|
|
<< ", deleting it: '" << objectFileName << "'" << endl;
|
|
#endif
|
|
QFile::remove( objectFileName );
|
|
fileExists = false;
|
|
}
|
|
}
|
|
|
|
// Avoid downloading again if it exists
|
|
if( fileExists )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Contact MsnObject is already in cache.";
|
|
#endif
|
|
|
|
// Already have it, handle processing in a generic way.
|
|
// Don't use slotMsnObjectReceived() because it uses sender() to get the Chat object.
|
|
showMsnObject( handle, msnObject->objectString(), chat );
|
|
}
|
|
else
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Starting MsnObject download (type=" << msnObject->getType() << ").";
|
|
#endif
|
|
|
|
// Create and initialize the application.
|
|
P2PApplication *app = new MsnObjectTransferP2P(appList, *msnObject);
|
|
app->setChat( chat ); // for winks, to display in originating chat window.
|
|
connect(app, SIGNAL( msnObjectReceived(const QString&, const MsnObject&) ),
|
|
this, SLOT ( slotMsnObjectReceived(const QString&, const MsnObject&) ));
|
|
startApplication(app);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Periodically called method for update commands.
|
|
*
|
|
* This method is synchronized with the ping timer of MsnNotificationConnection
|
|
* to avoid multiple timers which reduces power consumption (see www.linuxpowertop.org).
|
|
*
|
|
* Currently this is used to download display pictures in the background,
|
|
* but it can also be used for other events.
|
|
*/
|
|
void ChatMaster::timedUpdate()
|
|
{
|
|
// Handle the queue of display pictures to download, but only if we're not hidden
|
|
if( pendingDisplayPictures_.count() == 0 || CurrentAccount::instance()->getStatus() == STATUS_INVISIBLE )
|
|
{
|
|
return;
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "handling timed events.";
|
|
#endif
|
|
|
|
// See if there are pictures to download.
|
|
// Get 4 pictures at once.
|
|
int requestCount = pendingDisplayPictures_.count();
|
|
requestCount = requestCount > 4 ? 4 : requestCount;
|
|
while( requestCount > 0 && ! pendingDisplayPictures_.isEmpty() )
|
|
{
|
|
// Unshift the first handle of the list.
|
|
QString handle = pendingDisplayPictures_.first();
|
|
pendingDisplayPictures_.removeAll( handle );
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "see if the display picture of '" << handle << "' should be downloaded.";
|
|
#endif
|
|
|
|
// See if the picture was already downloaded somehow (e.g. user opened a chat)
|
|
|
|
// First get the contact.
|
|
// Using getContactList()->.. because it skips the InvitedContact objectss returned by CurrentAccount::getContactByHandle()
|
|
Contact *contact = CurrentAccount::instance()->getContactList()->getContactByHandle( handle );
|
|
if( contact == 0 )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "contact not found, was it removed?";
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
// See if the contact is still online.
|
|
if( contact->isOffline() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "contact is no longer online.";
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
// See if the contact is still blocked.
|
|
// Avoid starting a chat which "invites" the contact to chat back.
|
|
if( contact->isBlocked() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "contact is blocked.";
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
// Get the msn object, can become null now.
|
|
const MsnObject *msnObject = contact->getMsnObject();
|
|
if( msnObject == 0 )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "MsnObject has been reset.";
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
// See if the object already exists in the cache
|
|
QString objectFileName = KMessConfig::instance()->getMsnObjectFileName( *msnObject );
|
|
if( QFile::exists( objectFileName )
|
|
&& ! QImageReader::imageFormat( objectFileName ).isEmpty()
|
|
&& msnObject->verifyFile( objectFileName ) )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Picture is already downloaded.";
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
|
|
// See if the actual object is currently being downloaded (e.g. user just opened a chat).
|
|
// Put as last check because it creates the ApplicationList on demand.
|
|
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
// Made the debug output easier to understand.
|
|
if( ! contact->hasApplicationList() )
|
|
{
|
|
kDebug() << "Picture is not available, creating application list.";
|
|
}
|
|
#endif
|
|
|
|
// Get the application list
|
|
ApplicationList *appList = getApplicationList( handle );
|
|
if( KMESS_NULL(appList) ) continue;
|
|
|
|
// See if a picture is currently being downloaded.
|
|
if( appList->hasMsnObjectTransfer( *msnObject ) )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "transfer for the picture is already active.";
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
|
|
// All tests passed.
|
|
// Download the picture.
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kDebug() << "Picture is not available, starting switchboard connection to download it.";
|
|
#endif
|
|
|
|
// Start a chat to download the picture.
|
|
// The download will be initiated up by ChatMaster::slotContactJoinedChat().
|
|
emit requestSwitchboard( handle, ChatInformation::CONNECTION_BACKGROUND );
|
|
requestCount--;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#include "chatmaster.moc"
|