2274 lines
70 KiB
C++
2274 lines
70 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/msnobject.h"
|
|
#include "../model/contactlist.h"
|
|
#include "../network/applications/applicationlist.h"
|
|
#include "../network/applications/mimeapplication.h"
|
|
#include "../network/applications/p2papplication.h"
|
|
#include "../network/applications/filetransfer.h"
|
|
#include "../network/applications/filetransferp2p.h"
|
|
#include "../network/applications/inktransferp2p.h"
|
|
#include "../network/applications/msnobjecttransferp2p.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 <QTextDocument>
|
|
#include <QTest>
|
|
|
|
#include <KLocalizedString>
|
|
#include <kwindowsystem.h>
|
|
#include <kx11extras.h>
|
|
|
|
// Settings for debugging
|
|
#define CHATMASTER_SEND_FILES_MSNFTP 0
|
|
|
|
|
|
|
|
// The constructor
|
|
ChatMaster::ChatMaster(QObject *parent)
|
|
: QObject(parent),
|
|
initialized_(false)
|
|
{
|
|
setObjectName("ChatMaster");
|
|
}
|
|
|
|
|
|
|
|
// The destructor
|
|
ChatMaster::~ChatMaster()
|
|
{
|
|
disconnected();
|
|
|
|
// Delete all open windows
|
|
kmDebug() << chatWindows_;
|
|
qDeleteAll( chatWindows_ );
|
|
chatWindows_.clear();
|
|
|
|
// Cleanup pending messages
|
|
qDeleteAll( pendingMimeMessages_ );
|
|
pendingMimeMessages_.clear();
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "DESTROYED.";
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
// The connection has been established
|
|
void ChatMaster::connected()
|
|
{
|
|
// There are no chats to reactivate
|
|
if( chats_.isEmpty() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Trying reactivation of" << chats_.count() << "open chats.";
|
|
#endif
|
|
|
|
QList<ChatWindow*> windowsList;
|
|
|
|
// Go through all the chats and reactivate them
|
|
foreach( Chat *chat, chats_ )
|
|
{
|
|
// Reactivate right away chats which have a connection already. Should never happen though.
|
|
if( chat->getSwitchboardConnection() != 0 )
|
|
{
|
|
chat->setEnabled( true );
|
|
|
|
// Mark this chat's window for reactivation
|
|
if( ! windowsList.contains( chat->getChatWindow() ) )
|
|
{
|
|
windowsList.append( chat->getChatWindow() );
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
const QStringList participants( chat->getParticipants() );
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Reactivating chat:" << participants;
|
|
#endif
|
|
|
|
// Check if the participants list is empty: if so, there's something fishy going on.
|
|
if( participants.isEmpty() )
|
|
{
|
|
kmWarning() << "Tried to reactivate chat with no contacts - has a window?" << ( chat->getChatWindow() != 0 );
|
|
chat->getChatWindow()->removeChatTab( chat );
|
|
continue;
|
|
}
|
|
|
|
// Do not re-enable group chats, as they need a new invitation to work again
|
|
if( participants.count() > 1 )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "This is a group chat, skipping it.";
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
const QString &handle = participants.first();
|
|
|
|
// Only reactivate the chat if this contact is in the connected account's list
|
|
if( currentAccount_->getContactByHandle( handle ) == 0 )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "This chat is with a non-listed contact, closing it.";
|
|
#endif
|
|
chat->getChatWindow()->removeChatTab( chat );
|
|
continue;
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Reactivating chat with listed contact:" << handle;
|
|
#endif
|
|
|
|
// 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, handle );
|
|
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->setUIState( ChatWindow::Connected );
|
|
}
|
|
}
|
|
|
|
|
|
// The user has disconnected, so close all open switchboard connections and disable the chats
|
|
void ChatMaster::disconnected()
|
|
{
|
|
// Don't waste time disabling the chats when exiting.
|
|
KMessApplication *kmessApp = static_cast<KMessApplication *>(QApplication::instance());
|
|
if( kmessApp->quitSelected() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( ! chatWindows_.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "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->setUIState( ChatWindow::Disconnected );
|
|
}
|
|
}
|
|
|
|
// close down all switchboards first.
|
|
if( ! switchboardConnections_.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Closing" << switchboardConnections_.count() << "open switchboards.";
|
|
#endif
|
|
|
|
// give each switchboard a chance to close properly.
|
|
foreach( MsnSwitchboardConnection *conn, switchboardConnections_ )
|
|
{
|
|
conn->closeConnection();
|
|
}
|
|
}
|
|
|
|
if( ! chats_.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Closing" << chats_.count() << "open chats.";
|
|
#endif
|
|
|
|
// Go through all the chats and disable them
|
|
foreach( Chat *chat, chats_ )
|
|
{
|
|
chat->setEnabled( false );
|
|
}
|
|
}
|
|
|
|
// Cleanup switchboard connections.
|
|
qDeleteAll( switchboardConnections_ );
|
|
switchboardConnections_.clear();
|
|
}
|
|
|
|
|
|
|
|
// KMess is establishing a connection.
|
|
void ChatMaster::connecting()
|
|
{
|
|
// There are no chats modify.
|
|
if( chats_.isEmpty() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Disabling reconnect button of" << chats_.count() << "open chats.";
|
|
#endif
|
|
|
|
QList<ChatWindow*> windowsList;
|
|
|
|
// Go through all the chats and add their window.
|
|
foreach( Chat *chat, chats_ )
|
|
{
|
|
if( ! windowsList.contains( chat->getChatWindow() ) )
|
|
{
|
|
windowsList.append( chat->getChatWindow() );
|
|
}
|
|
}
|
|
|
|
// Go through all the windows and disable their reconnect button.
|
|
foreach( ChatWindow *window, windowsList )
|
|
{
|
|
window->setUIState( ChatWindow::Connecting );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Forward a new switchboard server request signal coming from an existing switchboard connection
|
|
void ChatMaster::forwardRequestNewSwitchboard( QString handle )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "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::forwardContactAdded( 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::forwardContactBlocked( 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_->getContactByHandle(handle);
|
|
if(KMESS_NULL(contact)) return 0;
|
|
|
|
if( handle == currentAccount_->getHandle() )
|
|
{
|
|
kmWarning() << "Cannot start applications with yourself.";
|
|
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
|
|
kmDebug() << "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->isContactInChat( handle, true ) )
|
|
{
|
|
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_ )
|
|
{
|
|
kmDebug() << "Already initialized.";
|
|
return false;
|
|
}
|
|
|
|
// Save the current account reference
|
|
currentAccount_ = CurrentAccount::instance();
|
|
|
|
// Get the tabbed chat settings
|
|
chatTabbedMode_ = currentAccount_->getTabbedChatMode();
|
|
|
|
// Connect the contact list
|
|
const ContactList *contactList = currentAccount_->getContactList();
|
|
connect( contactList, SIGNAL( contactChangedMsnObject(Contact*) ),
|
|
this, SLOT ( slotContactChangedMsnObject(Contact*) ) );
|
|
|
|
// Update chat grouping when settings are changed
|
|
connect( currentAccount_, SIGNAL( changedChatStyleSettings() ),
|
|
this, SLOT ( updateChatGrouping() ) );
|
|
// If the user has added him/herself as a contact, we need to update its picture
|
|
// when a new display picture is set
|
|
connect( currentAccount_, SIGNAL( changedMsnObject() ),
|
|
this, SLOT ( slotContactChangedMsnObject() ) );
|
|
|
|
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;
|
|
|
|
// Forbid chatting with yourself
|
|
if( handle == currentAccount_->getHandle() )
|
|
{
|
|
kmWarning() << "Cannot chat with yourself.";
|
|
return;
|
|
}
|
|
|
|
// 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_;
|
|
ContactBase *contact = currentAccount->getContactByHandle( handle );
|
|
|
|
// TODO we shouldn't decide if the chat will be in offline mode,
|
|
// we should see if the CAL return value from SB is 217
|
|
if( contact && contact->getStatus() == STATUS_OFFLINE )
|
|
{
|
|
// Either the contact is offline or invisible.
|
|
// We'll start an offline chat.
|
|
connectionType = ChatInformation::CONNECTION_OFFLINE;
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Starting an offline chat with " << handle;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
// Start a normal chat with an online contact
|
|
connectionType = ChatInformation::CONNECTION_CHAT;
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Requesting a switchboard to chat with " << handle;
|
|
#endif
|
|
}
|
|
|
|
MsnSwitchboardConnection *switchboard = getContactSwitchboardConnection( handle, true );
|
|
if( switchboard != 0 )
|
|
{
|
|
// The user request chat with user that have already opened one SB
|
|
// So, re-use the SB and force the raise of chat!
|
|
createChat( switchboard, true );
|
|
return;
|
|
}
|
|
|
|
// Append the requested chat with handle to the list
|
|
// So when the chat will be created it will be raised
|
|
requestedChats_.append( handle );
|
|
emit requestSwitchboard( handle, connectionType );
|
|
}
|
|
|
|
|
|
|
|
// A chat is closing
|
|
void ChatMaster::slotChatClosing( Chat *chat )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Marking chat as closing.";
|
|
#endif
|
|
|
|
// Make sure this chat window don't receive new messages.
|
|
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
|
|
kmDebug() << "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
|
|
kmDebug() << "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
|
|
kmDebug() << "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
|
|
kmDebug() << "Connecting additional signals for InkTransferP2P.";
|
|
#endif
|
|
|
|
connect( application, SIGNAL( inkReceived(const QString&,const QString&,InkFormat) ),
|
|
this, SLOT( slotGotInkMessage(const QString&,const QString&,InkFormat) ));
|
|
}
|
|
else
|
|
{
|
|
kmWarning() << "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
|
|
kmDebug() << "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
|
|
kmDebug() << "Invalid raising request received; chat is probably already closed.";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
ChatWindow *chatWindow = chat->getChatWindow();
|
|
if( chatWindow == 0 || ! chatWindows_.contains( chatWindow ) )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Invalid raising request received; chat is probably already closed.";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
if( chatWindow->isVisible() && ! force )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Window has focus already.";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// Move the window on the current desktop if it wasn't shown before or if we're forcing its raise
|
|
// TODO: make this optional, maybe the user doesn't want the windows to span over desktops
|
|
if( ! chatWindow->isVisible() || force )
|
|
{
|
|
if( KWindowSystem::isPlatformX11() )
|
|
{
|
|
KX11Extras::setOnDesktop( chatWindow->winId(), KX11Extras::currentDesktop() );
|
|
}
|
|
}
|
|
|
|
// 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( ! force )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Showing minimized window.";
|
|
#endif
|
|
// Required for new chats
|
|
chatWindow->showMinimized();
|
|
return;
|
|
}
|
|
|
|
// Activate the selected tab
|
|
chatWindow->setCurrentChat( chat );
|
|
|
|
// Notify about the new window and force it to show and receive focus. Only use upon user request.
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Forcing window raise.";
|
|
#endif
|
|
|
|
// Show the chat's window
|
|
if( chatWindow->isMinimized() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Window was minimized, showing it.";
|
|
#endif
|
|
chatWindow->showNormal();
|
|
}
|
|
else
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Window was not minimized, showing it again.";
|
|
#endif
|
|
chatWindow->show();
|
|
}
|
|
|
|
// Workaround a bug in KWin: force the window to get focus
|
|
if( chatWindow->windowState() & Qt::WindowMinimized) {
|
|
chatWindow->setWindowState( (chatWindow->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive );
|
|
chatWindow->show();
|
|
}
|
|
|
|
// Set the keyboard focus to the chat window
|
|
chatWindow->raise();
|
|
KWindowSystem::activateWindow( chatWindow->windowHandle() );
|
|
chatWindow->activateWindow();
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @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
|
|
kmDebug() << "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
|
|
kmDebug() << "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
|
|
kmDebug() << "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
|
|
kmDebug() << "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
|
|
kmDebug() << "Delivering application/ink/OIM chat message to chat window.";
|
|
#endif
|
|
|
|
const QString &handle = message.getContactHandle();
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "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
|
|
kmDebug() << "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.
|
|
kmWarning() << "Chat window is closing, suppressing message '" << message.getBody() << "' from '" << handle << "'.";
|
|
}
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
else
|
|
{
|
|
kmDebug() << "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_APP_INVITE:
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Message is important, delivering it.";
|
|
#endif
|
|
break;
|
|
|
|
default:
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "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
|
|
kmDebug() << "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
|
|
kmDebug() << "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;
|
|
|
|
// 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_->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_->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( Chat *chatUpdate, chats_ )
|
|
{
|
|
if( chatUpdate->isContactInChat( handle ) )
|
|
{
|
|
chatUpdate->updateCustomEmoticon( handle, emoticonCode );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MsnObject::BACKGROUND:
|
|
// handle background transfers
|
|
|
|
default:
|
|
kmWarning() << "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 )
|
|
{
|
|
// Avoid trying to download our own display picture, if the user has added him/herself;
|
|
// instead simply use the current one.
|
|
// This method can be called to update the user's own picture in list from two places:
|
|
// 1)on login when the contact is first received, 2)when the user changes the display pic.
|
|
if( contact == 0 )
|
|
{
|
|
contact = currentAccount_->getContactList()->getContactByHandle( currentAccount_->getHandle() );
|
|
|
|
// When the connection starts, this method gets called
|
|
// but the contact list is not ready yet. Cancel.
|
|
if( contact == 0 )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
if( contact->getHandle() == currentAccount_->getHandle() )
|
|
{
|
|
contact->getExtension()->setContactPicturePath( currentAccount_->getPicturePath() );
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Updated user's own display picture";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
QString handle( contact->getHandle() );
|
|
|
|
// If the contact no longer likes to display an msn object, remove it
|
|
if( contact->getMsnObject() == 0 )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Contact '" << handle << "' removed its MSN object";
|
|
#endif
|
|
|
|
contact->getExtension()->setContactPicturePath( QString() );
|
|
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
|
|
kmDebug() << "Picture for '" << handle << "' is already downloaded.";
|
|
#endif
|
|
|
|
contact->getExtension()->setContactPicturePath( objectFileName );
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "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
|
|
kmWarning() << "Corrupt picture was already set, "
|
|
<< "resetting contact picture." << endl;
|
|
#endif
|
|
contact->getExtension()->setContactPicturePath( QString() );
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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
|
|
kmDebug() << "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
|
|
kmDebug() << "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( ContactBase *contact )
|
|
{
|
|
// Get switchboard connection from slot sender
|
|
MsnSwitchboardConnection *connection = static_cast<MsnSwitchboardConnection*>( const_cast<QObject*>( sender() ) );
|
|
if(KMESS_NULL(connection)) return;
|
|
|
|
QString handle( contact->getHandle() );
|
|
|
|
// 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
|
|
kmDebug() << "Contact '" << handle << "' joined multi-chat, not sending pending messages.";
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "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 *fullContact = currentAccount_->getContactList()->getContactByHandle( handle );
|
|
if( fullContact != 0 && fullContact->hasP2PSupport() )
|
|
{
|
|
if( fullContact->getMsnObject() != 0 )
|
|
{
|
|
startMsnObjectDownload( fullContact->getHandle(), fullContact->getMsnObject(), 0 );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief Deliver an application command to the correct application.
|
|
*/
|
|
void ChatMaster::slotDeliverAppCommand(QString cookie, QString handle, QString command)
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Delivering application command.";
|
|
#endif
|
|
|
|
Application *app = 0;
|
|
|
|
// Get the contact.
|
|
ContactBase *contact = currentAccount_->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 )
|
|
{
|
|
kmWarning() << "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
|
|
kmDebug() << "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
|
|
kmDebug() << "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
|
|
kmDebug() << "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 &ink, const QString &contactHandle, InkFormat format )
|
|
{
|
|
#ifdef KMESSDEBUG_SWITCHBOARD_GENERAL
|
|
switch( format )
|
|
{
|
|
case FORMAT_ISF: kmDebug() << "Parsing ISF ink message"; break;
|
|
case FORMAT_GIF: kmDebug() << "Parsing GIF ink message"; break;
|
|
}
|
|
#endif
|
|
|
|
// Get the contact data for the shown messages
|
|
QString friendlyName;
|
|
QString contactPicture;
|
|
const ContactBase *contact = currentAccount_->getContactByHandle( contactHandle );
|
|
if( contact != 0 )
|
|
{
|
|
friendlyName = contact->getFriendlyName( STRING_ORIGINAL );
|
|
contactPicture = contact->getContactPicturePath();
|
|
}
|
|
|
|
#if KMESS_ENABLE_INK == 0
|
|
// Receiving Ink is supported only for GIF, as GIF reading is always built-in into Qt
|
|
// and KHTML can show GIF images without any extra libs
|
|
if( format != FORMAT_GIF )
|
|
{
|
|
showSpecialMessage( ChatMessage( ChatMessage::TYPE_SYSTEM,
|
|
ChatMessage::CONTENT_SYSTEM_ERROR,
|
|
true,
|
|
i18nc( "Error message shown in chat, %1 is the name of the contact",
|
|
"You received an handwritten message from %1, "
|
|
"but it could not be displayed. This version of "
|
|
"KMess was built without ISF support.", friendlyName ),
|
|
contactHandle,
|
|
friendlyName ) );
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
|
|
// Validate the incoming data
|
|
QString inkData = ink.trimmed();
|
|
QRegExp inkChars( "([^A-Za-z0-9:+/=])" );
|
|
if( ! inkData.startsWith( "base64:" ) || inkChars.indexIn( inkData ) != -1 )
|
|
{
|
|
kmWarning() << "Received invalid ink message from" << contactHandle << ", found char 0x" << QString::number( inkChars.cap(1)[0].unicode(), 16 ) << "at position" << inkChars.pos(1);
|
|
kmWarning() << "Ink data dump:" << inkData;
|
|
|
|
showSpecialMessage( ChatMessage( ChatMessage::TYPE_SYSTEM,
|
|
ChatMessage::CONTENT_SYSTEM_ERROR,
|
|
true,
|
|
i18nc( "Error message shown in chat, %1 is the name of the contact",
|
|
"You received an handwritten message from %1, "
|
|
"but it could not be displayed. The data could "
|
|
"not be read.", friendlyName ),
|
|
contactHandle,
|
|
friendlyName ) );
|
|
return;
|
|
}
|
|
|
|
QString imageMimeType;
|
|
QByteArray inkEncodedData( inkData.toLatin1() );
|
|
inkEncodedData.replace( "base64:", "" );
|
|
|
|
switch( format )
|
|
{
|
|
case FORMAT_ISF:
|
|
{
|
|
#if KMESS_ENABLE_INK == 1
|
|
// ISF messages need to be base64-decoded, converted to a picture readable by KHTML,
|
|
// then base64-encoded again.
|
|
// Since I'd rather avoid using giflib to encode ISF data into pictures, I'll use PNG.
|
|
imageMimeType = "image/png";
|
|
|
|
Isf::Drawing drawing( Isf::Stream::reader( inkEncodedData, true /* fromBase64 */ ) );
|
|
inkEncodedData = Isf::Stream::writerPng( drawing, true );
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
case FORMAT_GIF:
|
|
// Yes, this is it, just set the GIF mimetype. The data is already
|
|
// there in the inkEncodedData variable, and KHTML can show inline
|
|
// gif images right away
|
|
imageMimeType = "image/gif";
|
|
break;
|
|
}
|
|
|
|
// Use the ink data as inline image
|
|
|
|
// Cast the sender object to determine if the ink is received 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 important to determine what switchboard
|
|
// is used, to find the correct chat window where to show 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:" + imageMimeType + ";"
|
|
"base64," + QString::fromLatin1( inkEncodedData ).toHtmlEscaped() + "\">",
|
|
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_->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( currentAccount_->getContactFriendlyNameByHandle( handle, STRING_CLEANED_ESCAPED ) );
|
|
|
|
// 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
|
|
kmDebug() << "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
|
|
kmDebug() << "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
|
|
kmDebug() << "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
|
|
kmDebug() << "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
|
|
kmDebug() << "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_->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 = 0;
|
|
const QString &handle = chatInfo.getContactHandle();
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Starting new switchboard session with" << handle;
|
|
#endif
|
|
|
|
/**
|
|
* Refresh requests are made by expired/disconnected switchboard sessions, and they are refreshed
|
|
* with the new connection data.
|
|
* All other types of chat request create a new switchboard session from the chat info.
|
|
* We could attach the newly created SB to a chat window, but we don't know what kind of chat it
|
|
* may be at this point (the list of initial participants will be received later, when the SB
|
|
* connection will be established). Therefore, we just don't attach it to any chat, and so we
|
|
* posticipate the decision to later.
|
|
* (Also remember that you may have more than one chat with a contact, a 1-on-1, a group one,
|
|
* and a data transfer one)
|
|
*/
|
|
if( chatInfo.getType() == ChatInformation::CONNECTION_REFRESH )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Finding the session to refresh.";
|
|
#endif
|
|
foreach( MsnSwitchboardConnection *currentSwitchboard, switchboardConnections_ )
|
|
{
|
|
if( currentSwitchboard->getContactsInChat().contains( handle )
|
|
&& currentSwitchboard->isWaiting() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Session found, it will be refreshed.";
|
|
#endif
|
|
switchboard = currentSwitchboard;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( switchboard == 0 )
|
|
{
|
|
kmWarning() << "Unable to find which session requested a refresh.";
|
|
return 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switchboard = createSwitchboardConnection();
|
|
if( KMESS_NULL(switchboard) ) return 0;
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Creating new session.";
|
|
#endif
|
|
}
|
|
|
|
// 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( handle ) )
|
|
{
|
|
pendingDisplayPictures_.removeAll( handle );
|
|
}
|
|
|
|
// Connect to the new server
|
|
switchboard->start( chatInfo );
|
|
|
|
Chat *chat = getChatBySwitchboard( switchboard );
|
|
if( chat )
|
|
{
|
|
chat->setEnabled( true );
|
|
}
|
|
|
|
return switchboard;
|
|
}
|
|
|
|
|
|
|
|
// Start a chat with the information gathered from a switchboard connection
|
|
Chat *ChatMaster::createChat( MsnSwitchboardConnection *switchboard, bool requestedByUser )
|
|
{
|
|
#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
|
|
const QStringList partecipants( switchboard->getContactsInChat() );
|
|
newChat = getContactsChat( partecipants, true );
|
|
|
|
// If the chat was requested by a contact, check if we have a chat request
|
|
// pending for that contact and remove it
|
|
if( ! requestedByUser && partecipants.count() < 2 )
|
|
{
|
|
const QString& handle = partecipants.first();
|
|
// Also change the request source to the user if we asked a chat with this contact first
|
|
requestedByUser = requestedChats_.contains( handle );
|
|
if( requestedByUser )
|
|
{
|
|
// Remove the request
|
|
requestedChats_.removeAll( handle );
|
|
}
|
|
}
|
|
|
|
if( newChat != 0 )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "A chat with contacts"
|
|
<< switchboard->getContactsInChat().join(",") << "already exists, raising it." << endl;
|
|
#endif
|
|
|
|
// The chat may have been disabled, reenable it just in case.
|
|
// Seen happening with group chats where you get reinvited to after a reconnection.
|
|
newChat->setEnabled( true );
|
|
|
|
// Delete the previous switchboard ( if it's different from the current one ) and attach the new
|
|
MsnSwitchboardConnection *oldConnection = newChat->getSwitchboardConnection();
|
|
if( oldConnection != switchboard )
|
|
{
|
|
newChat->setSwitchboardConnection( switchboard );
|
|
slotSwitchboardDelete( oldConnection, true );
|
|
}
|
|
|
|
// 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, requestedByUser );
|
|
return newChat;
|
|
}
|
|
|
|
// Create the chat widget
|
|
newChat = new Chat();
|
|
|
|
if( newChat == 0 )
|
|
{
|
|
kmDebug() << "Couldn't create a new chat tab.";
|
|
return 0;
|
|
}
|
|
|
|
// Initialize it
|
|
if( ! newChat->initialize( switchboard ) )
|
|
{
|
|
kmDebug() << "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, switchboard->getUserStartedChat() );
|
|
if( newChat == 0 )
|
|
{
|
|
kmWarning() << "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( contactAllowed(QString) ),
|
|
this, SIGNAL( allowContact(QString) ) );
|
|
connect( newChat, SIGNAL( addContact(QString) ),
|
|
this, SIGNAL( addContact(QString) ) );
|
|
connect( newChat, SIGNAL( contactAdded(QString,bool) ),
|
|
this, SLOT ( forwardContactAdded(QString,bool) ) );
|
|
connect( newChat, SIGNAL( contactBlocked(QString,bool) ),
|
|
this, SLOT ( forwardContactBlocked(QString,bool) ) );
|
|
connect( newChat, SIGNAL( startPrivateChat(const QString&) ),
|
|
this, SLOT ( requestChat(QString) ) );
|
|
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( closing(Chat*) ),
|
|
this, SLOT ( slotChatClosing(Chat*) ) );
|
|
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, requestedByUser );
|
|
|
|
return newChat;
|
|
}
|
|
|
|
|
|
|
|
// Create a new chat window or retrieve an existing one if tabbed chatting is enabled
|
|
ChatWindow *ChatMaster::createChatWindow( Chat *chat )
|
|
{
|
|
// Search a window for the chat according to the users grouping settings
|
|
ChatWindow *window = findWindowForChat( chat );
|
|
|
|
// Found a window to use, return that
|
|
if( window != 0 )
|
|
{
|
|
return window;
|
|
}
|
|
|
|
// Create the new chat window
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "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() )
|
|
{
|
|
kmWarning() << "Couldn't initialize the new chat window!";
|
|
return 0;
|
|
}
|
|
|
|
// Connect its signals
|
|
connect( window, SIGNAL( destroyed(QObject*) ),
|
|
this, SLOT ( slotChatWindowDestroyed(QObject*) ) );
|
|
connect( window, SIGNAL( reconnect() ),
|
|
this, SIGNAL( reconnect() ) );
|
|
|
|
// 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
|
|
kmDebug() << "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 ) )
|
|
{
|
|
kmWarning() << "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(ContactBase*) ),
|
|
this, SLOT ( slotContactJoinedChat(ContactBase*) ));
|
|
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&,InkFormat) ),
|
|
this, SLOT ( slotGotInkMessage(const QString&, const QString&,InkFormat) ));
|
|
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_->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() ) )
|
|
{
|
|
// kmWarning() << "Attempted to start a file transfer with an offline contact!";
|
|
return;
|
|
}
|
|
|
|
if( contact == 0 || ! contact->hasP2PSupport() )
|
|
{
|
|
// This should be so rare that it justifies a warning message.
|
|
if( contact == 0 )
|
|
{
|
|
kmWarning() << "Contact" << handle << "object not found, using old MIME invitations because P2P might not be supported.";
|
|
}
|
|
else
|
|
{
|
|
kmWarning() << "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);
|
|
return;
|
|
}
|
|
|
|
#if CHATMASTER_SEND_FILES_MSNFTP
|
|
kmWarning() << "Sending files is forced over MSNFTP for debugging, using old MIME invitations.";
|
|
MimeApplication *app = new FileTransfer(handle, filename);
|
|
startApplication(app);
|
|
#else
|
|
// The contact supports file transfer over MSNP2P
|
|
P2PApplication *app = new FileTransferP2P(appList, filename);
|
|
startApplication(app);
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
// Start a chat and send a file to the contact
|
|
void ChatMaster::startChatAndFileTransfer( const QString &handle, const QString &filename )
|
|
{
|
|
requestChat( handle );
|
|
int i = 0;
|
|
|
|
// FIXME: Workaround needed, because the filetransfer is "lost" when the Switchboard is not open yet.
|
|
// suggested workarround: passing an "initial action" to requestChat().
|
|
while( !getContactSwitchboardConnection( handle, true ) && i++ < 75 )
|
|
{
|
|
QTest::qWait(200);
|
|
}
|
|
|
|
startFileTransfer( handle, filename );
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
kmDebug() << "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. ( TODO this step is skipped for WINK for the moment )
|
|
if( fileExists && msnObject->getType() != MsnObject::WINK )
|
|
{
|
|
bool imageCorrupt = QImageReader::imageFormat( objectFileName ).isEmpty();
|
|
if( imageCorrupt || ! msnObject->verifyFile( objectFileName ) )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "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
|
|
kmDebug() << "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
|
|
kmDebug() << "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_->getStatus() == STATUS_INVISIBLE )
|
|
{
|
|
return;
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "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
|
|
kmDebug() << "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_->getContactList()->getContactByHandle( handle );
|
|
if( contact == 0 )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "contact not found, was it removed?";
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
// See if the contact is still online.
|
|
if( contact->isOffline() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "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
|
|
kmDebug() << "contact is blocked.";
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
// Get the msn object, can become null now.
|
|
const MsnObject *msnObject = contact->getMsnObject();
|
|
if( msnObject == 0 )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "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
|
|
kmDebug() << "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() )
|
|
{
|
|
kmDebug() << "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
|
|
kmDebug() << "transfer for the picture is already active.";
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
|
|
// All tests passed.
|
|
// Download the picture.
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "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--;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Update the grouping of chats/tabs.
|
|
void ChatMaster::updateChatGrouping()
|
|
{
|
|
if( currentAccount_->getTabbedChatMode() == chatTabbedMode_ )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Chat grouping mode not changed, nothing to update";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Updating Chat grouping";
|
|
#endif
|
|
chatTabbedMode_ = currentAccount_->getTabbedChatMode();
|
|
|
|
if( chatWindows_.empty() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "No ChatWindows, nothing to update";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
ChatWindow *currentWindow;
|
|
ChatWindow *window;
|
|
|
|
foreach( Chat *chat, chats_ )
|
|
{
|
|
currentWindow = chat->getChatWindow();
|
|
window = findWindowForChat( chat);
|
|
|
|
if( window == 0)
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "No suited window found for chat" << chat;
|
|
#endif
|
|
|
|
ChatWindow* newChatWindow = createChatWindow( chat);
|
|
|
|
newChatWindow->addChatTab( chat, false );
|
|
currentWindow->removeChatTab( chat);
|
|
currentWindow->checkAndCloseWindow();
|
|
|
|
raiseChat( chat, false );
|
|
}
|
|
else if( window != currentWindow )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "Moving chat" << chat << "to other window" << window;
|
|
#endif
|
|
|
|
window->addChatTab( chat, false );
|
|
currentWindow->removeChatTab( chat);
|
|
currentWindow->checkAndCloseWindow();
|
|
|
|
raiseChat( chat, false );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Search the appropriate window for a chat.
|
|
ChatWindow *ChatMaster::findWindowForChat( Chat *chat )
|
|
{
|
|
ChatWindow *window = 0;
|
|
ChatWindow *otherGroupWindow = 0;
|
|
CurrentAccount *currentAccount = currentAccount_;
|
|
|
|
if( ! chatWindows_.empty() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATMASTER
|
|
kmDebug() << "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
|
|
kmDebug() << "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
|
|
kmDebug() << "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
|
|
kmDebug() << "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
|
|
kmDebug() << "Match found!";
|
|
#endif
|
|
window = chatItem->getChatWindow();
|
|
|
|
// We found a possibly suited window.
|
|
// Check if it already displays tabs of another group.
|
|
// If so, we can't use it.
|
|
if( otherGroupWindow == window )
|
|
{
|
|
window = 0;
|
|
}
|
|
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// Keep track of the window used by another group.
|
|
otherGroupWindow = chatItem->getChatWindow();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return window;
|
|
}
|