summaryrefslogtreecommitdiffstats
path: root/kopete/protocols/oscar/liboscar/client.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kopete/protocols/oscar/liboscar/client.cpp')
-rw-r--r--kopete/protocols/oscar/liboscar/client.cpp1353
1 files changed, 1353 insertions, 0 deletions
diff --git a/kopete/protocols/oscar/liboscar/client.cpp b/kopete/protocols/oscar/liboscar/client.cpp
new file mode 100644
index 00000000..af967512
--- /dev/null
+++ b/kopete/protocols/oscar/liboscar/client.cpp
@@ -0,0 +1,1353 @@
+/*
+ client.cpp - Kopete Oscar Protocol
+
+ Copyright (c) 2004-2005 Matt Rogers <mattr@kde.org>
+
+ Based on code Copyright (c) 2004 SuSE Linux AG <http://www.suse.com>
+ Based on Iris, Copyright (C) 2003 Justin Karneges
+
+ Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org>
+
+ *************************************************************************
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Lesser General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ *************************************************************************
+*/
+
+#include "client.h"
+
+#include <qtimer.h>
+#include <qtextcodec.h>
+
+#include <kdebug.h> //for kdDebug()
+#include <klocale.h>
+
+#include "buddyicontask.h"
+#include "clientreadytask.h"
+#include "connectionhandler.h"
+#include "changevisibilitytask.h"
+#include "chatnavservicetask.h"
+#include "errortask.h"
+#include "icquserinfo.h"
+#include "icquserinfotask.h"
+#include "logintask.h"
+#include "connection.h"
+#include "messagereceivertask.h"
+#include "onlinenotifiertask.h"
+#include "oscarclientstream.h"
+#include "oscarconnector.h"
+#include "oscarsettings.h"
+#include "oscarutils.h"
+#include "ownuserinfotask.h"
+#include "profiletask.h"
+#include "senddcinfotask.h"
+#include "sendmessagetask.h"
+#include "serverredirecttask.h"
+#include "servicesetuptask.h"
+#include "ssimanager.h"
+#include "ssimodifytask.h"
+#include "ssiauthtask.h"
+#include "offlinemessagestask.h"
+#include "task.h"
+#include "typingnotifytask.h"
+#include "userinfotask.h"
+#include "usersearchtask.h"
+#include "warningtask.h"
+#include "chatservicetask.h"
+#include "rateclassmanager.h"
+
+
+namespace
+{
+ class DefaultCodecProvider : public Client::CodecProvider
+ {
+ public:
+ virtual QTextCodec* codecForContact( const QString& ) const
+ {
+ return QTextCodec::codecForMib( 4 );
+ }
+ virtual QTextCodec* codecForAccount() const
+ {
+ return QTextCodec::codecForMib( 4 );
+ }
+ };
+
+ DefaultCodecProvider defaultCodecProvider;
+}
+
+class Client::ClientPrivate
+{
+public:
+ ClientPrivate() {}
+
+ QString host, user, pass;
+ uint port;
+ int tzoffset;
+ bool active;
+
+ enum { StageOne, StageTwo };
+ int stage;
+
+ //Protocol specific data
+ bool isIcq;
+ bool redirectRequested;
+ QValueList<WORD> redirectionServices;
+ WORD currentRedirect;
+ QByteArray cookie;
+ DWORD connectAsStatus; // icq only
+ QString connectWithMessage; // icq only
+ Oscar::Settings* settings;
+
+ //Tasks
+ ErrorTask* errorTask;
+ OnlineNotifierTask* onlineNotifier;
+ OwnUserInfoTask* ownStatusTask;
+ MessageReceiverTask* messageReceiverTask;
+ SSIAuthTask* ssiAuthTask;
+ ICQUserInfoRequestTask* icqInfoTask;
+ UserInfoTask* userInfoTask;
+ TypingNotifyTask * typingNotifyTask;
+ SSIModifyTask* ssiModifyTask;
+ //Managers
+ SSIManager* ssiManager;
+ ConnectionHandler connections;
+
+ //Our Userinfo
+ UserDetails ourDetails;
+
+ //Infos
+ QValueList<int> exchanges;
+
+ QString statusMessage; // for away-,DND-message etc...
+
+ //away messages
+ struct AwayMsgRequest
+ {
+ QString contact;
+ ICQStatus contactStatus;
+ };
+ QValueList<AwayMsgRequest> awayMsgRequestQueue;
+ QTimer* awayMsgRequestTimer;
+ CodecProvider* codecProvider;
+
+ const Oscar::ClientVersion* version;
+};
+
+Client::Client( QObject* parent )
+:QObject( parent, "oscarclient" )
+{
+ m_loginTask = 0L;
+ m_loginTaskTwo = 0L;
+
+ d = new ClientPrivate;
+ d->tzoffset = 0;
+ d->active = false;
+ d->isIcq = false; //default to AIM
+ d->redirectRequested = false;
+ d->currentRedirect = 0;
+ d->connectAsStatus = 0x0; // default to online
+ d->ssiManager = new SSIManager( this );
+ d->settings = new Oscar::Settings();
+ d->errorTask = 0L;
+ d->onlineNotifier = 0L;
+ d->ownStatusTask = 0L;
+ d->messageReceiverTask = 0L;
+ d->ssiAuthTask = 0L;
+ d->icqInfoTask = 0L;
+ d->userInfoTask = 0L;
+ d->stage = ClientPrivate::StageOne;
+ d->typingNotifyTask = 0L;
+ d->ssiModifyTask = 0L;
+ d->awayMsgRequestTimer = new QTimer();
+ d->codecProvider = &defaultCodecProvider;
+
+ connect( this, SIGNAL( redirectionFinished( WORD ) ),
+ this, SLOT( checkRedirectionQueue( WORD ) ) );
+ connect( d->awayMsgRequestTimer, SIGNAL( timeout() ),
+ this, SLOT( nextICQAwayMessageRequest() ) );
+}
+
+Client::~Client()
+{
+
+ //delete the connections differently than in deleteConnections()
+ //deleteLater() seems to cause destruction order issues
+ deleteStaticTasks();
+ delete d->settings;
+ delete d->ssiManager;
+ delete d->awayMsgRequestTimer;
+ delete d;
+}
+
+Oscar::Settings* Client::clientSettings() const
+{
+ return d->settings;
+}
+
+void Client::connectToServer( Connection *c, const QString& server, bool auth )
+{
+ d->connections.append( c );
+ if ( auth == true )
+ {
+ m_loginTask = new StageOneLoginTask( c->rootTask() );
+ connect( m_loginTask, SIGNAL( finished() ), this, SLOT( lt_loginFinished() ) );
+ }
+
+ connect( c, SIGNAL( socketError( int, const QString& ) ), this, SLOT( determineDisconnection( int, const QString& ) ) );
+ c->connectToServer(server, auth);
+}
+
+void Client::start( const QString &host, const uint port, const QString &userId, const QString &pass )
+{
+ Q_UNUSED( host );
+ Q_UNUSED( port );
+ d->user = userId;
+ d->pass = pass;
+ d->stage = ClientPrivate::StageOne;
+ d->active = false;
+}
+
+void Client::close()
+{
+ d->active = false;
+ d->awayMsgRequestTimer->stop();
+ d->awayMsgRequestQueue.clear();
+ d->connections.clear();
+ deleteStaticTasks();
+
+ //don't clear the stored status between stage one and two
+ if ( d->stage == ClientPrivate::StageTwo )
+ {
+ d->connectAsStatus = 0x0;
+ d->connectWithMessage = QString::null;
+ }
+
+ d->exchanges.clear();
+ d->redirectRequested = false;
+ d->currentRedirect = 0;
+ d->redirectionServices.clear();
+ d->ssiManager->clear();
+}
+
+void Client::setStatus( AIMStatus status, const QString &_message )
+{
+ // AIM: you're away exactly when your away message isn't empty.
+ // can't use QString::null as a message either; ProfileTask
+ // interprets null as "don't change".
+ QString message;
+ if ( status == Online )
+ message = QString::fromAscii("");
+ else
+ {
+ if ( _message.isEmpty() )
+ message = QString::fromAscii(" ");
+ else
+ message = _message;
+ }
+
+ Connection* c = d->connections.connectionForFamily( 0x0002 );
+ if ( !c )
+ return;
+ ProfileTask* pt = new ProfileTask( c->rootTask() );
+ pt->setAwayMessage( message );
+ pt->go( true );
+}
+
+void Client::setStatus( DWORD status, const QString &message )
+{
+ // remember the message to reply with, when requested
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Setting status message to "<< message << endl;
+ d->statusMessage = message;
+ // ICQ: if we're active, set status. otherwise, just store the status for later.
+ if ( d->active )
+ {
+ //the first connection is always the BOS connection
+ Connection* c = d->connections.connectionForFamily( 0x0013 );
+ if ( !c )
+ return; //TODO trigger an error of some sort?
+
+ ChangeVisibilityTask* cvt = new ChangeVisibilityTask( c->rootTask() );
+ if ( ( status & 0x0100 ) == 0x0100 )
+ {
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Setting invisible" << endl;
+ cvt->setVisible( false );
+ }
+ else
+ {
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Setting visible" << endl;
+ cvt->setVisible( true );
+ }
+ cvt->go( true );
+ c = d->connections.connectionForFamily( 0x0002 );
+ if ( !c )
+ return;
+
+ SendDCInfoTask* sdcit = new SendDCInfoTask( c->rootTask(), status );
+ sdcit->go( true ); //autodelete
+ // TODO: send away message
+ }
+ else
+ {
+ d->connectAsStatus = status;
+ d->connectWithMessage = message;
+ }
+}
+
+UserDetails Client::ourInfo() const
+{
+ return d->ourDetails;
+}
+
+QString Client::host()
+{
+ return d->host;
+}
+
+int Client::port()
+{
+ return d->port;
+}
+
+SSIManager* Client::ssiManager() const
+{
+ return d->ssiManager;
+}
+
+const Oscar::ClientVersion* Client::version() const
+{
+ return d->version;
+}
+
+// SLOTS //
+
+void Client::streamConnected()
+{
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << endl;
+ d->stage = ClientPrivate::StageTwo;
+ if ( m_loginTaskTwo )
+ m_loginTaskTwo->go();
+}
+
+void Client::lt_loginFinished()
+{
+ /* Check for stage two login first, since we create the stage two
+ * task when we finish stage one
+ */
+ if ( d->stage == ClientPrivate::StageTwo )
+ {
+ //we've finished logging in. start the services setup
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "stage two done. setting up services" << endl;
+ initializeStaticTasks();
+ ServiceSetupTask* ssTask = new ServiceSetupTask( d->connections.defaultConnection()->rootTask() );
+ connect( ssTask, SIGNAL( finished() ), this, SLOT( serviceSetupFinished() ) );
+ ssTask->go( true ); //fire and forget
+ m_loginTaskTwo->deleteLater();
+ m_loginTaskTwo = 0;
+ }
+ else if ( d->stage == ClientPrivate::StageOne )
+ {
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "stage one login done" << endl;
+ disconnect( m_loginTask, SIGNAL( finished() ), this, SLOT( lt_loginFinished() ) );
+
+ if ( m_loginTask->statusCode() == 0 ) //we can start stage two
+ {
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "no errors from stage one. moving to stage two" << endl;
+
+ //cache these values since they'll be deleted when we close the connections (which deletes the tasks)
+ d->host = m_loginTask->bosServer();
+ d->port = m_loginTask->bosPort().toUInt();
+ d->cookie = m_loginTask->loginCookie();
+ close();
+ QTimer::singleShot( 100, this, SLOT(startStageTwo() ) );
+ }
+ else
+ {
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "errors reported. not moving to stage two" << endl;
+ close(); //deletes the connections for us
+ }
+
+ m_loginTask->deleteLater();
+ m_loginTask = 0;
+ }
+
+}
+
+void Client::startStageTwo()
+{
+ //create a new connection and set it up
+ Connection* c = createConnection( d->host, QString::number( d->port ) );
+ new CloseConnectionTask( c->rootTask() );
+
+ //create the new login task
+ m_loginTaskTwo = new StageTwoLoginTask( c->rootTask() );
+ m_loginTaskTwo->setCookie( d->cookie );
+ QObject::connect( m_loginTaskTwo, SIGNAL( finished() ), this, SLOT( lt_loginFinished() ) );
+
+
+ //connect
+ QObject::connect( c, SIGNAL( connected() ), this, SLOT( streamConnected() ) );
+ connectToServer( c, d->host, false ) ;
+
+}
+
+void Client::serviceSetupFinished()
+{
+ d->active = true;
+
+ if ( isIcq() )
+ setStatus( d->connectAsStatus, d->connectWithMessage );
+
+ d->ownStatusTask->go();
+
+ if ( isIcq() )
+ {
+ //retrieve offline messages
+ Connection* c = d->connections.connectionForFamily( 0x0015 );
+ if ( !c )
+ return;
+
+ OfflineMessagesTask *offlineMsgTask = new OfflineMessagesTask( c->rootTask() );
+ connect( offlineMsgTask, SIGNAL( receivedOfflineMessage(const Oscar::Message& ) ),
+ this, SIGNAL( messageReceived(const Oscar::Message& ) ) );
+ offlineMsgTask->go( true );
+ }
+
+ emit haveSSIList();
+ emit loggedIn();
+}
+
+void Client::receivedIcqInfo( const QString& contact, unsigned int type )
+{
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "received icq info for " << contact
+ << " of type " << type << endl;
+
+ if ( type == ICQUserInfoRequestTask::Short )
+ emit receivedIcqShortInfo( contact );
+ else
+ emit receivedIcqLongInfo( contact );
+}
+
+void Client::receivedInfo( Q_UINT16 sequence )
+{
+ UserDetails details = d->userInfoTask->getInfoFor( sequence );
+ emit receivedUserInfo( details.userId(), details );
+}
+
+void Client::offlineUser( const QString& user, const UserDetails& )
+{
+ emit userIsOffline( user );
+}
+
+void Client::haveOwnUserInfo()
+{
+ kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << endl;
+ UserDetails ud = d->ownStatusTask->getInfo();
+ d->ourDetails = ud;
+ emit haveOwnInfo();
+}
+
+void Client::setCodecProvider( Client::CodecProvider* codecProvider )
+{
+ d->codecProvider = codecProvider;
+}
+
+void Client::setVersion( const Oscar::ClientVersion* version )
+{
+ d->version = version;
+}
+
+// INTERNALS //
+
+QString Client::userId() const
+{
+ return d->user;
+}
+
+QString Client::password() const
+{
+ return d->pass;
+}
+
+QString Client::statusMessage() const
+{
+ return d->statusMessage;
+}
+
+void Client::setStatusMessage( const QString &message )
+{
+ d->statusMessage = message;
+}
+
+QCString Client::ipAddress() const
+{
+ //!TODO determine ip address
+ return "127.0.0.1";
+}
+
+void Client::notifyTaskError( const Oscar::SNAC& s, int errCode, bool fatal )
+{
+ emit taskError( s, errCode, fatal );
+}
+
+void Client::notifySocketError( int errCode, const QString& msg )
+{
+ emit socketError( errCode, msg );
+}
+
+void Client::sendMessage( const Oscar::Message& msg, bool isAuto)
+{
+ Connection* c = 0L;
+ if ( msg.type() == 0x0003 )
+ {
+ c = d->connections.connectionForChatRoom( msg.exchange(), msg.chatRoom() );
+ if ( !c )
+ return;
+
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "sending message to chat room" << endl;
+ ChatServiceTask* cst = new ChatServiceTask( c->rootTask(), msg.exchange(), msg.chatRoom() );
+ cst->setMessage( msg );
+ cst->setEncoding( d->codecProvider->codecForAccount()->name() );
+ cst->go( true );
+ }
+ else
+ {
+ c = d->connections.connectionForFamily( 0x0004 );
+ if ( !c )
+ return;
+ SendMessageTask *sendMsgTask = new SendMessageTask( c->rootTask() );
+ // Set whether or not the message is an automated response
+ sendMsgTask->setAutoResponse( isAuto );
+ sendMsgTask->setMessage( msg );
+ sendMsgTask->go( true );
+ }
+}
+
+void Client::receivedMessage( const Oscar::Message& msg )
+{
+ if ( msg.type() == 2 && !msg.hasProperty( Oscar::Message::AutoResponse ) )
+ {
+ // type 2 message needs an autoresponse, regardless of type
+ Connection* c = d->connections.connectionForFamily( 0x0004 );
+ if ( !c )
+ return;
+
+ Oscar::Message response ( msg );
+ if ( msg.hasProperty( Oscar::Message::StatusMessageRequest ) )
+ {
+ QTextCodec* codec = d->codecProvider->codecForContact( msg.sender() );
+ response.setText( Oscar::Message::UserDefined, statusMessage(), codec );
+ }
+ else
+ {
+ response.setEncoding( Oscar::Message::UserDefined );
+ response.setTextArray( QByteArray() );
+ }
+ response.setReceiver( msg.sender() );
+ response.addProperty( Oscar::Message::AutoResponse );
+ SendMessageTask *sendMsgTask = new SendMessageTask( c->rootTask() );
+ sendMsgTask->setMessage( response );
+ sendMsgTask->go( true );
+ }
+ if ( msg.hasProperty( Oscar::Message::StatusMessageRequest ) )
+ {
+ if ( msg.hasProperty( Oscar::Message::AutoResponse ) )
+ {
+ // we got a response to a status message request.
+ QString awayMessage( msg.text( d->codecProvider->codecForContact( msg.sender() ) ) );
+ kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Received an away message: " << awayMessage << endl;
+ emit receivedAwayMessage( msg.sender(), awayMessage );
+ }
+ }
+ else if ( ! msg.hasProperty( Oscar::Message::AutoResponse ) )
+ {
+ // Filter out miranda's invisible check
+ if ( msg.messageType() == 0x0004 && msg.textArray().isEmpty() )
+ return;
+
+ // let application handle it
+ kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Emitting receivedMessage" << endl;
+ emit messageReceived( msg );
+ }
+}
+
+void Client::requestAuth( const QString& contactid, const QString& reason )
+{
+ Connection* c = d->connections.connectionForFamily( 0x0013 );
+ if ( !c )
+ return;
+ d->ssiAuthTask->sendAuthRequest( contactid, reason );
+}
+
+void Client::sendAuth( const QString& contactid, const QString& reason, bool auth )
+{
+ Connection* c = d->connections.connectionForFamily( 0x0013 );
+ if ( !c )
+ return;
+ d->ssiAuthTask->sendAuthReply( contactid, reason, auth );
+}
+
+bool Client::isActive() const
+{
+ return d->active;
+}
+
+bool Client::isIcq() const
+{
+ return d->isIcq;
+}
+
+void Client::setIsIcq( bool isIcq )
+{
+ d->isIcq = isIcq;
+}
+
+void Client::debug( const QString& str )
+{
+ Q_UNUSED(str);
+// qDebug( "CLIENT: %s", str.ascii() );
+}
+
+void Client::initializeStaticTasks()
+{
+ //set up the extra tasks
+ Connection* c = d->connections.defaultConnection();
+ if ( !c )
+ return;
+ d->errorTask = new ErrorTask( c->rootTask() );
+ d->onlineNotifier = new OnlineNotifierTask( c->rootTask() );
+ d->ownStatusTask = new OwnUserInfoTask( c->rootTask() );
+ d->messageReceiverTask = new MessageReceiverTask( c->rootTask() );
+ d->ssiAuthTask = new SSIAuthTask( c->rootTask() );
+ d->icqInfoTask = new ICQUserInfoRequestTask( c->rootTask() );
+ d->userInfoTask = new UserInfoTask( c->rootTask() );
+ d->typingNotifyTask = new TypingNotifyTask( c->rootTask() );
+ d->ssiModifyTask = new SSIModifyTask( c->rootTask(), true );
+
+ connect( d->onlineNotifier, SIGNAL( userIsOnline( const QString&, const UserDetails& ) ),
+ this, SIGNAL( receivedUserInfo( const QString&, const UserDetails& ) ) );
+ connect( d->onlineNotifier, SIGNAL( userIsOffline( const QString&, const UserDetails& ) ),
+ this, SLOT( offlineUser( const QString&, const UserDetails & ) ) );
+
+ connect( d->ownStatusTask, SIGNAL( gotInfo() ), this, SLOT( haveOwnUserInfo() ) );
+ connect( d->ownStatusTask, SIGNAL( buddyIconUploadRequested() ), this,
+ SIGNAL( iconNeedsUploading() ) );
+
+ connect( d->messageReceiverTask, SIGNAL( receivedMessage( const Oscar::Message& ) ),
+ this, SLOT( receivedMessage( const Oscar::Message& ) ) );
+
+ connect( d->ssiAuthTask, SIGNAL( authRequested( const QString&, const QString& ) ),
+ this, SIGNAL( authRequestReceived( const QString&, const QString& ) ) );
+ connect( d->ssiAuthTask, SIGNAL( authReplied( const QString&, const QString&, bool ) ),
+ this, SIGNAL( authReplyReceived( const QString&, const QString&, bool ) ) );
+
+ connect( d->icqInfoTask, SIGNAL( receivedInfoFor( const QString&, unsigned int ) ),
+ this, SLOT( receivedIcqInfo( const QString&, unsigned int ) ) );
+
+ connect( d->userInfoTask, SIGNAL( receivedProfile( const QString&, const QString& ) ),
+ this, SIGNAL( receivedProfile( const QString&, const QString& ) ) );
+ connect( d->userInfoTask, SIGNAL( receivedAwayMessage( const QString&, const QString& ) ),
+ this, SIGNAL( receivedAwayMessage( const QString&, const QString& ) ) );
+ connect( d->typingNotifyTask, SIGNAL( typingStarted( const QString& ) ),
+ this, SIGNAL( userStartedTyping( const QString& ) ) );
+ connect( d->typingNotifyTask, SIGNAL( typingFinished( const QString& ) ),
+ this, SIGNAL( userStoppedTyping( const QString& ) ) );
+}
+
+void Client::removeGroup( const QString& groupName )
+{
+ Connection* c = d->connections.connectionForFamily( 0x0013 );
+ if ( !c )
+ return;
+
+ kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Removing group " << groupName << " from SSI" << endl;
+ SSIModifyTask* ssimt = new SSIModifyTask( c->rootTask() );
+ if ( ssimt->removeGroup( groupName ) )
+ ssimt->go( true );
+ else
+ delete ssimt;
+}
+
+void Client::addGroup( const QString& groupName )
+{
+ Connection* c = d->connections.connectionForFamily( 0x0013 );
+ if ( !c )
+ return;
+
+ kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Adding group " << groupName << " to SSI" << endl;
+ SSIModifyTask* ssimt = new SSIModifyTask( c->rootTask() );
+ if ( ssimt->addGroup( groupName ) )
+ ssimt->go( true );
+ else
+ delete ssimt;
+}
+
+void Client::addContact( const QString& contactName, const QString& groupName )
+{
+ Connection* c = d->connections.connectionForFamily( 0x0013 );
+ if ( !c )
+ return;
+
+ kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Adding contact " << contactName << " to SSI in group " << groupName << endl;
+ SSIModifyTask* ssimt = new SSIModifyTask( c->rootTask() );
+ if ( ssimt->addContact( contactName, groupName ) )
+ ssimt->go( true );
+ else
+ delete ssimt;
+}
+
+void Client::removeContact( const QString& contactName )
+{
+ Connection* c = d->connections.connectionForFamily( 0x0013 );
+ if ( !c )
+ return;
+
+ kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Removing contact " << contactName << " from SSI" << endl;
+ SSIModifyTask* ssimt = new SSIModifyTask( c->rootTask() );
+ if ( ssimt->removeContact( contactName ) )
+ ssimt->go( true );
+ else
+ delete ssimt;
+}
+
+void Client::renameGroup( const QString & oldGroupName, const QString & newGroupName )
+{
+ Connection* c = d->connections.connectionForFamily( 0x0013 );
+ if ( !c )
+ return;
+
+ kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Renaming group " << oldGroupName << " to " << newGroupName << endl;
+ SSIModifyTask* ssimt = new SSIModifyTask( c->rootTask() );
+ if ( ssimt->renameGroup( oldGroupName, newGroupName ) )
+ ssimt->go( true );
+ else
+ delete ssimt;
+}
+
+void Client::modifySSIItem( const Oscar::SSI& oldItem, const Oscar::SSI& newItem )
+{
+ int action = 0; //0 modify, 1 add, 2 remove TODO cleanup!
+ Connection* c = d->connections.connectionForFamily( 0x0013 );
+ if ( !c )
+ return;
+
+ if ( !oldItem && newItem )
+ action = 1;
+ if ( oldItem && !newItem )
+ action = 2;
+
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Add/Mod/Del item on server" << endl;
+ SSIModifyTask* ssimt = new SSIModifyTask( c->rootTask() );
+ switch ( action )
+ {
+ case 0:
+ if ( ssimt->modifyItem( oldItem, newItem ) )
+ ssimt->go( true );
+ else
+ delete ssimt;
+ break;
+ case 1:
+ if ( ssimt->addItem( newItem ) )
+ ssimt->go( true );
+ else
+ delete ssimt;
+ break;
+ case 2:
+ if ( ssimt->removeItem( oldItem ) )
+ ssimt->go( true );
+ else
+ delete ssimt;
+ break;
+ }
+}
+
+void Client::changeContactGroup( const QString& contact, const QString& newGroupName )
+{
+ Connection* c = d->connections.connectionForFamily( 0x0013 );
+ if ( !c )
+ return;
+
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Changing " << contact << "'s group to "
+ << newGroupName << endl;
+ SSIModifyTask* ssimt = new SSIModifyTask( c->rootTask() );
+ if ( ssimt->changeGroup( contact, newGroupName ) )
+ ssimt->go( true );
+ else
+ delete ssimt;
+}
+
+void Client::requestFullInfo( const QString& contactId )
+{
+ Connection* c = d->connections.connectionForFamily( 0x0015 );
+ if ( !c )
+ return;
+ d->icqInfoTask->setUser( contactId );
+ d->icqInfoTask->setType( ICQUserInfoRequestTask::Long );
+ d->icqInfoTask->go();
+}
+
+void Client::requestShortInfo( const QString& contactId )
+{
+ Connection* c = d->connections.connectionForFamily( 0x0015 );
+ if ( !c )
+ return;
+ d->icqInfoTask->setUser( contactId );
+ d->icqInfoTask->setType( ICQUserInfoRequestTask::Short );
+ d->icqInfoTask->go();
+}
+
+void Client::sendWarning( const QString& contact, bool anonymous )
+{
+ Connection* c = d->connections.connectionForFamily( 0x0004 );
+ if ( !c )
+ return;
+ WarningTask* warnTask = new WarningTask( c->rootTask() );
+ warnTask->setContact( contact );
+ warnTask->setAnonymous( anonymous );
+ QObject::connect( warnTask, SIGNAL( userWarned( const QString&, Q_UINT16, Q_UINT16 ) ),
+ this, SIGNAL( userWarned( const QString&, Q_UINT16, Q_UINT16 ) ) );
+ warnTask->go( true );
+}
+
+ICQGeneralUserInfo Client::getGeneralInfo( const QString& contact )
+{
+ return d->icqInfoTask->generalInfoFor( contact );
+}
+
+ICQWorkUserInfo Client::getWorkInfo( const QString& contact )
+{
+ return d->icqInfoTask->workInfoFor( contact );
+}
+
+ICQEmailInfo Client::getEmailInfo( const QString& contact )
+{
+ return d->icqInfoTask->emailInfoFor( contact );
+}
+
+ICQMoreUserInfo Client::getMoreInfo( const QString& contact )
+{
+ return d->icqInfoTask->moreInfoFor( contact );
+}
+
+ICQInterestInfo Client::getInterestInfo( const QString& contact )
+{
+ return d->icqInfoTask->interestInfoFor( contact );
+}
+
+ICQShortInfo Client::getShortInfo( const QString& contact )
+{
+ return d->icqInfoTask->shortInfoFor( contact );
+}
+
+QValueList<int> Client::chatExchangeList() const
+{
+ return d->exchanges;
+}
+
+void Client::setChatExchangeList( const QValueList<int>& exchanges )
+{
+ d->exchanges = exchanges;
+}
+
+void Client::requestAIMProfile( const QString& contact )
+{
+ d->userInfoTask->requestInfoFor( contact, UserInfoTask::Profile );
+}
+
+void Client::requestAIMAwayMessage( const QString& contact )
+{
+ d->userInfoTask->requestInfoFor( contact, UserInfoTask::AwayMessage );
+}
+
+void Client::requestICQAwayMessage( const QString& contact, ICQStatus contactStatus )
+{
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "requesting away message for " << contact << endl;
+ Oscar::Message msg;
+ msg.setType( 2 );
+ msg.setReceiver( contact );
+ msg.addProperty( Oscar::Message::StatusMessageRequest );
+ switch ( contactStatus )
+ {
+ case ICQAway:
+ msg.setMessageType( 0xE8 ); // away
+ break;
+ case ICQOccupied:
+ msg.setMessageType( 0xE9 ); // occupied
+ break;
+ case ICQNotAvailable:
+ msg.setMessageType( 0xEA ); // not awailable
+ break;
+ case ICQDoNotDisturb:
+ msg.setMessageType( 0xEB ); // do not disturb
+ break;
+ case ICQFreeForChat:
+ msg.setMessageType( 0xEC ); // free for chat
+ break;
+ default:
+ // may be a good way to deal with possible error and lack of online status message?
+ emit receivedAwayMessage( contact, "Sorry, this protocol does not support this type of status message" );
+ return;
+ }
+ sendMessage( msg );
+}
+
+void Client::addICQAwayMessageRequest( const QString& contact, ICQStatus contactStatus )
+{
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "adding away message request for "
+ << contact << " to queue" << endl;
+
+ //remove old request if still exists
+ removeICQAwayMessageRequest( contact );
+
+ ClientPrivate::AwayMsgRequest amr = { contact, contactStatus };
+ d->awayMsgRequestQueue.prepend( amr );
+
+ if ( !d->awayMsgRequestTimer->isActive() )
+ d->awayMsgRequestTimer->start( 1000 );
+}
+
+void Client::removeICQAwayMessageRequest( const QString& contact )
+{
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "removing away message request for "
+ << contact << " from queue" << endl;
+
+ QValueList<ClientPrivate::AwayMsgRequest>::iterator it = d->awayMsgRequestQueue.begin();
+ while ( it != d->awayMsgRequestQueue.end() )
+ {
+ if ( (*it).contact == contact )
+ it = d->awayMsgRequestQueue.erase( it );
+ else
+ it++;
+ }
+}
+
+void Client::nextICQAwayMessageRequest()
+{
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "request queue count " << d->awayMsgRequestQueue.count() << endl;
+
+ if ( d->awayMsgRequestQueue.empty() )
+ {
+ d->awayMsgRequestTimer->stop();
+ return;
+ }
+ else
+ {
+ Connection* c = d->connections.connectionForFamily( 0x0004 );
+ if ( !c )
+ return;
+
+ SNAC s = { 0x0004, 0x0006, 0x0000, 0x00000000 };
+ //get time needed to restore level to initial
+ //for some reason when we are long under initial level
+ //icq server will start to block our messages
+ int time = c->rateManager()->timeToInitialLevel( s );
+ if ( time > 0 )
+ {
+ d->awayMsgRequestTimer->changeInterval( time );
+ return;
+ }
+ else
+ {
+ d->awayMsgRequestTimer->changeInterval( 5000 );
+ }
+ }
+
+ ClientPrivate::AwayMsgRequest amr;
+
+ amr = d->awayMsgRequestQueue.back();
+ d->awayMsgRequestQueue.pop_back();
+ requestICQAwayMessage( amr.contact, amr.contactStatus );
+}
+
+void Client::requestStatusInfo( const QString& contact )
+{
+ d->userInfoTask->requestInfoFor( contact, UserInfoTask::General );
+}
+
+void Client::whitePagesSearch( const ICQWPSearchInfo& info )
+{
+ Connection* c = d->connections.connectionForFamily( 0x0015 );
+ if ( !c )
+ return;
+ UserSearchTask* ust = new UserSearchTask( c->rootTask() );
+ connect( ust, SIGNAL( foundUser( const ICQSearchResult& ) ),
+ this, SIGNAL( gotSearchResults( const ICQSearchResult& ) ) );
+ connect( ust, SIGNAL( searchFinished( int ) ), this, SIGNAL( endOfSearch( int ) ) );
+ ust->go( true ); //onGo does nothing in this task. This is just here so autodelete works
+ ust->searchWhitePages( info );
+}
+
+void Client::uinSearch( const QString& uin )
+{
+ Connection* c = d->connections.connectionForFamily( 0x0015 );
+ if ( !c )
+ return;
+ UserSearchTask* ust = new UserSearchTask( c->rootTask() );
+ connect( ust, SIGNAL( foundUser( const ICQSearchResult& ) ),
+ this, SIGNAL( gotSearchResults( const ICQSearchResult& ) ) );
+ connect( ust, SIGNAL( searchFinished( int ) ), this, SIGNAL( endOfSearch( int ) ) );
+ ust->go( true ); //onGo does nothing in this task. This is just here so autodelete works
+ ust->searchUserByUIN( uin );
+}
+
+void Client::updateProfile( const QString& profile )
+{
+ Connection* c = d->connections.connectionForFamily( 0x0002 );
+ if ( !c )
+ return;
+ ProfileTask* pt = new ProfileTask( c->rootTask() );
+ pt->setProfileText( profile );
+ pt->go(true);
+}
+
+void Client::sendTyping( const QString & contact, bool typing )
+{
+ Connection* c = d->connections.connectionForFamily( 0x0004 );
+ if ( !c )
+ return;
+ d->typingNotifyTask->setParams( contact, ( typing ? TypingNotifyTask::Begin : TypingNotifyTask::Finished ) );
+ d->typingNotifyTask->go( false ); // don't delete the task after sending
+}
+
+void Client::connectToIconServer()
+{
+ Connection* c = d->connections.connectionForFamily( 0x0010 );
+ if ( c )
+ return;
+
+ requestServerRedirect( 0x0010 );
+ return;
+}
+
+void Client::setIgnore( const QString& user, bool ignore )
+{
+ Oscar::SSI item = ssiManager()->findItem( user, ROSTER_IGNORE );
+ if ( item && !ignore )
+ {
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Removing " << user << " from ignore list" << endl;
+ this->modifySSIItem( item, Oscar::SSI() );
+ }
+ else if ( !item && ignore )
+ {
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Adding " << user << " to ignore list" << endl;
+ Oscar::SSI s( user, 0, ssiManager()->nextContactId(), ROSTER_IGNORE, QValueList<TLV>() );
+ this->modifySSIItem( Oscar::SSI(), s );
+ }
+}
+
+void Client::setVisibleTo( const QString& user, bool visible )
+{
+ Oscar::SSI item = ssiManager()->findItem( user, ROSTER_VISIBLE );
+ if ( item && !visible )
+ {
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Removing " << user << " from visible list" << endl;
+ this->modifySSIItem( item, Oscar::SSI() );
+ }
+ else if ( !item && visible )
+ {
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Adding " << user << " to visible list" << endl;
+ Oscar::SSI s( user, 0, ssiManager()->nextContactId(), ROSTER_VISIBLE, QValueList<TLV>() );
+ this->modifySSIItem( Oscar::SSI(), s );
+ }
+}
+
+void Client::setInvisibleTo( const QString& user, bool invisible )
+{
+ Oscar::SSI item = ssiManager()->findItem( user, ROSTER_INVISIBLE );
+ if ( item && !invisible )
+ {
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Removing " << user << " from invisible list" << endl;
+ this->modifySSIItem( item, Oscar::SSI() );
+ }
+ else if ( !item && invisible )
+ {
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Adding " << user << " to invisible list" << endl;
+ Oscar::SSI s( user, 0, ssiManager()->nextContactId(), ROSTER_INVISIBLE, QValueList<TLV>() );
+ this->modifySSIItem( Oscar::SSI(), s );
+ }
+}
+
+void Client::requestBuddyIcon( const QString& user, const QByteArray& hash, BYTE hashType )
+{
+ Connection* c = d->connections.connectionForFamily( 0x0010 );
+ if ( !c )
+ return;
+
+ BuddyIconTask* bit = new BuddyIconTask( c->rootTask() );
+ connect( bit, SIGNAL( haveIcon( const QString&, QByteArray ) ),
+ this, SIGNAL( haveIconForContact( const QString&, QByteArray ) ) );
+ bit->requestIconFor( user );
+ bit->setHashType( hashType );
+ bit->setHash( hash );
+ bit->go( true );
+}
+
+void Client::requestServerRedirect( WORD family, WORD exchange,
+ QByteArray cookie, WORD instance,
+ const QString& room )
+{
+ //making the assumption that family 2 will always be the BOS connection
+ //use it instead since we can't query for family 1
+ Connection* c = d->connections.connectionForFamily( family );
+ if ( c && family != 0x000E )
+ return; //we already have the connection
+
+ c = d->connections.connectionForFamily( 0x0002 );
+ if ( !c )
+ return;
+
+ if ( d->redirectionServices.findIndex( family ) == -1 )
+ d->redirectionServices.append( family ); //don't add families twice
+
+ if ( d->currentRedirect != 0 )
+ return; //we're already doing one redirection
+
+ d->currentRedirect = family;
+
+ //FIXME. this won't work if we have to defer the connection because we're
+ //already connecting to something
+ ServerRedirectTask* srt = new ServerRedirectTask( c->rootTask() );
+ if ( family == 0x000E )
+ {
+ srt->setChatParams( exchange, cookie, instance );
+ srt->setChatRoom( room );
+ }
+
+ connect( srt, SIGNAL( haveServer( const QString&, const QByteArray&, WORD ) ),
+ this, SLOT( haveServerForRedirect( const QString&, const QByteArray&, WORD ) ) );
+ srt->setService( family );
+ srt->go( true );
+}
+
+void Client::haveServerForRedirect( const QString& host, const QByteArray& cookie, WORD )
+{
+ //nasty sender() usage to get the task with chat room info
+ QObject* o = const_cast<QObject*>( sender() );
+ ServerRedirectTask* srt = dynamic_cast<ServerRedirectTask*>( o );
+
+ //create a new connection and set it up
+ int colonPos = host.find(':');
+ QString realHost, realPort;
+ if ( colonPos != -1 )
+ {
+ realHost = host.left( colonPos );
+ realPort = host.right(4); //we only need 4 bytes
+ }
+ else
+ {
+ realHost = host;
+ realPort = QString::fromLatin1("5190");
+ }
+
+ Connection* c = createConnection( realHost, realPort );
+ //create the new login task
+ m_loginTaskTwo = new StageTwoLoginTask( c->rootTask() );
+ m_loginTaskTwo->setCookie( cookie );
+ QObject::connect( m_loginTaskTwo, SIGNAL( finished() ), this, SLOT( serverRedirectFinished() ) );
+
+ //connect
+ connectToServer( c, d->host, false );
+ QObject::connect( c, SIGNAL( connected() ), this, SLOT( streamConnected() ) );
+
+ if ( srt )
+ d->connections.addChatInfoForConnection( c, srt->chatExchange(), srt->chatRoomName() );
+}
+
+void Client::serverRedirectFinished()
+{
+ if ( m_loginTaskTwo->statusCode() == 0 )
+ { //stage two was successful
+ Connection* c = d->connections.connectionForFamily( d->currentRedirect );
+ if ( !c )
+ return;
+ ClientReadyTask* crt = new ClientReadyTask( c->rootTask() );
+ crt->setFamilies( c->supportedFamilies() );
+ crt->go( true );
+ }
+
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "redirection finished for service "
+ << d->currentRedirect << endl;
+
+ if ( d->currentRedirect == 0x0010 )
+ emit iconServerConnected();
+
+ if ( d->currentRedirect == 0x000D )
+ {
+ connect( this, SIGNAL( chatNavigationConnected() ),
+ this, SLOT( requestChatNavLimits() ) );
+ emit chatNavigationConnected();
+ }
+
+ if ( d->currentRedirect == 0x000E )
+ {
+ //HACK! such abuse! think of a better way
+ if ( !m_loginTaskTwo )
+ {
+ kdWarning(OSCAR_RAW_DEBUG) << k_funcinfo << "no login task to get connection from!" << endl;
+ emit redirectionFinished( d->currentRedirect );
+ return;
+ }
+
+ Connection* c = m_loginTaskTwo->client();
+ QString roomName = d->connections.chatRoomForConnection( c );
+ WORD exchange = d->connections.exchangeForConnection( c );
+ if ( c )
+ {
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "setting up chat connection" << endl;
+ ChatServiceTask* cst = new ChatServiceTask( c->rootTask(), exchange, roomName );
+ connect( cst, SIGNAL( userJoinedChat( Oscar::WORD, const QString&, const QString& ) ),
+ this, SIGNAL( userJoinedChat( Oscar::WORD, const QString&, const QString& ) ) );
+ connect( cst, SIGNAL( userLeftChat( Oscar::WORD, const QString&, const QString& ) ),
+ this, SIGNAL( userLeftChat( Oscar::WORD, const QString&, const QString& ) ) );
+ connect( cst, SIGNAL( newChatMessage( const Oscar::Message& ) ),
+ this, SIGNAL( messageReceived( const Oscar::Message& ) ) );
+ }
+ emit chatRoomConnected( exchange, roomName );
+ }
+
+ emit redirectionFinished( d->currentRedirect );
+
+}
+
+void Client::checkRedirectionQueue( WORD family )
+{
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "checking redirection queue" << endl;
+ d->redirectionServices.remove( family );
+ d->currentRedirect = 0;
+ if ( !d->redirectionServices.isEmpty() )
+ {
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "scheduling new redirection" << endl;
+ requestServerRedirect( d->redirectionServices.front() );
+ }
+}
+
+
+void Client::requestChatNavLimits()
+{
+ Connection* c = d->connections.connectionForFamily( 0x000D );
+ if ( !c )
+ return;
+
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "requesting chat nav service limits" << endl;
+ ChatNavServiceTask* cnst = new ChatNavServiceTask( c->rootTask() );
+ cnst->setRequestType( ChatNavServiceTask::Limits );
+ QObject::connect( cnst, SIGNAL( haveChatExchanges( const QValueList<int>& ) ),
+ this, SLOT( setChatExchangeList( const QValueList<int>& ) ) );
+ cnst->go( true ); //autodelete
+
+}
+
+void Client::determineDisconnection( int code, const QString& string )
+{
+ if ( !sender() )
+ return;
+
+ //yay for the sender() hack!
+ QObject* obj = const_cast<QObject*>( sender() );
+ Connection* c = dynamic_cast<Connection*>( obj );
+ if ( !c )
+ return;
+
+ if ( c->isSupported( 0x0002 ) ||
+ d->stage == ClientPrivate::StageOne ) //emit on login
+ {
+ emit socketError( code, string );
+ }
+
+ //connection is deleted. deleteLater() is used
+ d->connections.remove( c );
+ c = 0;
+}
+
+void Client::sendBuddyIcon( const QByteArray& iconData )
+{
+ Connection* c = d->connections.connectionForFamily( 0x0010 );
+ if ( !c )
+ return;
+
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "icon length is " << iconData.size() << endl;
+ BuddyIconTask* bit = new BuddyIconTask( c->rootTask() );
+ bit->uploadIcon( iconData.size(), iconData );
+ bit->go( true );
+}
+
+void Client::joinChatRoom( const QString& roomName, int exchange )
+{
+ Connection* c = d->connections.connectionForFamily( 0x000D );
+ if ( !c )
+ return;
+
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "joining the chat room '" << roomName
+ << "' on exchange " << exchange << endl;
+ ChatNavServiceTask* cnst = new ChatNavServiceTask( c->rootTask() );
+ connect( cnst, SIGNAL( connectChat( WORD, QByteArray, WORD, const QString& ) ),
+ this, SLOT( setupChatConnection( WORD, QByteArray, WORD, const QString& ) ) );
+ cnst->createRoom( exchange, roomName );
+
+}
+
+void Client::setupChatConnection( WORD exchange, QByteArray cookie, WORD instance, const QString& room )
+{
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "cookie is:" << cookie << endl;
+ QByteArray realCookie( cookie );
+ kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "connection to chat room" << endl;
+ requestServerRedirect( 0x000E, exchange, realCookie, instance, room );
+}
+
+void Client::disconnectChatRoom( WORD exchange, const QString& room )
+{
+ Connection* c = d->connections.connectionForChatRoom( exchange, room );
+ if ( !c )
+ return;
+
+ d->connections.remove( c );
+ c = 0;
+}
+
+
+Connection* Client::createConnection( const QString& host, const QString& port )
+{
+ KNetworkConnector* knc = new KNetworkConnector( 0 );
+ knc->setOptHostPort( host, port.toUInt() );
+ ClientStream* cs = new ClientStream( knc, 0 );
+ cs->setNoopTime( 60000 );
+ Connection* c = new Connection( knc, cs, "BOS" );
+ cs->setConnection( c );
+ c->setClient( this );
+ return c;
+}
+
+void Client::deleteStaticTasks()
+{
+ delete d->errorTask;
+ delete d->onlineNotifier;
+ delete d->ownStatusTask;
+ delete d->messageReceiverTask;
+ delete d->ssiAuthTask;
+ delete d->icqInfoTask;
+ delete d->userInfoTask;
+ delete d->typingNotifyTask;
+ delete d->ssiModifyTask;
+
+ d->errorTask = 0;
+ d->onlineNotifier = 0;
+ d->ownStatusTask = 0;
+ d->messageReceiverTask = 0;
+ d->ssiAuthTask = 0;
+ d->icqInfoTask = 0;
+ d->userInfoTask = 0;
+ d->typingNotifyTask = 0;
+ d->ssiModifyTask = 0;
+}
+
+bool Client::hasIconConnection( ) const
+{
+ Connection* c = d->connections.connectionForFamily( 0x0010 );
+ return c;
+}
+
+#include "client.moc"
+//kate: tab-width 4; indent-mode csands; space-indent off; replace-tabs off;