diff options
Diffstat (limited to 'kopete/protocols/groupwise/gwaccount.cpp')
-rw-r--r-- | kopete/protocols/groupwise/gwaccount.cpp | 1646 |
1 files changed, 1646 insertions, 0 deletions
diff --git a/kopete/protocols/groupwise/gwaccount.cpp b/kopete/protocols/groupwise/gwaccount.cpp new file mode 100644 index 00000000..48ef3833 --- /dev/null +++ b/kopete/protocols/groupwise/gwaccount.cpp @@ -0,0 +1,1646 @@ +/* + gwaccount.cpp - Kopete GroupWise Protocol + + Copyright (c) 2004 SUSE Linux AG http://www.suse.com + + Based on Testbed + Copyright (c) 2003 by Will Stephenson <will@stevello.free-online.co.uk> + + Kopete (c) 2002-2003 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 General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include <sys/utsname.h> + +#include <qvalidator.h> + +#include <kaboutdata.h> +#include <kapplication.h> +#include <kconfig.h> +#include <kdebug.h> +#include <kdialogbase.h> +#include <kinputdialog.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kpassivepopup.h> + +#include <kopeteuiglobal.h> +#include <kopeteaway.h> +#include <kopeteawayaction.h> +#include <kopetecontactlist.h> +#include <kopetegroup.h> +#include <kopeteglobal.h> +#include <kopetemetacontact.h> +#include <kopetepassword.h> +#include <kopeteview.h> + +#include "client.h" +#include "qca.h" +#include "gwcontact.h" +#include "gwcontactlist.h" +#include "gwprotocol.h" +#include "gwconnector.h" +#include "gwmessagemanager.h" +#include "privacymanager.h" +#include "qcatlshandler.h" +#include "userdetailsmanager.h" +#include "tasks/createcontacttask.h" +#include "tasks/createcontactinstancetask.h" +#include "tasks/deleteitemtask.h" +#include "tasks/movecontacttask.h" +#include "tasks/updatecontacttask.h" +#include "tasks/updatefoldertask.h" +#include "ui/gwchatsearchdialog.h" +#include "ui/gwprivacy.h" +#include "ui/gwprivacydialog.h" +#include "ui/gwreceiveinvitationdialog.h" + +#include "gwaccount.h" + +GroupWiseAccount::GroupWiseAccount( GroupWiseProtocol *parent, const QString& accountID, const char *name ) +: Kopete::ManagedConnectionAccount ( parent, accountID, 0, "groupwiseaccount" ) +{ + Q_UNUSED( name ); + // Init the myself contact + setMyself( new GroupWiseContact( this, accountId(), Kopete::ContactList::self()->myself(), 0, 0, 0 ) ); + myself()->setOnlineStatus( GroupWiseProtocol::protocol()->groupwiseOffline ); + + // Contact list management + QObject::connect( Kopete::ContactList::self(), SIGNAL( groupRenamed( Kopete::Group *, const QString & ) ), + SLOT( slotKopeteGroupRenamed( Kopete::Group * ) ) ); + QObject::connect( Kopete::ContactList::self(), SIGNAL( groupRemoved( Kopete::Group * ) ), + SLOT( slotKopeteGroupRemoved( Kopete::Group * ) ) ); + + m_actionAutoReply = new KAction ( i18n( "&Set Auto-Reply..." ), QString::null, 0, this, + SLOT( slotSetAutoReply() ), this, "actionSetAutoReply"); + m_actionJoinChatRoom = new KAction ( i18n( "&Join Channel..." ), QString::null, 0, this, + SLOT( slotJoinChatRoom() ), this, "actionJoinChatRoom"); + m_actionManagePrivacy = new KAction ( i18n( "&Manage Privacy..." ), QString::null, 0, this, + SLOT( slotPrivacy() ), this, "actionPrivacy"); + + m_connector = 0; + m_QCATLS = 0; + m_tlsHandler = 0; + m_clientStream = 0; + m_client = 0; + m_dontSync = false; + m_serverListModel = 0; +} + +GroupWiseAccount::~GroupWiseAccount() +{ + cleanup(); +} + +KActionMenu* GroupWiseAccount::actionMenu() +{ + KActionMenu *m_actionMenu=Kopete::Account::actionMenu(); + + m_actionAutoReply->setEnabled( isConnected() ); + m_actionManagePrivacy->setEnabled( isConnected() ); + m_actionJoinChatRoom->setEnabled( isConnected() ); + m_actionMenu->insert( m_actionManagePrivacy ); + m_actionMenu->insert( m_actionAutoReply ); + m_actionMenu->insert( m_actionJoinChatRoom ); + /* Used for debugging */ + /* + theActionMenu->insert( new KAction ( "Test rtfize()", QString::null, 0, this, + SLOT( slotTestRTFize() ), this, + "actionTestRTFize") ); + */ + return m_actionMenu; +} + +int GroupWiseAccount::port() const +{ + return configGroup()->readNumEntry( "Port" ); +} + +const QString GroupWiseAccount::server() const +{ + return configGroup()->readEntry( "Server" ); +} + +Client * GroupWiseAccount::client() const +{ + return m_client; +} + +GroupWiseProtocol *GroupWiseAccount::protocol() const +{ + return static_cast<GroupWiseProtocol *>( Kopete::Account::protocol() ); +} + +GroupWiseChatSession * GroupWiseAccount::chatSession( Kopete::ContactPtrList others, const GroupWise::ConferenceGuid & guid, Kopete::Contact::CanCreateFlags canCreate ) +{ + GroupWiseChatSession * chatSession = 0; + do // one iteration misuse of do...while to enable an easy drop-out once we locate a manager + { + // do we have a manager keyed by GUID? + if ( !guid.isEmpty() ) + { + chatSession = findChatSessionByGuid( guid ); + if ( chatSession ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " found a message manager by GUID: " << guid << endl; + break; + } + } + // does the factory know about one, going on the chat members? + chatSession = dynamic_cast<GroupWiseChatSession*>( + Kopete::ChatSessionManager::self()->findChatSession( myself(), others, protocol() ) ); + if ( chatSession ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " found a message manager by members with GUID: " << chatSession->guid() << endl; + // re-add the returning contact(s) (very likely only one) to the chat + Kopete::Contact * returningContact; + for ( returningContact = others.first(); returningContact; returningContact = others.next() ) + chatSession->joined( static_cast<GroupWiseContact *>( returningContact ) ); + + if ( !guid.isEmpty() ) + chatSession->setGuid( guid ); + break; + } + // we don't have an existing message manager for this chat, so create one if we may + if ( canCreate ) + { + chatSession = new GroupWiseChatSession( myself(), others, protocol(), guid ); + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << + " created a new message manager with GUID: " << chatSession->guid() << endl; + m_chatSessions.append( chatSession ); + // listen for the message manager telling us that the user + //has left the conference so we remove it from our map + QObject::connect( chatSession, SIGNAL( leavingConference( GroupWiseChatSession * ) ), + SLOT( slotLeavingConference( GroupWiseChatSession * ) ) ); + break; + } + //kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << + // " no message manager available." << endl; + } + while ( 0 ); + //dumpManagers(); + return chatSession; +} + +GroupWiseChatSession * GroupWiseAccount::findChatSessionByGuid( const GroupWise::ConferenceGuid & guid ) +{ + GroupWiseChatSession * chatSession = 0; + QValueList<GroupWiseChatSession *>::ConstIterator it; + for ( it = m_chatSessions.begin(); it != m_chatSessions.end(); ++it ) + { + if ( (*it)->guid() == guid ) + { + chatSession = *it; + break; + } + } + return chatSession; +} + +GroupWiseContact * GroupWiseAccount::contactForDN( const QString & dn ) +{ + QDictIterator<Kopete::Contact> it( contacts() ); + // check if we have a DN for them + for( ; it.current(); ++it ) + { + GroupWiseContact * candidate = static_cast<GroupWiseContact*>( it.current() ); + if ( candidate && candidate->dn() == dn ) + return candidate; + } + // we might have just added the contact with a user ID, try the first section of the dotted dn + return static_cast< GroupWiseContact * >( contacts()[ protocol()->dnToDotted( dn ).section( '.', 0, 0 ) ] ); +} + +void GroupWiseAccount::setAway( bool away, const QString & reason ) +{ + if ( away ) + { + if ( Kopete::Away::getInstance()->idleTime() > 10 ) // don't go AwayIdle unless the user has actually been idle this long + setOnlineStatus( protocol()->groupwiseAwayIdle, QString::null ); + else + setOnlineStatus( protocol()->groupwiseAway, reason ); + } + else + setOnlineStatus( protocol()->groupwiseAvailable ); +} + +void GroupWiseAccount::performConnectWithPassword( const QString &password ) +{ + if ( password.isEmpty() ) + { + disconnect(); + return; + } + // don't try and connect if we are already connected + if ( isConnected () ) + return; + + bool sslPossible = QCA::isSupported(QCA::CAP_TLS); + + if (!sslPossible) + { + KMessageBox::queuedMessageBox(Kopete::UI::Global::mainWidget (), KMessageBox::Error, + i18n ("SSL support could not be initialized for account %1. This is most likely because the QCA TLS plugin is not installed on your system."). + arg(myself()->contactId()), + i18n ("GroupWise SSL Error")); + return; + } + if ( m_client ) + { + m_client->close(); + cleanup(); + } + // set up network classes + m_connector = new KNetworkConnector( 0 ); + //myConnector->setOptHostPort( "localhost", 8300 ); + m_connector->setOptHostPort( server(), port() ); + m_connector->setOptSSL( true ); + Q_ASSERT( QCA::isSupported(QCA::CAP_TLS) ); + m_QCATLS = new QCA::TLS; + m_tlsHandler = new QCATLSHandler( m_QCATLS ); + m_clientStream = new ClientStream( m_connector, m_tlsHandler, 0); + + QObject::connect( m_connector, SIGNAL( error() ), this, SLOT( slotConnError() ) ); + QObject::connect( m_connector, SIGNAL( connected() ), this, SLOT( slotConnConnected() ) ); + + QObject::connect (m_clientStream, SIGNAL (connectionClosed()), + this, SLOT (slotCSDisconnected())); + QObject::connect (m_clientStream, SIGNAL (delayedCloseFinished()), + this, SLOT (slotCSDisconnected())); + // Notify us when the transport layer is connected + QObject::connect( m_clientStream, SIGNAL( connected() ), SLOT( slotCSConnected() ) ); + // it's necessary to catch this signal and tell the TLS handler to proceed + // even if we don't check cert validity + QObject::connect( m_tlsHandler, SIGNAL(tlsHandshaken()), SLOT( slotTLSHandshaken()) ); + // starts the client once the security layer is up, but see below + QObject::connect( m_clientStream, SIGNAL( securityLayerActivated(int) ), SLOT( slotTLSReady(int) ) ); + // we could handle login etc in start(), in which case we would emit this signal after that + //QObject::connect (jabberClientStream, SIGNAL (authenticated()), + // this, SLOT (slotCSAuthenticated ())); + // we could also get do the actual login in response to this.. + //QObject::connect (m_clientStream, SIGNAL (needAuthParams(bool, bool, bool)), + // this, SLOT (slotCSNeedAuthParams (bool, bool, bool))); + + // not implemented: warning + QObject::connect( m_clientStream, SIGNAL( warning(int) ), SLOT( slotCSWarning(int) ) ); + // not implemented: error + QObject::connect( m_clientStream, SIGNAL( error(int) ), SLOT( slotCSError(int) ) ); + + m_client = new Client( 0, CMSGPRES_GW_6_5 ); + + // NB these are prefixed with QObject:: to avoid any chance of a clash with our connect() methods. + // we connected successfully + QObject::connect( m_client, SIGNAL( loggedIn() ), SLOT( slotLoggedIn() ) ); + // or connection failed + QObject::connect( m_client, SIGNAL( loginFailed() ), SLOT( slotLoginFailed() ) ); + // folder listed + QObject::connect( m_client, SIGNAL( folderReceived( const FolderItem & ) ), SLOT( receiveFolder( const FolderItem & ) ) ); + // contact listed + QObject::connect( m_client, SIGNAL( contactReceived( const ContactItem & ) ), SLOT( receiveContact( const ContactItem & ) ) ); + // contact details listed + QObject::connect( m_client, SIGNAL( contactUserDetailsReceived( const GroupWise::ContactDetails & ) ), SLOT( receiveContactUserDetails( const GroupWise::ContactDetails & ) ) ); + // contact status changed + QObject::connect( m_client, SIGNAL( statusReceived( const QString &, Q_UINT16, const QString & ) ), SLOT( receiveStatus( const QString &, Q_UINT16 , const QString & ) ) ); + // incoming message + QObject::connect( m_client, SIGNAL( messageReceived( const ConferenceEvent & ) ), SLOT( handleIncomingMessage( const ConferenceEvent & ) ) ); + // auto reply to one of our messages because the recipient is away + QObject::connect( m_client, SIGNAL( autoReplyReceived( const ConferenceEvent & ) ), SLOT( handleIncomingMessage( const ConferenceEvent & ) ) ); + + QObject::connect( m_client, SIGNAL( ourStatusChanged( GroupWise::Status, const QString &, const QString & ) ), SLOT( changeOurStatus( GroupWise::Status, const QString &, const QString & ) ) ); + // conference events + QObject::connect( m_client, + SIGNAL( conferenceCreated( const int, const GroupWise::ConferenceGuid & ) ), + SIGNAL( conferenceCreated( const int, const GroupWise::ConferenceGuid & ) ) ); + QObject::connect( m_client, SIGNAL( conferenceCreationFailed( const int, const int ) ), SIGNAL( conferenceCreationFailed( const int, const int ) ) ); + QObject::connect( m_client, SIGNAL( invitationReceived( const ConferenceEvent & ) ), SLOT( receiveInvitation( const ConferenceEvent & ) ) ); + QObject::connect( m_client, SIGNAL( conferenceLeft( const ConferenceEvent & ) ), SLOT( receiveConferenceLeft( const ConferenceEvent & ) ) ); + QObject::connect( m_client, SIGNAL( conferenceJoinNotifyReceived( const ConferenceEvent & ) ), SLOT( receiveConferenceJoinNotify( const ConferenceEvent & ) ) ); + QObject::connect( m_client, SIGNAL( inviteNotifyReceived( const ConferenceEvent & ) ), SLOT( receiveInviteNotify( const ConferenceEvent & ) ) ); + QObject::connect( m_client, SIGNAL( invitationDeclined( const ConferenceEvent & ) ), SLOT( receiveInviteDeclined( const ConferenceEvent & ) ) ); + + QObject::connect( m_client, SIGNAL( conferenceJoined( const GroupWise::ConferenceGuid &, const QStringList &, const QStringList & ) ), SLOT( receiveConferenceJoin( const GroupWise::ConferenceGuid &, const QStringList & , const QStringList & ) ) ); + + // typing events + QObject::connect( m_client, SIGNAL( contactTyping( const ConferenceEvent & ) ), + SIGNAL( contactTyping( const ConferenceEvent & ) ) ); + QObject::connect( m_client, SIGNAL( contactNotTyping( const ConferenceEvent & ) ), + SIGNAL( contactNotTyping( const ConferenceEvent & ) ) ); + // misc + QObject::connect( m_client, SIGNAL( accountDetailsReceived( const GroupWise::ContactDetails &) ), SLOT( receiveAccountDetails( const GroupWise::ContactDetails & ) ) ); + QObject::connect( m_client, SIGNAL( connectedElsewhere() ), SLOT( slotConnectedElsewhere() ) ); + // privacy - contacts can't connect directly to this signal because myself() is initialised before m_client + QObject::connect( m_client->privacyManager(), SIGNAL( privacyChanged( const QString &, bool ) ), SIGNAL( privacyChanged( const QString &, bool ) ) ); + + // GW7 + QObject::connect( m_client, SIGNAL( broadcastReceived( const ConferenceEvent & ) ), SLOT( handleIncomingMessage( const ConferenceEvent & ) ) ); + QObject::connect( m_client, SIGNAL( systemBroadcastReceived( const ConferenceEvent & ) ), SLOT( handleIncomingMessage( const ConferenceEvent & ) ) ); + + struct utsname utsBuf; + uname (&utsBuf); + m_client->setClientName ("Kopete"); + m_client->setClientVersion ( kapp->aboutData ()->version () ); + m_client->setOSName (QString ("%1 %2").arg (utsBuf.sysname, 1).arg (utsBuf.release, 2)); + + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "Connecting to GroupWise server " << server() << ":" << port() << endl; + + NovellDN dn; + dn.dn = "maeuschen"; + dn.server = "reiser.suse.de"; + m_serverListModel = new GWContactList( this ); + myself()->setOnlineStatus( protocol()->groupwiseConnecting ); + m_client->connectToServer( m_clientStream, dn, true ); + + QObject::connect( m_client, SIGNAL( messageSendingFailed() ), SLOT( slotMessageSendingFailed() ) ); +} + +void GroupWiseAccount::slotMessageSendingFailed() +{ + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, + i18n("Message Sending Failed", "Kopete was not able to send the last message sent on account '%1'.\nIf possible, please send the console output from Kopete to <wstephenson@novell.com> for analysis." ).arg( accountId() ) , i18n ("Unable to Send Message on Account '%1'").arg( accountId() ) ); +} + +void GroupWiseAccount::setOnlineStatus( const Kopete::OnlineStatus& status, const QString &reason ) +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + if ( status == protocol()->groupwiseUnknown + || status == protocol()->groupwiseConnecting + || status == protocol()->groupwiseInvalid ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " called with invalid status \"" + << status.description() << "\"" << endl; + } + // going offline + else if ( status == protocol()->groupwiseOffline ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " DISCONNECTING" << endl; + disconnect(); + } + // changing status + else if ( isConnected() ) + { + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "changing status to \"" << status.description() << "\"" << endl; + // Appear Offline is achieved by explicitly setting the status to offline, + // rather than disconnecting as when really going offline. + if ( status == protocol()->groupwiseAppearOffline ) + m_client->setStatus( GroupWise::Offline, reason, configGroup()->readEntry( "AutoReply" ) ); + else + m_client->setStatus( ( GroupWise::Status )status.internalStatus(), reason, configGroup()->readEntry( "AutoReply" ) ); + } + // going online + else + { + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "Must be connected before changing status" << endl; + m_initialReason = reason; + connect( status ); + } +} + +void GroupWiseAccount::disconnect () +{ + disconnect ( Manual ); +} + +void GroupWiseAccount::disconnect( Kopete::Account::DisconnectReason reason ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + + if( isConnected () ) + { + kdDebug (GROUPWISE_DEBUG_GLOBAL) << k_funcinfo << "Still connected, closing connection..." << endl; + QValueList<GroupWiseChatSession *>::ConstIterator it; + for ( it = m_chatSessions.begin() ; it != m_chatSessions.end(); ++it ) + (*it)->setClosed(); + + /* Tell backend class to disconnect. */ + m_client->close (); + } + + // clear the model of the server side contact list, so that when we reconnect, there will not be any stale entries to confuse GroupWiseContact::syncGroups() + delete m_serverListModel; + m_serverListModel = 0; + + // make sure that the connection animation gets stopped if we're still + // in the process of connecting + myself()->setOnlineStatus( GroupWiseProtocol::protocol()->groupwiseOffline ); + + disconnected( reason ); + kdDebug(GROUPWISE_DEBUG_GLOBAL) << k_funcinfo << "Disconnected." << endl; +} + +void GroupWiseAccount::cleanup() +{ + delete m_client; + delete m_clientStream; + delete m_QCATLS; + delete m_connector; + + m_connector = 0; + m_QCATLS = 0; + m_clientStream = 0; + m_client = 0; +} + +void GroupWiseAccount::createConference( const int clientId, const QStringList& invitees ) +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + // TODO: remove this it prevents sending a list of participants with the createconf + if ( isConnected() ) + m_client->createConference( clientId , invitees ); +} + +void GroupWiseAccount::sendInvitation( const GroupWise::ConferenceGuid & guid, const QString & dn, const QString & message ) +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + if ( isConnected() ) + { + GroupWise::OutgoingMessage msg; + msg.guid = guid; + msg.message = message; + m_client->sendInvitation( guid, dn, msg ); + } +} + +void GroupWiseAccount::slotLoggedIn() +{ + reconcileOfflineChanges(); + // set local status display + myself()->setOnlineStatus( protocol()->groupwiseAvailable ); + // set status on server + if ( initialStatus() != Kopete::OnlineStatus(Kopete::OnlineStatus::Online) && + ( ( GroupWise::Status )initialStatus().internalStatus() != GroupWise::Unknown ) ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "Initial status is not online, setting status to " << initialStatus().internalStatus() << endl; + m_client->setStatus( ( GroupWise::Status )initialStatus().internalStatus(), m_initialReason, configGroup()->readEntry( "AutoReply" ) ); + } +} + +void GroupWiseAccount::reconcileOfflineChanges() +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + m_dontSync = true; + //sanity check the server side model vs our contact list. + //Contacts might have been removed from some groups or entirely on the server. + //Any contact not present on the server should be deleted locally. + + // for each metacontact group membership: + // for each GroupWiseContact + // get its contact list instances + // get its metacontact's groups + // for each group + // is there no CLI with the same id? + // if MC has no other contacts + // if MC's groups size is 1 + // remove MC + // else + // remove from group + // else + // if MC's groups size is 1 and group is topLevel + // remove contact + // else // Contact's group membership were changed elsewhere, but we can't change it here without + // // affecting other protocols' contacts + // set flag to warn user that incompatible changes were made on other client + bool conflicts = false; + QDictIterator<Kopete::Contact> it( contacts() ); + for ( ; it.current(); ++it ) + { + if ( *it == myself() ) + continue; + + GroupWiseContact * c = static_cast< GroupWiseContact *>( *it ); + GWContactInstanceList instances = m_serverListModel->instancesWithDn( c->dn() ); + QPtrList<Kopete::Group> groups = c->metaContact()->groups(); + QPtrListIterator<Kopete::Group> grpIt( groups ); + while ( *grpIt ) + { + QPtrListIterator<Kopete::Group> candidate = grpIt; + ++grpIt; + bool found = false; + GWContactInstanceList::Iterator instIt = instances.begin(); + for ( ; instIt != instances.end(); ++instIt ) + { + QString groupId = ( *candidate )->pluginData( protocol(), accountId() + " objectId" ); + if ( groupId.isEmpty() ) + if ( *candidate == Kopete::Group::topLevel() ) + groupId = "0"; // hack the top level's objectId to 0 + else + continue; + + GWFolder * folder = ::qt_cast<GWFolder*>( ( *instIt )->parent() ); + if ( folder->id == ( unsigned int )groupId.toInt() ) + { + found = true; + instances.remove( instIt ); + break; + } + } + if ( !found ) + { + if ( c->metaContact()->contacts().count() == 1 ) + { + if ( c->metaContact()->groups().count() == 1 ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "contact instance " << c->dn() << " not found on server side list, deleting metacontact with only this contact, in one group" << c->metaContact()->displayName() << endl; + Kopete::ContactList::self()->removeMetaContact( c->metaContact() ); + break; + } + else + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "contact instance " << c->dn() << " not found, removing metacontact " << c->metaContact()->displayName() << " from group " << ( *candidate )->displayName() << endl; + c->metaContact()->removeFromGroup( *candidate ); + } + } + else + { + if ( c->metaContact()->groups().count() == 1 ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "contact instance " << c->dn() << " not found, removing contact " << c->metaContact()->displayName() << " from metacontact with other contacts " << endl; + c->deleteLater(); + break; + } + else + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "metacontact " << c->metaContact()->displayName( ) << "has multiple children and group membership, and contact " << c->dn() << " was removed from one group on the server." << endl; + conflicts = true; + } + } // + } //end while, now check the next group membership + } //end for, now check the next groupwise contact + if ( conflicts ) + // show queuedmessagebox + KPassivePopup::message( i18n( "Conflicting Changes Made Offline" ), i18n( "A change happened to your GroupWise contact list while you were offline which was impossible to reconcile." ), Kopete::UI::Global::mainWidget() ); + m_dontSync = false; +} + +void GroupWiseAccount::slotLoginFailed() +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + password().setWrong(); + disconnect(); + connect(); +} + +void GroupWiseAccount::slotKopeteGroupRenamed( Kopete::Group * renamedGroup ) +{ + if ( isConnected() ) + { + QString objectIdString = renamedGroup->pluginData( protocol(), accountId() + " objectId" ); + // if this group exists on the server + if ( !objectIdString.isEmpty() ) + { + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + + GroupWise::FolderItem fi; + fi.id = objectIdString.toInt(); + if ( fi.id != 0 ) + { + fi.sequence = renamedGroup->pluginData( protocol(), accountId() + " sequence" ).toInt(); + fi.name= renamedGroup->pluginData( protocol(), accountId() + " serverDisplayName" ); + + UpdateFolderTask * uft = new UpdateFolderTask( client()->rootTask() ); + uft->renameFolder( renamedGroup->displayName(), fi ); + uft->go( true ); + // would be safer to do this in a slot fired on uft's finished() signal + renamedGroup->setPluginData( protocol(), accountId() + " serverDisplayName", + renamedGroup->displayName() ); + } + } + } + //else + // errornotconnected +} + +void GroupWiseAccount::slotKopeteGroupRemoved( Kopete::Group * group ) +{ + if ( isConnected() ) + { + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + // the member contacts should be deleted separately, so just delete the folder here + // get the folder object id + QString objectIdString = group->pluginData( protocol(), accountId() + " objectId" ); + if ( !objectIdString.isEmpty() ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "deleting folder with objectId: " << objectIdString << endl; + int objectId = objectIdString.toInt(); + if ( objectId == 0 ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "deleted folder " << group->displayName() << " has root folder objectId 0!" << endl; + return; + } + DeleteItemTask * dit = new DeleteItemTask( client()->rootTask() ); + dit->item( 0, objectId ); + // the group is deleted synchronously after this slot returns; so there is no point listening for signals + dit->go( true ); + } + } + //else + // errornotconnected +} + +void GroupWiseAccount::slotConnError() +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, + i18n( "Error shown when connecting failed", "Kopete was not able to connect to the GroupWise Messenger server for account '%1'.\nPlease check your server and port settings and try again." ).arg( accountId() ) , i18n ("Unable to Connect '%1'").arg( accountId() ) ); + + disconnect(); +} + +void GroupWiseAccount::slotConnConnected() +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; +} + +void GroupWiseAccount::slotCSDisconnected() +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "Disconnected from Groupwise server." << endl; + myself()->setOnlineStatus( protocol()->groupwiseOffline ); + QValueList<GroupWiseChatSession *>::ConstIterator it; + for ( it = m_chatSessions.begin() ; it != m_chatSessions.end(); ++it ) + (*it)->setClosed(); + setAllContactsStatus( protocol()->groupwiseOffline ); + client()->close(); +} + +void GroupWiseAccount::slotCSConnected() +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "Connected to Groupwise server." << endl; + +} + +void GroupWiseAccount::slotCSError( int error ) +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "Got error from ClientStream:" << error << endl; +} + +void GroupWiseAccount::slotCSWarning( int warning ) +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "Got warning from ClientStream:" << warning << endl; +} + +void GroupWiseAccount::slotTLSHandshaken() +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "TLS handshake complete" << endl; + int validityResult = m_QCATLS->certificateValidityResult (); + + if( validityResult == QCA::TLS::Valid ) + { + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << "Certificate is valid, continuing." << endl; + // valid certificate, continue + m_tlsHandler->continueAfterHandshake (); + } + else + { + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << "Certificate is not valid, continuing anyway" << endl; + // certificate is not valid, query the user + if(handleTLSWarning (validityResult, server (), myself()->contactId ()) == KMessageBox::Continue) + { + m_tlsHandler->continueAfterHandshake (); + } + else + { + disconnect ( Kopete::Account::Manual ); + } + } +} + +int GroupWiseAccount::handleTLSWarning (int warning, QString server, QString accountId) +{ + QString validityString, code; + + switch(warning) + { + case QCA::TLS::NoCert: + validityString = i18n("No certificate was presented."); + code = "NoCert"; + break; + case QCA::TLS::HostMismatch: + validityString = i18n("The host name does not match the one in the certificate."); + code = "HostMismatch"; + break; + case QCA::TLS::Rejected: + validityString = i18n("The Certificate Authority rejected the certificate."); + code = "Rejected"; + break; + case QCA::TLS::Untrusted: + // FIXME: write better error message here + validityString = i18n("The certificate is untrusted."); + code = "Untrusted"; + break; + case QCA::TLS::SignatureFailed: + validityString = i18n("The signature is invalid."); + code = "SignatureFailed"; + break; + case QCA::TLS::InvalidCA: + validityString = i18n("The Certificate Authority is invalid."); + code = "InvalidCA"; + break; + case QCA::TLS::InvalidPurpose: + // FIXME: write better error message here + validityString = i18n("Invalid certificate purpose."); + code = "InvalidPurpose"; + break; + case QCA::TLS::SelfSigned: + validityString = i18n("The certificate is self-signed."); + code = "SelfSigned"; + break; + case QCA::TLS::Revoked: + validityString = i18n("The certificate has been revoked."); + code = "Revoked"; + break; + case QCA::TLS::PathLengthExceeded: + validityString = i18n("Maximum certificate chain length was exceeded."); + code = "PathLengthExceeded"; + break; + case QCA::TLS::Expired: + validityString = i18n("The certificate has expired."); + code = "Expired"; + break; + case QCA::TLS::Unknown: + default: + validityString = i18n("An unknown error occurred trying to validate the certificate."); + code = "Unknown"; + break; + } + + return KMessageBox::warningContinueCancel(Kopete::UI::Global::mainWidget (), + i18n("The certificate of server %1 could not be validated for account %2: %3"). + arg(server). + arg(accountId). + arg(validityString), + i18n("GroupWise Connection Certificate Problem"), + KStdGuiItem::cont(), + QString("KopeteTLSWarning") + server + code); +} + +void GroupWiseAccount::slotTLSReady( int secLayerCode ) +{ + // i don't know what secLayerCode is for... + Q_UNUSED( secLayerCode ); + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + m_client->start( server(), port(), accountId(), password().cachedValue() ); +} + +void GroupWiseAccount::handleIncomingMessage( const ConferenceEvent & message ) +{ + QString typeName = "UNKNOWN"; + if ( message.type == ReceiveMessage ) + typeName = "message"; + else if ( message.type == ReceiveAutoReply ) + typeName = "autoreply"; + else if ( message.type == ReceivedBroadcast ) + typeName = "broadcast"; + else if ( message.type == ReceivedSystemBroadcast ) + typeName = "system broadcast"; + + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " received a " << typeName << " from " << message.user << ", to conference: " << message.guid << ", message: " << message.message << endl; + + GroupWiseContact * sender = contactForDN( message.user ); + if ( !sender ) + sender = createTemporaryContact( message.user ); + + // if we receive a message from an Offline contact, they are probably blocking us + // but we have to set their status to Unknown so that we can reply to them. + kdDebug( GROUPWISE_DEBUG_GLOBAL) << "sender is: " << sender->onlineStatus().description() << endl; + if ( sender->onlineStatus() == protocol()->groupwiseOffline ) { + sender->setMessageReceivedOffline( true ); + } + + Kopete::ContactPtrList contactList; + contactList.append( sender ); + // FIND A MESSAGE MANAGER FOR THIS CONTACT + GroupWiseChatSession *sess = chatSession( contactList, message.guid, Kopete::Contact::CanCreate ); + + // add an auto-reply indicator if needed + QString messageMunged = message.message; + if ( message.type == ReceiveAutoReply ) + { + QString prefix = i18n("Prefix used for automatically generated auto-reply" + " messages when the contact is Away, contains contact's name", + "Auto reply from %1: " ).arg( sender->metaContact()->displayName() ); + messageMunged = prefix + message.message; + } + if ( message.type == GroupWise::ReceivedBroadcast ) + { + QString prefix = i18n("Prefix used for broadcast messages", + "Broadcast message from %1: " ).arg( sender->metaContact()->displayName() ); + messageMunged = prefix + message.message; + } + if ( message.type == GroupWise::ReceivedSystemBroadcast ) + { + QString prefix = i18n("Prefix used for system broadcast messages", + "System Broadcast message from %1: " ).arg( sender->metaContact()->displayName() ); + messageMunged = prefix + message.message; + } + + kdDebug(GROUPWISE_DEBUG_GLOBAL) << k_funcinfo << " message before KopeteMessage and appending: " << messageMunged << endl; + Kopete::Message * newMessage = + new Kopete::Message( message.timeStamp, sender, contactList, messageMunged, + Kopete::Message::Inbound, + ( message.type == ReceiveAutoReply ) ? Kopete::Message::PlainText : Kopete::Message::RichText ); + Q_ASSERT( sess ); + sess->appendMessage( *newMessage ); + kdDebug(GROUPWISE_DEBUG_GLOBAL) << "message from KopeteMessage: plainbody: " << newMessage->plainBody() << " parsedbody: " << newMessage->parsedBody() << endl; + delete newMessage; +} + +void GroupWiseAccount::receiveFolder( const FolderItem & folder ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo + << " objectId: " << folder.id + << " sequence: " << folder.sequence + << " parentId: " << folder.parentId + << " displayName: " << folder.name << endl; + if ( folder.parentId != 0 ) + { + kdWarning( GROUPWISE_DEBUG_GLOBAL ) << " - received a nested folder. These were not supported in GroupWise or Kopete as of Sept 2004, aborting! (parentId = " << folder.parentId << ")" << endl; + return; + } + + GWFolder * fld = m_serverListModel->addFolder( folder.id, folder.sequence, folder.name ); + Q_ASSERT( fld ); + + // either find a local group and record these details there, or create a new group to suit + Kopete::Group * found = 0; + QPtrList<Kopete::Group> groupList = Kopete::ContactList::self()->groups(); + for ( Kopete::Group *grp = groupList.first(); grp; grp = groupList.next() ) + { + // see if there is already a local group that matches this group + QString groupId = grp->pluginData( protocol(), accountId() + " objectId" ); + if ( groupId.isEmpty() ) + if ( folder.name == grp->displayName() ) // no match on id, match on display name instead + { + grp->setPluginData( protocol(), accountId() + " objectId", QString::number( folder.id ) ); + found = grp; + break; + } + if ( folder.id == (unsigned int)groupId.toInt() ) + { + // was it renamed locally while we were offline? + if ( grp->displayName() != folder.name ) + { + slotKopeteGroupRenamed( grp ); + grp->setPluginData( protocol(), accountId() + " serverDisplayName", grp->displayName() ); + fld->displayName = grp->displayName(); + } + + found = grp; + break; + } + } + + if ( !found ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " - not found locally, creating Kopete::Group" << endl; + Kopete::Group * grp = new Kopete::Group( folder.name ); + grp->setPluginData( protocol(), accountId() + " serverDisplayName", folder.name ); + grp->setPluginData( protocol(), accountId() + " objectId", QString::number( folder.id ) ); + Kopete::ContactList::self()->addGroup( grp ); + } +} + +void GroupWiseAccount::receiveContact( const ContactItem & contact ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo + << " objectId: " << contact.id + << ", sequence: " << contact.sequence + << ", parentId: " << contact.parentId + << ", dn: " << contact.dn + << ", displayName: " << contact.displayName << endl; + //kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "\n dotted notation is '" << protocol()->dnToDotted( contact.dn ) << "'\n" <<endl; + + // add to new style contact list + GWContactInstance * gwInst = m_serverListModel->addContactInstance( contact.id, contact.parentId, contact.sequence, contact.displayName, contact.dn ); + Q_ASSERT( gwInst ); + + GroupWiseContact * c = contactForDN( contact.dn ); + // this contact is new to us, create him on the server + if ( !c ) + { + Kopete::MetaContact *metaContact = new Kopete::MetaContact(); + metaContact->setDisplayName( contact.displayName ); + c = new GroupWiseContact( this, contact.dn, metaContact, contact.id, contact.parentId, contact.sequence ); + Kopete::ContactList::self()->addMetaContact( metaContact ); + } + // add the metacontact to the ContactItem's group, if not there aleady + if ( contact.parentId == 0 ) + c->metaContact()->addToGroup( Kopete::Group::topLevel() ); + else + { + // check the metacontact is in the group this listing-of-the-contact is in... + GWFolder * folder = m_serverListModel->findFolderById( contact.parentId ); + if ( !folder ) // inconsistent + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " - ERROR - contact's folder doesn't exist on server" << endl; + DeleteItemTask * dit = new DeleteItemTask( client()->rootTask() ); + dit->item( contact.parentId, contact.id ); +// QObject::connect( dit, SIGNAL( gotContactDeleted( const ContactItem & ) ), SLOT( receiveContactDeleted( const ContactItem & ) ) ); + dit->go( true ); + return; + } + Kopete::Group *grp = Kopete::ContactList::self()->findGroup( folder->displayName ); + // grp should exist, because we receive the folders from the server before the contacts + if ( grp ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " - making sure MC is in group " << grp->displayName() << endl; + m_dontSync = true; + c->metaContact()->addToGroup( grp ); //addToGroup() is safe to call if already a member + m_dontSync = false; + } + } + + c->setNickName( contact.displayName ); + //m_serverListModel->dump(); +} + +void GroupWiseAccount::receiveAccountDetails( const ContactDetails & details ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo + << "Auth attribute: " << details.authAttribute + << ", Away message: " << details.awayMessage + << ", CN" << details.cn + << ", DN" << details.dn + << ", fullName" << details.fullName + << ", surname" << details.surname + << ", givenname" << details.givenName + << ", status" << details.status + << endl; + if ( details.cn.lower() == accountId().lower().section('@', 0, 0) ) // incase user set account ID foo@novell.com + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " - got our details in contact list, updating them" << endl; + GroupWiseContact * detailsOwner= static_cast<GroupWiseContact *>( myself() ); + detailsOwner->updateDetails( details ); + //detailsOwner->setProperty( Kopete::Global::Properties::self()->nickName(), details.fullName ); + + // Very important, without knowing our DN we can't do much else + Q_ASSERT( !details.dn.isEmpty() ); + m_client->setUserDN( details.dn ); + return; + } + else + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " - passed someone else's details in contact list!" << endl; + } +} + +void GroupWiseAccount::receiveContactUserDetails( const ContactDetails & details ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo + << "Auth attribute: " << details.authAttribute + << ", Away message: " << details.awayMessage + << ", CN" << details.cn + << ", DN" << details.dn + << ", fullName" << details.fullName + << ", surname" << details.surname + << ", givenname" << details.givenName + << ", status" << details.status + << endl; + // HACK: lowercased DN + if ( !details.dn.isNull() ) + { + // are the details for someone in our contact list? + GroupWiseContact * detailsOwner = contactForDN( details.dn ); + + if( detailsOwner ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " - updating details for " << details.dn << endl; + detailsOwner->updateDetails( details ); + } + else + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " - got details for " << details.dn << ", but they aren't in our contact list!" << endl; + } + } +} + +GroupWiseContact * GroupWiseAccount::createTemporaryContact( const QString & dn ) +{ + ContactDetails details = client()->userDetailsManager()->details( dn ); + GroupWiseContact * c = static_cast<GroupWiseContact *>( contacts()[ details.dn.lower() ] ); + if ( !c && details.dn != accountId() ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "Got a temporary contact DN: " << details.dn << endl; + // the client is telling us about a temporary contact we need to know about so add them + Kopete::MetaContact *metaContact = new Kopete::MetaContact (); + metaContact->setTemporary (true); + QString displayName = details.fullName; + if ( displayName.isEmpty() ) + displayName = details.givenName + " " + details.surname; + + metaContact->setDisplayName( displayName ); + c = new GroupWiseContact( this, details.dn, metaContact, 0, 0, 0 ); + c->updateDetails( details ); + c->setProperty( Kopete::Global::Properties::self()->nickName(), protocol()->dnToDotted( details.dn ) ); + Kopete::ContactList::self()->addMetaContact( metaContact ); + // the contact details probably don't contain status - but we can ask for it + if ( details.status == GroupWise::Invalid && isConnected() ) + m_client->requestStatus( details.dn ); + } + else + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "Notified of existing temporary contact DN: " << details.dn << endl; + return c; +} + +void GroupWiseAccount::receiveStatus( const QString & contactId, Q_UINT16 status, const QString &awayMessage ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "got status for: " << contactId << ", status: " << status << ", away message: " << awayMessage << endl; + GroupWiseContact * c = contactForDN( contactId ); + if ( c ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " - their KOS is: " << protocol()->gwStatusToKOS( status ).description() << endl; + Kopete::OnlineStatus kos = protocol()->gwStatusToKOS( status ); + c->setOnlineStatus( kos ); + c->setProperty( protocol()->propAwayMessage, awayMessage ); + } + else + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " couldn't find " << contactId << endl; +} + +void GroupWiseAccount::changeOurStatus( GroupWise::Status status, const QString & awayMessage, const QString & autoReply ) +{ + if ( status == GroupWise::Offline ) + myself()->setOnlineStatus( protocol()->groupwiseAppearOffline ); + else + myself()->setOnlineStatus( protocol()->gwStatusToKOS( status ) ); + myself()->setProperty( protocol()->propAwayMessage, awayMessage ); + myself()->setProperty( protocol()->propAutoReply, autoReply ); +} + +void GroupWiseAccount::sendMessage( const GroupWise::ConferenceGuid &guid, const Kopete::Message & message ) +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + // make an outgoing message + if ( isConnected() ) + { + GroupWise::OutgoingMessage outMsg; + outMsg.guid = guid; + outMsg.message = message.plainBody(); + outMsg.rtfMessage = protocol()->rtfizeText( message.plainBody() ); + // make a list of DNs to send to + QStringList addresseeDNs; + Kopete::ContactPtrList addressees = message.to(); + for ( Kopete::Contact * contact = addressees.first(); contact; contact = addressees.next() ) + addresseeDNs.append( static_cast< GroupWiseContact* >( contact )->dn() ); + // send the message + m_client->sendMessage( addresseeDNs, outMsg ); + } +} + +bool GroupWiseAccount::createContact( const QString& contactId, Kopete::MetaContact* parentContact ) +{ + kdDebug ( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "contactId: " << contactId << endl; + + // first find all the groups that this contact is a member of + // record, in a folderitem, their display names and groupwise object id + // Set object id to 0 if not found - they do not exist on the server + bool topLevel = false; + QValueList< FolderItem > folders; + Kopete::GroupList groupList = parentContact->groups(); + for ( Kopete::Group *group = groupList.first(); group; group = groupList.next() ) + { + if ( group->type() == Kopete::Group::TopLevel ) // no need to create it on the server + { + topLevel = true; + continue; + } + + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "looking up: " << group->displayName() << endl; + GWFolder * fld = m_serverListModel->findFolderByName( group->displayName() ); + FolderItem fi; + if ( fld ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << fld->displayName << endl; + //FIXME - get rid of FolderItem & co + fi.parentId = ::qt_cast<GWFolder*>( fld->parent() )->id; + fi.id = fld->id; + fi.name = fld->displayName; + } + else + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "folder: " << group->displayName() << + "not found in server list model." << endl; + fi.parentId = 0; + fi.id = 0; + fi.name = group->displayName(); + } + folders.append( fi ); + + } + + // find out the sequence number to use for any new folders + int highestFreeSequence = m_serverListModel->maxSequenceNumber() + 1; + + // send this list along with the contact details to the server + // CreateContactTask will create the missing folders on the server + // and then add the contact to each one + // finally it will signal finished(), and we can query it for the details + // we gave it earlier and make sure the contact was successfully created. + // + // Since ToMetaContact expects synchronous contact creation + // we have to create the contact optimistically. + GroupWiseContact * gc = new GroupWiseContact( this, contactId, parentContact, 0, 0, 0 ); + ContactDetails dt = client()->userDetailsManager()->details( contactId ); + QString displayAs; + if ( dt.fullName.isEmpty() ) + displayAs = dt.givenName + " " + dt.surname; + else + displayAs = dt.fullName; + + gc->setNickName( displayAs ); + // If the CreateContactTask finishes with an error, we have to + // delete the contact we just created, in receiveContactCreated :/ + + if ( folders.isEmpty() && !topLevel ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "aborting because we didn't find any groups to add them to" << endl; + return false; + } + + // get the contact's full name to use as the display name of the created contact + CreateContactTask * cct = new CreateContactTask( client()->rootTask() ); + cct->contactFromUserId( contactId, parentContact->displayName(), highestFreeSequence, folders, topLevel ); + QObject::connect( cct, SIGNAL( finished() ), SLOT( receiveContactCreated() ) ); + cct->go( true ); + return true; +} + +void GroupWiseAccount::receiveContactCreated() +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + m_serverListModel->dump(); + + CreateContactTask * cct = ( CreateContactTask * )sender(); + if ( cct->success() ) + { + if ( client()->userDetailsManager()->known( cct->dn() ) ) + { + ContactDetails dt = client()->userDetailsManager()->details( cct->dn() ); + GroupWiseContact * c = contactForDN( cct->dn() ); + c->setOnlineStatus( protocol()->gwStatusToKOS( dt.status ) ); + c->setNickName( dt.fullName ); + c->updateDetails( dt ); + } + else + { + client()->requestDetails( QStringList( cct->dn() ) ); + client()->requestStatus( cct->dn() ); + } + } + else + { + // delete the contact created optimistically using the supplied userid; + Kopete::Contact * c = contacts()[ protocol()->dnToDotted( cct->userId() ) ]; + if ( c ) + { + // if the contact creation failed because it already exists on the server, don't delete it + if (!cct->statusCode() == NMERR_DUPLICATE_CONTACT ) + { + if ( c->metaContact()->contacts().count() == 1 ) + Kopete::ContactList::self()->removeMetaContact( c->metaContact() ); + else + delete c; + } + } + + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget (), KMessageBox::Error, + i18n ("The contact %1 could not be added to the contact list, with error message: %2"). + arg(cct->userId() ).arg( cct->statusString() ), + i18n ("Error Adding Contact") ); + } +} + +void GroupWiseAccount::deleteContact( GroupWiseContact * contact ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + contact->setDeleting( true ); + if ( isConnected() ) + { + // remove all the instances of this contact from the server's contact list + GWContactInstanceList instances = m_serverListModel->instancesWithDn( contact->dn() ); + GWContactInstanceList::iterator it = instances.begin(); + for ( ; it != instances.end(); ++it ) + { + DeleteItemTask * dit = new DeleteItemTask( client()->rootTask() ); + dit->item( ::qt_cast<GWFolder*>( (*it)->parent() )->id, (*it)->id ); + QObject::connect( dit, SIGNAL( gotContactDeleted( const ContactItem & ) ), SLOT( receiveContactDeleted( const ContactItem & ) ) ); + dit->go( true ); + } + } +} + +void GroupWiseAccount::receiveContactDeleted( const ContactItem & instance ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + // an instance of this contact was deleted on the server. + // Remove it from the model of the server side list, + // and if there are no other instances of this contact, delete the contact + m_serverListModel->removeInstanceById( instance.id ); + m_serverListModel->dump(); + + GWContactInstanceList instances = m_serverListModel->instancesWithDn( instance.dn ); + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " - " << instance.dn << " now has " << instances.count() << " instances remaining." << endl; + GroupWiseContact * c = contactForDN( instance.dn ); + if ( c && instances.count() == 0 && c->deleting() ) + { + c->deleteLater(); + } +} + + +void GroupWiseAccount::slotConnectedElsewhere() +{ + KPassivePopup::message( i18n ("Signed in as %1 Elsewhere").arg( accountId() ), + i18n( "The parameter is the user's own account id for this protocol", "You have been disconnected from GroupWise Messenger because you signed in as %1 elsewhere" ).arg( accountId() ) , Kopete::UI::Global::mainWidget() ); + disconnect(); +} + +void GroupWiseAccount::receiveInvitation( const ConferenceEvent & event ) +{ + // ask the user if they want to accept the invitation or not + GroupWiseContact * contactFrom = contactForDN( event.user ); + if ( !contactFrom ) + contactFrom = createTemporaryContact( event.user ); + if ( configGroup()->readEntry( "AlwaysAcceptInvitations" ) == "true" ) + { + client()->joinConference( event.guid ); + } + else + { + ReceiveInvitationDialog * dlg = new ReceiveInvitationDialog( this, event, + Kopete::UI::Global::mainWidget(), "invitedialog" ); + dlg->show(); + } + +} + +void GroupWiseAccount::receiveConferenceJoin( const GroupWise::ConferenceGuid & guid, const QStringList & participants, const QStringList & invitees ) +{ + // get a new GWMM + Kopete::ContactPtrList others; + GroupWiseChatSession * sess = chatSession( others, guid, Kopete::Contact::CanCreate); + // find each contact and add them to the GWMM, and tell them they are in the conference + for ( QValueList<QString>::ConstIterator it = participants.begin(); it != participants.end(); ++it ) + { + //kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " adding participant " << *it << endl; + GroupWiseContact * c = contactForDN( *it ); + if ( !c ) + c = createTemporaryContact( *it ); + sess->joined( c ); + } + // add each invitee too + for ( QValueList<QString>::ConstIterator it = invitees.begin(); it != invitees.end(); ++it ) + { + //kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " adding invitee " << *it << endl; + GroupWiseContact * c = contactForDN( *it ); + if ( !c ) + c = createTemporaryContact( *it ); + sess->addInvitee( c ); + } + sess->view( true )->raise( false ); +} + +void GroupWiseAccount::receiveConferenceJoinNotify( const ConferenceEvent & event ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + GroupWiseChatSession * sess = findChatSessionByGuid( event.guid ); + if ( sess ) + { + GroupWiseContact * c = contactForDN( event.user ); + if ( !c ) + c = createTemporaryContact( event.user ); + sess->joined( c ); + } + else + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " couldn't find a GWCS for conference: " << event.guid << endl; +} + +void GroupWiseAccount::receiveConferenceLeft( const ConferenceEvent & event ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + GroupWiseChatSession * sess = findChatSessionByGuid( event.guid ); + if ( sess ) + { + GroupWiseContact * c = contactForDN( event.user ); + if ( c ) + { + sess->left( c ); + } + else + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " couldn't find a contact for DN: " << event.user << endl; + } + else + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " couldn't find a GWCS for conference: " << event.guid << endl; + +} + +void GroupWiseAccount::receiveInviteDeclined( const ConferenceEvent & event ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + GroupWiseChatSession * sess = findChatSessionByGuid( event.guid ); + if ( sess ) + { + GroupWiseContact * c = contactForDN( event.user ); + if ( c ) + sess->inviteDeclined( c ); + } + else + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " couldn't find a GWCS for conference: " << event.guid << endl; +} + +void GroupWiseAccount::receiveInviteNotify( const ConferenceEvent & event ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + GroupWiseChatSession * sess = findChatSessionByGuid( event.guid ); + if ( sess ) + { + GroupWiseContact * c = contactForDN( event.user ); + if ( !c ) + c = createTemporaryContact( event.user ); + + sess->addInvitee( c ); + Kopete::Message declined = Kopete::Message( myself(), sess->members(), i18n("%1 has been invited to join this conversation.").arg( c->metaContact()->displayName() ), Kopete::Message::Internal, Kopete::Message::PlainText ); + sess->appendMessage( declined ); + } + else + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " couldn't find a GWCS for conference: " << event.guid << endl; +} + +void GroupWiseAccount::slotLeavingConference( GroupWiseChatSession * sess ) +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "unregistering message manager:" << sess->guid()<< endl; + if( isConnected () ) + m_client->leaveConference( sess->guid() ); + m_chatSessions.remove( sess ); + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << "m_chatSessions now contains:" << m_chatSessions.count() << " managers" << endl; + Kopete::ContactPtrList members = sess->members(); + for ( Kopete::Contact * contact = members.first(); contact; contact = members.next() ) + { + static_cast< GroupWiseContact * >( contact )->setMessageReceivedOffline( false ); + } +} + +void GroupWiseAccount::slotSetAutoReply() +{ + bool ok; + QRegExp rx( ".*" ); + QRegExpValidator validator( rx, this ); + QString newAutoReply = KInputDialog::getText( i18n( "Enter Auto-Reply Message" ), + i18n( "Please enter an Auto-Reply message that will be shown to users who message you while Away or Busy" ), configGroup()->readEntry( "AutoReply" ), + &ok, Kopete::UI::Global::mainWidget(), "autoreplymessagedlg", &validator ); + if ( ok ) + configGroup()->writeEntry( "AutoReply", newAutoReply ); +} + +void GroupWiseAccount::slotTestRTFize() +{ +/* bool ok; + const QString query = QString::fromLatin1("Enter a string to rtfize:"); + QString testText = KLineEditDlg::getText( query, QString::null, &ok, Kopete::UI::Global::mainWidget() ); + if ( ok ) + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "Converted text is: '" << protocol()->rtfizeText( testText ) << "'" << endl;*/ + +// bool ok; +// const QString query = i18n("Enter a contactId:"); +// QString testText = KInputDialog::getText( query, i18n("This is a test dialog and will not be in the final product!" ), QString::null, &ok, Kopete::UI::Global::mainWidget() ); +// if ( !ok ) +// return; +// kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "Trying to add contact: '" << protocol()->rtfizeText( testText ) << "'" << endl; +// Kopete::MetaContact *metaContact = new Kopete::MetaContact (); +// metaContact->setDisplayName( "Test Add MC" ); +// metaContact->setTemporary (true); +// createContact( testText, "Test Add Contact", metaContact ); +} + +void GroupWiseAccount::slotPrivacy() +{ + new GroupWisePrivacyDialog( this, Kopete::UI::Global::mainWidget(), "gwprivacydialog" ); +} + +void GroupWiseAccount::slotJoinChatRoom() +{ + new GroupWiseChatSearchDialog( this, Kopete::UI::Global::mainWidget(), "gwjoinchatdialog" ); +} + +bool GroupWiseAccount::isContactBlocked( const QString & dn ) +{ + if ( isConnected() ) + return client()->privacyManager()->isBlocked( dn ); + else + return false; +} + +void GroupWiseAccount::dumpManagers() +{ + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << " for: " << accountId() + << " containing: " << m_chatSessions.count() << " managers " << endl; + QValueList<GroupWiseChatSession *>::ConstIterator it; + for ( it = m_chatSessions.begin() ; it != m_chatSessions.end(); ++it ) + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "guid: " << (*it)->guid() << endl; +} + +bool GroupWiseAccount::dontSync() +{ + return m_dontSync; +} + +void GroupWiseAccount::syncContact( GroupWiseContact * contact ) +{ + if ( dontSync() ) + return; + + if ( contact != myself() ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << k_funcinfo << endl; + if ( !isConnected() ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << "not connected, can't sync display name or group membership" << endl; + return; + } + + // if this is a temporary contact, don't bother + if ( contact->metaContact()->isTemporary() ) + return; + + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " = CONTACT '" << contact->nickName() << "' IS IN " << contact->metaContact()->groups().count() << " MC GROUPS, AND HAS " << m_serverListModel->instancesWithDn( contact->dn() ).count() << " CONTACT LIST INSTANCES." << endl; + + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " = LOOKING FOR NOOP GROUP MEMBERSHIPS" << endl; + // 1) Seek matches between CLIs and MCGs and remove from the lists without taking any action. match on objectid, parentid + // 2) Each remaining unmatched pair is a move, initiate and remove - need to take care to always use greatest unused sequence number - if we have to set the sequence number to the following sequence number within the folder, we may have a problem where after the first move, we have to wait for the state of the CLIs to be updated pending the completion of the first move - this would be difficult to cope with, because our current lists would be out of date, or we'd have to restart the sync - assuming the first move created a new matched CLI-MCG pair, we could do that with little cost. + // 3) Any remaining entries in MCG list are adds, carry out + // 4) Any remaining entries in CLI list are removes, carry out + + // start by discovering the next free group sequence number in case we have to add any groups + int nextFreeSequence = m_serverListModel->maxSequenceNumber() + 1; + // 1) + // make a list of all the groups the metacontact is in + QPtrList<Kopete::Group> groupList = contact->metaContact()->groups(); + // make a list of all the groups this contact is in, according to the server model + GWContactInstanceList instances = m_serverListModel->instancesWithDn( contact->dn() ); + + // seek corresponding pairs in both lists and remove + // ( for each group ) + QPtrListIterator< Kopete::Group > grpIt( groupList ); + while ( *grpIt ) + { + QPtrListIterator< Kopete::Group > candidateGrp( groupList ); + candidateGrp = grpIt; + ++grpIt; + + GWContactInstanceList::Iterator instIt = instances.begin(); + const GWContactInstanceList::Iterator instEnd = instances.end(); + // ( see if a contactlist instance matches the group) + while ( instIt != instEnd ) + { + GWContactInstanceList::Iterator candidateInst = instIt; + ++instIt; + GWFolder * folder = ::qt_cast<GWFolder *>( ( *candidateInst )->parent() ); + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " - Looking for a match, MC grp '" + << ( *candidateGrp )->displayName() + << "', GWFolder '" << folder->displayName << "', objectId is " << folder->id << endl; + + if ( ( folder->id == 0 && ( ( *candidateGrp ) == Kopete::Group::topLevel() ) ) + || ( ( *candidateGrp )->displayName() == folder->displayName ) ) + { + //this pair matches, we can remove its members from both lists ) + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " - match! removing both entries" << endl; + instances.remove( candidateInst ); + groupList.remove( *candidateGrp ); + break; + } + } + } + + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " = LOOKING FOR UNMATCHED PAIRS => GROUP MOVES" << endl; + grpIt.toFirst(); + // ( take the first pair and carry out a move ) + while ( *grpIt && !instances.isEmpty() ) + { + QPtrListIterator< Kopete::Group > candidateGrp( groupList ); + candidateGrp = grpIt; + ++grpIt; + GWContactInstanceList::Iterator instIt = instances.begin(); + GWFolder * sourceFolder =::qt_cast<GWFolder*>( ( *instIt)->parent() ); + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " - moving contact instance from group '" << sourceFolder->displayName << "' to group '" << ( *candidateGrp )->displayName() << "'" << endl; + + // create contactItem parameter + ContactItem instance; + instance.id = ( *instIt )->id; + instance.parentId = sourceFolder->id; + instance.sequence = ( *instIt )->sequence; + instance.dn = ( *instIt )->dn; + instance.displayName = contact->nickName(); + // identify the destination folder + GWFolder * destinationFolder = m_serverListModel->findFolderByName( ( ( *candidateGrp )->displayName() ) ); + if ( destinationFolder ) // folder already exists on the server + { + MoveContactTask * mit = new MoveContactTask( client()->rootTask() ); + mit->moveContact( instance, destinationFolder->id ); + QObject::connect( mit, SIGNAL( gotContactDeleted( const ContactItem & ) ), SLOT( receiveContactDeleted( const ContactItem & ) ) ); + mit->go(); + } + else if ( *candidateGrp == Kopete::Group::topLevel() ) + { + MoveContactTask * mit = new MoveContactTask( client()->rootTask() ); + mit->moveContact( instance, 0 ); + QObject::connect( mit, SIGNAL( gotContactDeleted( const ContactItem & ) ), SLOT( receiveContactDeleted( const ContactItem & ) ) ); + mit->go(); + } + else + { + MoveContactTask * mit = new MoveContactTask( client()->rootTask() ); + QObject::connect( mit, SIGNAL( gotContactDeleted( const ContactItem & ) ), + SLOT( receiveContactDeleted( const ContactItem & ) ) ); + // discover the next free sequence number and add the group using that + mit->moveContactToNewFolder( instance, nextFreeSequence++, + ( *candidateGrp )->displayName() ); + mit->go( true ); + } + groupList.remove( candidateGrp ); + instances.remove( instIt ); + } + + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " = LOOKING FOR ADDS" << endl; + grpIt.toFirst(); + while ( *grpIt ) + { + QPtrListIterator< Kopete::Group > candidateGrp( groupList ); + candidateGrp = grpIt; + ++grpIt; + GWFolder * destinationFolder = m_serverListModel->findFolderByName( ( ( *candidateGrp )->displayName() ) ); + CreateContactInstanceTask * ccit = new CreateContactInstanceTask( client()->rootTask() ); + + contact->setNickName( contact->metaContact()->displayName() ); + // does this group exist on the server? Create the contact appropriately + if ( destinationFolder ) + { + int parentId = destinationFolder->id; + ccit->contactFromUserId( contact->dn(), contact->metaContact()->displayName(), parentId ); + } + else + { + if ( ( *candidateGrp ) == Kopete::Group::topLevel() ) + ccit->contactFromUserId( contact->dn(), contact->metaContact()->displayName(), + m_serverListModel->rootFolder->id ); + else + // discover the next free sequence number and add the group using that + ccit->contactFromUserIdAndFolder( contact->dn(), contact->metaContact()->displayName(), + nextFreeSequence++, ( *candidateGrp )->displayName() ); + } + ccit->go( true ); + groupList.remove( candidateGrp ); + } + + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " = LOOKING FOR REMOVES" << endl; + GWContactInstanceList::Iterator instIt = instances.begin(); + const GWContactInstanceList::Iterator instEnd = instances.end(); + // ( remove each remaining contactlist instance, because it doesn't exist locally any more ) + while ( instIt != instEnd ) + { + GWContactInstanceList::Iterator candidateInst = instIt; + ++instIt; + GWFolder * folder =::qt_cast<GWFolder*>( ( *candidateInst )->parent() ); + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " - remove contact instance '"<< ( *candidateInst )->id << "' in group '" << folder->displayName << "'" << endl; + + DeleteItemTask * dit = new DeleteItemTask( client()->rootTask() ); + dit->item( folder->id, (*candidateInst)->id ); + QObject::connect( dit, SIGNAL( gotContactDeleted( const ContactItem & ) ), SLOT( receiveContactDeleted( const ContactItem & ) ) ); + dit->go( true ); + + instances.remove( candidateInst ); + } + + // start an UpdateItem + if ( contact->metaContact()->displayName() != contact->nickName() ) + { + kdDebug( GROUPWISE_DEBUG_GLOBAL ) << " updating the contact's display name to the metacontact's: " << contact->metaContact()->displayName() << endl; + // form a list of the contact's groups + GWContactInstanceList instances = m_serverListModel->instancesWithDn( contact->dn() ); + GWContactInstanceList::Iterator it = instances.begin(); + const GWContactInstanceList::Iterator end = instances.end(); + for ( ; it != end; ++it ) + { + QValueList< ContactItem > instancesToChange; + ContactItem instance; + instance.id = (*it)->id; + instance.parentId = ::qt_cast<GWFolder *>( (*it)->parent() )->id; + instance.sequence = (*it)->sequence; + instance.dn = contact->dn(); + instance.displayName = contact->nickName(); + instancesToChange.append( instance ); + + UpdateContactTask * uct = new UpdateContactTask( client()->rootTask() ); + uct->renameContact( contact->metaContact()->displayName(), instancesToChange ); + QObject::connect ( uct, SIGNAL( finished() ), contact, SLOT( renamedOnServer() ) ); + uct->go( true ); + } + } + } +} + +#include "gwaccount.moc" |