diff options
Diffstat (limited to 'kopete/libkopete/kopetecontactlist.cpp')
-rw-r--r-- | kopete/libkopete/kopetecontactlist.cpp | 1112 |
1 files changed, 1112 insertions, 0 deletions
diff --git a/kopete/libkopete/kopetecontactlist.cpp b/kopete/libkopete/kopetecontactlist.cpp new file mode 100644 index 00000000..9aab9f2f --- /dev/null +++ b/kopete/libkopete/kopetecontactlist.cpp @@ -0,0 +1,1112 @@ +/* + kopetecontactlist.cpp - Kopete's Contact List backend + + Copyright (c) 2005 by Michael Larouche <michael.larouche@kdemail.net> + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2004 by Olivier Goffart <ogoffart @ kde.org> + Copyright (c) 2002 by Duncan Mac-Vicar Prett <duncan@kde.org> + + Copyright (c) 2002-2004 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 "kopetecontactlist.h" + +#include <qdir.h> +#include <qregexp.h> +#include <qtimer.h> + +#include <kapplication.h> +#include <kabc/stdaddressbook.h> +#include <kdebug.h> +#include <ksavefile.h> +#include <kstandarddirs.h> +#include <kopeteconfig.h> +#include <kglobal.h> +#include "kopetemetacontact.h" +#include "kopetecontact.h" +#include "kopetechatsession.h" +//#include "kopetemessage.h" +#include "kopetepluginmanager.h" +#include "kopeteprotocol.h" +#include "kopeteaccount.h" +#include "kopeteaccountmanager.h" +#include "kopetegroup.h" +#include "kopetepicture.h" + + +namespace Kopete +{ + +class ContactList::Private +{public: + /** Flag: do not save the contactlist until she is completely loaded */ + bool loaded ; + + QPtrList<MetaContact> contacts; + QPtrList<Group> groups; + QPtrList<MetaContact> selectedMetaContacts; + QPtrList<Group> selectedGroups; + + QTimer *saveTimer; + + MetaContact *myself; + + /** Flag: does the user uses the global identity */ + bool useGlobalIdentity; + + /** + * Current contact list version * 10 ( i.e. '10' is version '1.0' ) + */ + static const uint ContactListVersion = 10; +}; + +ContactList *ContactList::s_self = 0L; + +ContactList *ContactList::self() +{ + if( !s_self ) + s_self = new ContactList; + + return s_self; +} + +ContactList::ContactList() + : QObject( kapp, "KopeteContactList" ) +{ + d=new Private; + + //the myself metacontact can't be created now, because it will use + //ContactList::self() as parent which will call this constructor -> infinite loop + d->myself=0L; + + //no contactlist loaded yet, don't save them + d->loaded=false; + + // automatically save on changes to the list + d->saveTimer = new QTimer( this, "saveTimer" ); + connect( d->saveTimer, SIGNAL( timeout() ), SLOT ( save() ) ); + + connect( this, SIGNAL( metaContactAdded( Kopete::MetaContact * ) ), SLOT( slotSaveLater() ) ); + connect( this, SIGNAL( metaContactRemoved( Kopete::MetaContact * ) ), SLOT( slotSaveLater() ) ); + connect( this, SIGNAL( groupAdded( Kopete::Group * ) ), SLOT( slotSaveLater() ) ); + connect( this, SIGNAL( groupRemoved( Kopete::Group * ) ), SLOT( slotSaveLater() ) ); + connect( this, SIGNAL( groupRenamed( Kopete::Group *, const QString & ) ), SLOT( slotSaveLater() ) ); +} + +ContactList::~ContactList() +{ + delete d->myself; + delete d; +} + +QPtrList<MetaContact> ContactList::metaContacts() const +{ + return d->contacts; +} + + +QPtrList<Group> ContactList::groups() const +{ + return d->groups; +} + + +MetaContact *ContactList::metaContact( const QString &metaContactId ) const +{ + QPtrListIterator<MetaContact> it( d->contacts ); + + for( ; it.current(); ++it ) + { + if( it.current()->metaContactId() == metaContactId ) + return it.current(); + } + + return 0L; +} + + +Group * ContactList::group(unsigned int groupId) const +{ + Group *groupIterator; + for ( groupIterator = d->groups.first(); groupIterator; groupIterator = d->groups.next() ) + { + if( groupIterator->groupId()==groupId ) + return groupIterator; + } + return 0L; +} + + +Contact *ContactList::findContact( const QString &protocolId, + const QString &accountId, const QString &contactId ) const +{ + //Browsing metacontacts is too slow, better to uses the Dict of the account. + Account *i=AccountManager::self()->findAccount(protocolId,accountId); + if(!i) + { + kdDebug( 14010 ) << k_funcinfo << "Account not found" << endl; + return 0L; + } + return i->contacts()[contactId]; +} + + +MetaContact *ContactList::findMetaContactByDisplayName( const QString &displayName ) const +{ + QPtrListIterator<MetaContact> it( d->contacts ); + for( ; it.current(); ++it ) + { +// kdDebug(14010) << "Display Name: " << it.current()->displayName() << "\n"; + if( it.current()->displayName() == displayName ) { + return it.current(); + } + } + + return 0L; +} + +MetaContact* ContactList::findMetaContactByContactId( const QString &contactId ) const +{ + QPtrList<Account> acts=AccountManager::self()->accounts(); + QPtrListIterator<Account> it( acts ); + for ( ; it.current(); ++it ) + { + Contact *c=(*it)->contacts()[contactId]; + if(c && c->metaContact()) + return c->metaContact(); + } + return 0L; +} + +Group * ContactList::findGroup(const QString& displayName, int type) +{ + if( type == Group::Temporary ) + return Group::temporary(); + + Group *groupIterator; + for ( groupIterator = d->groups.first(); groupIterator; groupIterator = d->groups.next() ) + { + if( groupIterator->type() == type && groupIterator->displayName() == displayName ) + return groupIterator; + } + + Group *newGroup = new Group( displayName, (Group::GroupType)type ); + addGroup( newGroup ); + return newGroup; +} + + +QPtrList<MetaContact> ContactList::selectedMetaContacts() const +{ + return d->selectedMetaContacts; +} + +QPtrList<Group> ContactList::selectedGroups() const +{ + return d->selectedGroups; +} + + +void ContactList::addMetaContact( MetaContact *mc ) +{ + if ( d->contacts.contains( mc ) ) + return; + + d->contacts.append( mc ); + + emit metaContactAdded( mc ); + connect( mc, SIGNAL( persistentDataChanged( ) ), SLOT( slotSaveLater() ) ); + connect( mc, SIGNAL( addedToGroup( Kopete::MetaContact *, Kopete::Group * ) ), SIGNAL( metaContactAddedToGroup( Kopete::MetaContact *, Kopete::Group * ) ) ); + connect( mc, SIGNAL( removedFromGroup( Kopete::MetaContact *, Kopete::Group * ) ), SIGNAL( metaContactRemovedFromGroup( Kopete::MetaContact *, Kopete::Group * ) ) ); +} + + +void ContactList::removeMetaContact(MetaContact *m) +{ + if ( !d->contacts.contains(m) ) + { + kdDebug(14010) << k_funcinfo << "Trying to remove a not listed MetaContact." << endl; + return; + } + + if ( d->selectedMetaContacts.contains( m ) ) + { + d->selectedMetaContacts.remove( m ); + setSelectedItems( d->selectedMetaContacts, d->selectedGroups ); + } + + //removes subcontact from server here and now. + QPtrList<Contact> cts=m->contacts(); + for( Contact *c = cts.first(); c; c = cts.next() ) + { + c->deleteContact(); + } + + d->contacts.remove( m ); + emit metaContactRemoved( m ); + m->deleteLater(); +} + + +void ContactList::addGroup( Group * g ) +{ + if(!d->groups.contains(g) ) + { + d->groups.append( g ); + emit groupAdded( g ); + connect( g , SIGNAL ( displayNameChanged(Kopete::Group* , const QString & )) , this , SIGNAL ( groupRenamed(Kopete::Group* , const QString & )) ) ; + } +} + +void ContactList::removeGroup( Group *g ) +{ + if ( d->selectedGroups.contains( g ) ) + { + d->selectedGroups.remove( g ); + setSelectedItems( d->selectedMetaContacts, d->selectedGroups ); + } + + d->groups.remove( g ); + emit groupRemoved( g ); + g->deleteLater(); +} + + +void ContactList::setSelectedItems(QPtrList<MetaContact> metaContacts , QPtrList<Group> groups) +{ + kdDebug( 14010 ) << k_funcinfo << metaContacts.count() << " metacontacts, " << groups.count() << " groups selected" << endl; + d->selectedMetaContacts=metaContacts; + d->selectedGroups=groups; + + emit metaContactSelected( groups.isEmpty() && metaContacts.count()==1 ); + emit selectionChanged(); +} + +MetaContact* ContactList::myself() +{ + if(!d->myself) + d->myself=new MetaContact(); + return d->myself; +} + +void ContactList::loadGlobalIdentity() +{ + // Apply the global identity + if(Kopete::Config::enableGlobalIdentity()) + { + // Disconnect to make sure it will not cause duplicate calls. + disconnect(myself(), SIGNAL(displayNameChanged(const QString&, const QString&)), this, SLOT(slotDisplayNameChanged())); + disconnect(myself(), SIGNAL(photoChanged()), this, SLOT(slotPhotoChanged())); + + connect(myself(), SIGNAL(displayNameChanged(const QString&, const QString&)), this, SLOT(slotDisplayNameChanged())); + connect(myself(), SIGNAL(photoChanged()), this, SLOT(slotPhotoChanged())); + + // Ensure that the myself metaContactId is always the KABC whoAmI + KABC::Addressee a = KABC::StdAddressBook::self()->whoAmI(); + if(!a.isEmpty() && a.uid() != myself()->metaContactId()) + { + myself()->setMetaContactId(a.uid()); + } + + // Apply the global identity + // Maybe one of the myself contact from a account has a different displayName/photo at startup. + slotDisplayNameChanged(); + slotPhotoChanged(); + } + else + { + disconnect(myself(), SIGNAL(displayNameChanged(const QString&, const QString&)), this, SLOT(slotDisplayNameChanged())); + disconnect(myself(), SIGNAL(photoChanged()), this, SLOT(slotPhotoChanged())); + } +} + +void ContactList::slotDisplayNameChanged() +{ + static bool mutex=false; + if(mutex) + { + kdDebug (14010) << k_funcinfo << " mutex blocked" << endl ; + return; + } + mutex=true; + + kdDebug( 14010 ) << k_funcinfo << myself()->displayName() << endl; + + emit globalIdentityChanged(Kopete::Global::Properties::self()->nickName().key(), myself()->displayName()); + mutex=false; +} + +void ContactList::slotPhotoChanged() +{ + static bool mutex=false; + if(mutex) + { + kdDebug (14010) << k_funcinfo << " mutex blocked" << endl ; + return; + } + mutex=true; + kdDebug( 14010 ) << k_funcinfo << myself()->picture().path() << endl; + + emit globalIdentityChanged(Kopete::Global::Properties::self()->photo().key(), myself()->picture().path()); + mutex=false; + /* The mutex is usefull to don't have such as stack overflow + Kopete::ContactList::slotPhotoChanged -> Kopete::ContactList::globalIdentityChanged + MSNAccount::slotGlobalIdentityChanged -> Kopete::Contact::propertyChanged + Kopete::MetaContact::slotPropertyChanged -> Kopete::MetaContact::photoChanged -> Kopete::ContactList::slotPhotoChanged + */ +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +void ContactList::load() +{ + loadXML(); + // Apply the global identity when all the protocols plugins are loaded. + connect(PluginManager::self(), SIGNAL(allPluginsLoaded()), this, SLOT(loadGlobalIdentity())); +} + +void ContactList::loadXML() +{ + // don't save when we're in the middle of this... + d->loaded = false; + + QString filename = locateLocal( "appdata", QString::fromLatin1( "contactlist.xml" ) ); + if( filename.isEmpty() ) + { + d->loaded=true; + return ; + } + + QDomDocument contactList( QString::fromLatin1( "kopete-contact-list" ) ); + + QFile contactListFile( filename ); + contactListFile.open( IO_ReadOnly ); + contactList.setContent( &contactListFile ); + + QDomElement list = contactList.documentElement(); + + QString versionString = list.attribute( QString::fromLatin1( "version" ), QString::null ); + uint version = 0; + if( QRegExp( QString::fromLatin1( "[0-9]+\\.[0-9]" ) ).exactMatch( versionString ) ) + version = versionString.replace( QString::fromLatin1( "." ), QString::null ).toUInt(); + + if( version < Private::ContactListVersion ) + { + // The version string is invalid, or we're using an older version. + // Convert first and reparse the file afterwards + kdDebug( 14010 ) << k_funcinfo << "Contact list version " << version + << " is older than current version " << Private::ContactListVersion + << ". Converting first." << endl; + + contactListFile.close(); + + convertContactList( filename, version, Private::ContactListVersion ); + + contactList = QDomDocument ( QString::fromLatin1( "kopete-contact-list" ) ); + + contactListFile.open( IO_ReadOnly ); + contactList.setContent( &contactListFile ); + + list = contactList.documentElement(); + } + + addGroup( Kopete::Group::topLevel() ); + + QDomElement element = list.firstChild().toElement(); + while( !element.isNull() ) + { + if( element.tagName() == QString::fromLatin1("meta-contact") ) + { + //TODO: id isn't used + //QString id = element.attribute( "id", QString::null ); + Kopete::MetaContact *metaContact = new Kopete::MetaContact(); + if ( !metaContact->fromXML( element ) ) + { + delete metaContact; + metaContact = 0; + } + else + { + Kopete::ContactList::self()->addMetaContact( + metaContact ); + } + } + else if( element.tagName() == QString::fromLatin1("kopete-group") ) + { + Kopete::Group *group = new Kopete::Group(); + if( !group->fromXML( element ) ) + { + delete group; + group = 0; + } + else + { + Kopete::ContactList::self()->addGroup( group ); + } + } + // Only load myself metacontact information when Global Identity is enabled. + else if( element.tagName() == QString::fromLatin1("myself-meta-contact") && Kopete::Config::enableGlobalIdentity() ) + { + if( !myself()->fromXML( element ) ) + { + delete d->myself; + d->myself = 0; + } + } + else + { + kdWarning(14010) << "Kopete::ContactList::loadXML: " + << "Unknown element '" << element.tagName() + << "' in contact list!" << endl; + } + element = element.nextSibling().toElement(); + } + contactListFile.close(); + d->loaded=true; +} + +void ContactList::convertContactList( const QString &fileName, uint /* fromVersion */, uint /* toVersion */ ) +{ + // For now, ignore fromVersion and toVersion. These are meant for future + // changes to allow incremental (multi-pass) conversion so we don't have + // to rewrite the whole conversion code for each change. + + QDomDocument contactList( QString::fromLatin1( "messaging-contact-list" ) ); + QFile contactListFile( fileName ); + contactListFile.open( IO_ReadOnly ); + contactList.setContent( &contactListFile ); + + QDomElement oldList = contactList.documentElement(); + + QDomDocument newList( QString::fromLatin1( "kopete-contact-list" ) ); + newList.appendChild( newList.createProcessingInstruction( QString::fromLatin1( "xml" ), QString::fromLatin1( "version=\"1.0\"" ) ) ); + + QDomElement newRoot = newList.createElement( QString::fromLatin1( "kopete-contact-list" ) ); + newList.appendChild( newRoot ); + newRoot.setAttribute( QString::fromLatin1( "version" ), QString::fromLatin1( "1.0" ) ); + + QDomNode oldNode = oldList.firstChild(); + while( !oldNode.isNull() ) + { + QDomElement oldElement = oldNode.toElement(); + if( !oldElement.isNull() ) + { + if( oldElement.tagName() == QString::fromLatin1("meta-contact") ) + { + // Ignore ID, it is not used in the current list anyway + QDomElement newMetaContact = newList.createElement( QString::fromLatin1( "meta-contact" ) ); + newRoot.appendChild( newMetaContact ); + + // Plugin data is stored completely different, and requires + // some bookkeeping to convert properly + QMap<QString, QDomElement> pluginData; + QStringList icqData; + QStringList gaduData; + + // ICQ and Gadu can only be converted properly if the address book fields + // are already parsed. Therefore, scan for those first and add the rest + // afterwards + QDomNode oldContactNode = oldNode.firstChild(); + while( !oldContactNode.isNull() ) + { + QDomElement oldContactElement = oldContactNode.toElement(); + if( !oldContactElement.isNull() && oldContactElement.tagName() == QString::fromLatin1("address-book-field") ) + { + // Convert address book fields. + // Jabber will be called "xmpp", Aim/Toc and Aim/Oscar both will + // be called "aim". MSN, AIM, IRC, Oscar and SMS don't use address + // book fields yet; Gadu and ICQ can be converted as-is. + // As Yahoo is unfinished we won't try to convert at all. + QString id = oldContactElement.attribute( QString::fromLatin1( "id" ), QString::null ); + QString data = oldContactElement.text(); + + QString app, key, val; + QString separator = QString::fromLatin1( "," ); + if( id == QString::fromLatin1( "messaging/gadu" ) ) + separator = QString::fromLatin1( "\n" ); + else if( id == QString::fromLatin1( "messaging/icq" ) ) + separator = QString::fromLatin1( ";" ); + else if( id == QString::fromLatin1( "messaging/jabber" ) ) + id = QString::fromLatin1( "messaging/xmpp" ); + + if( id == QString::fromLatin1( "messaging/gadu" ) || id == QString::fromLatin1( "messaging/icq" ) || + id == QString::fromLatin1( "messaging/winpopup" ) || id == QString::fromLatin1( "messaging/xmpp" ) ) + { + app = id; + key = QString::fromLatin1( "All" ); + val = data.replace( separator, QChar( 0xE000 ) ); + } + + if( !app.isEmpty() ) + { + QDomElement addressBookField = newList.createElement( QString::fromLatin1( "address-book-field" ) ); + newMetaContact.appendChild( addressBookField ); + + addressBookField.setAttribute( QString::fromLatin1( "app" ), app ); + addressBookField.setAttribute( QString::fromLatin1( "key" ), key ); + + addressBookField.appendChild( newList.createTextNode( val ) ); + + // ICQ didn't store the contactId locally, only in the address + // book fields, so we need to be able to access it later + if( id == QString::fromLatin1( "messaging/icq" ) ) + icqData = QStringList::split( QChar( 0xE000 ), val ); + else if( id == QString::fromLatin1("messaging/gadu") ) + gaduData = QStringList::split( QChar( 0xE000 ), val ); + } + } + oldContactNode = oldContactNode.nextSibling(); + } + + // Now, convert the other elements + oldContactNode = oldNode.firstChild(); + while( !oldContactNode.isNull() ) + { + QDomElement oldContactElement = oldContactNode.toElement(); + if( !oldContactElement.isNull() ) + { + if( oldContactElement.tagName() == QString::fromLatin1("display-name") ) + { + QDomElement displayName = newList.createElement( QString::fromLatin1( "display-name" ) ); + displayName.appendChild( newList.createTextNode( oldContactElement.text() ) ); + newMetaContact.appendChild( displayName ); + } + else if( oldContactElement.tagName() == QString::fromLatin1("groups") ) + { + QDomElement groups = newList.createElement( QString::fromLatin1( "groups" ) ); + newMetaContact.appendChild( groups ); + + QDomNode oldGroup = oldContactElement.firstChild(); + while( !oldGroup.isNull() ) + { + QDomElement oldGroupElement = oldGroup.toElement(); + if ( oldGroupElement.tagName() == QString::fromLatin1("group") ) + { + QDomElement group = newList.createElement( QString::fromLatin1( "group" ) ); + group.appendChild( newList.createTextNode( oldGroupElement.text() ) ); + groups.appendChild( group ); + } + else if ( oldGroupElement.tagName() == QString::fromLatin1("top-level") ) + { + QDomElement group = newList.createElement( QString::fromLatin1( "top-level" ) ); + groups.appendChild( group ); + } + + oldGroup = oldGroup.nextSibling(); + } + } + else if( oldContactElement.tagName() == QString::fromLatin1( "plugin-data" ) ) + { + // Convert the plugin data + QString id = oldContactElement.attribute( QString::fromLatin1( "plugin-id" ), QString::null ); + QString data = oldContactElement.text(); + + bool convertOldAim = false; + uint fieldCount = 1; + QString addressBookLabel; + if( id == QString::fromLatin1("MSNProtocol") ) + { + fieldCount = 3; + addressBookLabel = QString::fromLatin1("msn"); + } + else if( id == QString::fromLatin1("IRCProtocol") ) + { + fieldCount = 3; + addressBookLabel = QString::fromLatin1("irc"); + } + else if( id == QString::fromLatin1("OscarProtocol") ) + { + fieldCount = 2; + addressBookLabel = QString::fromLatin1("aim"); + } + else if( id == QString::fromLatin1("AIMProtocol") ) + { + id = QString::fromLatin1("OscarProtocol"); + convertOldAim = true; + addressBookLabel = QString::fromLatin1("aim"); + } + else if( id == QString::fromLatin1("ICQProtocol") || id == QString::fromLatin1("WPProtocol") || id == QString::fromLatin1("GaduProtocol") ) + { + fieldCount = 1; + } + else if( id == QString::fromLatin1("JabberProtocol") ) + { + fieldCount = 4; + } + else if( id == QString::fromLatin1("SMSProtocol") ) + { + // SMS used a variable serializing using a dot as delimiter. + // The minimal count is three though (id, name, delimiter). + fieldCount = 2; + addressBookLabel = QString::fromLatin1("sms"); + } + + if( pluginData[ id ].isNull() ) + { + pluginData[ id ] = newList.createElement( QString::fromLatin1( "plugin-data" ) ); + pluginData[ id ].setAttribute( QString::fromLatin1( "plugin-id" ), id ); + newMetaContact.appendChild( pluginData[ id ] ); + } + + // Do the actual conversion + if( id == QString::fromLatin1( "MSNProtocol" ) || id == QString::fromLatin1( "OscarProtocol" ) || + id == QString::fromLatin1( "AIMProtocol" ) || id == QString::fromLatin1( "IRCProtocol" ) || + id == QString::fromLatin1( "ICQProtocol" ) || id == QString::fromLatin1( "JabberProtocol" ) || + id == QString::fromLatin1( "SMSProtocol" ) || id == QString::fromLatin1( "WPProtocol" ) || + id == QString::fromLatin1( "GaduProtocol" ) ) + { + QStringList strList = QStringList::split( QString::fromLatin1( "||" ), data ); + + // Unescape '||' + for( QStringList::iterator it = strList.begin(); it != strList.end(); ++it ) + { + ( *it ).replace( QString::fromLatin1( "\\|;" ), QString::fromLatin1( "|" ) ). + replace( QString::fromLatin1( "\\\\" ), QString::fromLatin1( "\\" ) ); + } + + uint idx = 0; + while( idx < strList.size() ) + { + QDomElement dataField; + + dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "contactId" ) ); + if( id == QString::fromLatin1("ICQProtocol") ) + dataField.appendChild( newList.createTextNode( icqData[ idx ] ) ); + else if( id == QString::fromLatin1("GaduProtocol") ) + dataField.appendChild( newList.createTextNode( gaduData[ idx ] ) ); + else if( id == QString::fromLatin1("JabberProtocol") ) + dataField.appendChild( newList.createTextNode( strList[ idx + 1 ] ) ); + else + dataField.appendChild( newList.createTextNode( strList[ idx ] ) ); + + dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "displayName" ) ); + if( convertOldAim || id == QString::fromLatin1("ICQProtocol") || id == QString::fromLatin1("WPProtocol") || id == QString::fromLatin1("GaduProtocol") ) + dataField.appendChild( newList.createTextNode( strList[ idx ] ) ); + else if( id == QString::fromLatin1("JabberProtocol") ) + dataField.appendChild( newList.createTextNode( strList[ idx + 2 ] ) ); + else + dataField.appendChild( newList.createTextNode( strList[ idx + 1 ] ) ); + + if( id == QString::fromLatin1("MSNProtocol") ) + { + dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "groups" ) ); + dataField.appendChild( newList.createTextNode( strList[ idx + 2 ] ) ); + } + else if( id == QString::fromLatin1("IRCProtocol") ) + { + dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "serverName" ) ); + dataField.appendChild( newList.createTextNode( strList[ idx + 2 ] ) ); + } + else if( id == QString::fromLatin1("JabberProtocol") ) + { + dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "accountId" ) ); + dataField.appendChild( newList.createTextNode( strList[ idx ] ) ); + + dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "groups" ) ); + dataField.appendChild( newList.createTextNode( strList[ idx + 3 ] ) ); + } + else if( id == QString::fromLatin1( "SMSProtocol" ) && + ( idx + 2 < strList.size() ) && strList[ idx + 2 ] != QString::fromLatin1( "." ) ) + { + dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "serviceName" ) ); + dataField.appendChild( newList.createTextNode( strList[ idx + 2 ] ) ); + + dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "servicePrefs" ) ); + dataField.appendChild( newList.createTextNode( strList[ idx + 3 ] ) ); + + // Add extra fields + idx += 2; + } + + // MSN, AIM, IRC, Oscar and SMS didn't store address book fields up + // to now, so create one + if( id != QString::fromLatin1("ICQProtocol") && id != QString::fromLatin1("JabberProtocol") && id != QString::fromLatin1("WPProtocol") && id != QString::fromLatin1("GaduProtocol") ) + { + QDomElement addressBookField = newList.createElement( QString::fromLatin1( "address-book-field" ) ); + newMetaContact.appendChild( addressBookField ); + + addressBookField.setAttribute( QString::fromLatin1( "app" ), + QString::fromLatin1( "messaging/" ) + addressBookLabel ); + addressBookField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "All" ) ); + addressBookField.appendChild( newList.createTextNode( strList[ idx ] ) ); + } + + idx += fieldCount; + } + } + else if( id == QString::fromLatin1("ContactNotesPlugin") || id == QString::fromLatin1("CryptographyPlugin") || id == QString::fromLatin1("TranslatorPlugin") ) + { + QDomElement dataField = newList.createElement( QString::fromLatin1( "plugin-data-field" ) ); + pluginData[ id ].appendChild( dataField ); + if( id == QString::fromLatin1("ContactNotesPlugin") ) + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "notes" ) ); + else if( id == QString::fromLatin1("CryptographyPlugin") ) + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "gpgKey" ) ); + else if( id == QString::fromLatin1("TranslatorPlugin") ) + dataField.setAttribute( QString::fromLatin1( "key" ), QString::fromLatin1( "languageKey" ) ); + + dataField.appendChild( newList.createTextNode( data ) ); + } + } + } + oldContactNode = oldContactNode.nextSibling(); + } + } + else if( oldElement.tagName() == QString::fromLatin1("kopete-group") ) + { + QDomElement newGroup = newList.createElement( QString::fromLatin1( "kopete-group" ) ); + newRoot.appendChild( newGroup ); + + QDomNode oldGroupNode = oldNode.firstChild(); + while( !oldGroupNode.isNull() ) + { + QDomElement oldGroupElement = oldGroupNode.toElement(); + + if( oldGroupElement.tagName() == QString::fromLatin1("display-name") ) + { + QDomElement displayName = newList.createElement( QString::fromLatin1( "display-name" ) ); + displayName.appendChild( newList.createTextNode( oldGroupElement.text() ) ); + newGroup.appendChild( displayName ); + } + if( oldGroupElement.tagName() == QString::fromLatin1("type") ) + { + if( oldGroupElement.text() == QString::fromLatin1("Temporary") ) + newGroup.setAttribute( QString::fromLatin1( "type" ), QString::fromLatin1( "temporary" ) ); + else if( oldGroupElement.text() == QString::fromLatin1( "TopLevel" ) ) + newGroup.setAttribute( QString::fromLatin1( "type" ), QString::fromLatin1( "top-level" ) ); + else + newGroup.setAttribute( QString::fromLatin1( "type" ), QString::fromLatin1( "standard" ) ); + } + if( oldGroupElement.tagName() == QString::fromLatin1("view") ) + { + if( oldGroupElement.text() == QString::fromLatin1("collapsed") ) + newGroup.setAttribute( QString::fromLatin1( "view" ), QString::fromLatin1( "collapsed" ) ); + else + newGroup.setAttribute( QString::fromLatin1( "view" ), QString::fromLatin1( "expanded" ) ); + } + else if( oldGroupElement.tagName() == QString::fromLatin1("plugin-data") ) + { + // Per-group plugin data + // FIXME: This needs updating too, ideally, convert this in a later + // contactlist.xml version + QDomElement groupPluginData = newList.createElement( QString::fromLatin1( "plugin-data" ) ); + newGroup.appendChild( groupPluginData ); + + groupPluginData.setAttribute( QString::fromLatin1( "plugin-id" ), + oldGroupElement.attribute( QString::fromLatin1( "plugin-id" ), QString::null ) ); + groupPluginData.appendChild( newList.createTextNode( oldGroupElement.text() ) ); + } + + oldGroupNode = oldGroupNode.nextSibling(); + } + } + else + { + kdWarning( 14010 ) << k_funcinfo << "Unknown element '" << oldElement.tagName() + << "' in contact list!" << endl; + } + } + oldNode = oldNode.nextSibling(); + } + + // Close the file, and save the new file + contactListFile.close(); + + QDir().rename( fileName, fileName + QString::fromLatin1( ".bak" ) ); + + // kdDebug( 14010 ) << k_funcinfo << "XML output:\n" << newList.toString( 2 ) << endl; + + contactListFile.open( IO_WriteOnly ); + QTextStream stream( &contactListFile ); + stream.setEncoding( QTextStream::UnicodeUTF8 ); + stream << newList.toString( 2 ); + + contactListFile.flush(); + contactListFile.close(); +} + +void Kopete::ContactList::save() +{ + saveXML(); +} + +void Kopete::ContactList::saveXML() +{ + if(!d->loaded) + { + kdDebug(14010) << "Kopete::ContactList::saveXML: contactlist not loaded, abort saving" << endl; + return; + } + + QString contactListFileName = locateLocal( "appdata", QString::fromLatin1( "contactlist.xml" ) ); + KSaveFile contactListFile( contactListFileName ); + if( contactListFile.status() == 0 ) + { + QTextStream *stream = contactListFile.textStream(); + stream->setEncoding( QTextStream::UnicodeUTF8 ); + toXML().save( *stream, 4 ); + + if ( contactListFile.close() ) + { + // cancel any scheduled saves + d->saveTimer->stop(); + return; + } + else + { + kdDebug(14010) << "Kopete::ContactList::saveXML: failed to write contactlist, error code is: " << contactListFile.status() << endl; + } + } + else + { + kdWarning(14010) << "Kopete::ContactList::saveXML: Couldn't open contact list file " + << contactListFileName << ". Contact list not saved." << endl; + } + + // if we got here, saving the contact list failed. retry every minute until it works. + d->saveTimer->start( 60000, true /* single-shot: will get restarted by us next time if it's still failing */ ); +} + +const QDomDocument ContactList::toXML() +{ + QDomDocument doc; + doc.appendChild( doc.createElement( QString::fromLatin1("kopete-contact-list") ) ); + doc.documentElement().setAttribute( QString::fromLatin1("version"), QString::fromLatin1("1.0")); + + // Save group information. ie: Open/Closed, pehaps later icons? Who knows. + for( Kopete::Group *g = d->groups.first(); g; g = d->groups.next() ) + doc.documentElement().appendChild( doc.importNode( g->toXML(), true ) ); + + // Save metacontact information. + for( Kopete::MetaContact *m = d->contacts.first(); m; m = d->contacts.next() ) + if( !m->isTemporary() ) + doc.documentElement().appendChild( doc.importNode( m->toXML(), true ) ); + + // Save myself metacontact information + if( Kopete::Config::enableGlobalIdentity() ) + { + QDomElement myselfElement = myself()->toXML(true); // Save minimal information. + myselfElement.setTagName( QString::fromLatin1("myself-meta-contact") ); + doc.documentElement().appendChild( doc.importNode( myselfElement, true ) ); + } + + return doc; +} + +QStringList ContactList::contacts() const +{ + QStringList contacts; + QPtrListIterator<Kopete::MetaContact> it( d->contacts ); + for( ; it.current(); ++it ) + { + contacts.append( it.current()->displayName() ); + } + return contacts; +} + +QStringList ContactList::contactStatuses() const +{ + QStringList meta_contacts; + QPtrListIterator<Kopete::MetaContact> it( d->contacts ); + for( ; it.current(); ++it ) + { + meta_contacts.append( QString::fromLatin1( "%1 (%2)" ). + arg( it.current()->displayName(), it.current()->statusString() )); + } + return meta_contacts; +} + +QStringList ContactList::reachableContacts() const +{ + QStringList contacts; + QPtrListIterator<Kopete::MetaContact> it( d->contacts ); + for( ; it.current(); ++it ) + { + if ( it.current()->isReachable() ) + contacts.append( it.current()->displayName() ); + } + return contacts; +} + +QPtrList<Contact> ContactList::onlineContacts() const +{ + QPtrList<Kopete::Contact> result; + QPtrListIterator<Kopete::MetaContact> it( d->contacts ); + for( ; it.current(); ++it ) + { + if ( it.current()->isOnline() ) + { + QPtrList<Kopete::Contact> contacts = it.current()->contacts(); + QPtrListIterator<Kopete::Contact> cit( contacts ); + for( ; cit.current(); ++cit ) + { + if ( cit.current()->isOnline() ) + result.append( cit.current() ); + } + } + } + return result; +} + +QPtrList<Kopete::MetaContact> Kopete::ContactList::onlineMetaContacts() const +{ + QPtrList<Kopete::MetaContact> result; + QPtrListIterator<Kopete::MetaContact> it( d->contacts ); + for( ; it.current(); ++it ) + { + if ( it.current()->isOnline() ) + result.append( it.current() ); + } + return result; +} + +QPtrList<Kopete::MetaContact> Kopete::ContactList::onlineMetaContacts( const QString &protocolId ) const +{ + QPtrList<Kopete::MetaContact> result; + QPtrListIterator<Kopete::MetaContact> it( d->contacts ); + for( ; it.current(); ++it ) + { + // FIXME: This loop is not very efficient :( + if ( it.current()->isOnline() ) + { + QPtrList<Kopete::Contact> contacts = it.current()->contacts(); + QPtrListIterator<Kopete::Contact> cit( contacts ); + for( ; cit.current(); ++cit ) + { + if( cit.current()->isOnline() && cit.current()->protocol()->pluginId() == protocolId ) + result.append( it.current() ); + } + } + } + return result; +} + +QPtrList<Kopete::Contact> Kopete::ContactList::onlineContacts( const QString &protocolId ) const +{ + QPtrList<Kopete::Contact> result; + QPtrListIterator<Kopete::MetaContact> it( d->contacts ); + for( ; it.current(); ++it ) + { + // FIXME: This loop is not very efficient :( + if ( it.current()->isOnline() ) + { + QPtrList<Kopete::Contact> contacts = it.current()->contacts(); + QPtrListIterator<Kopete::Contact> cit( contacts ); + for( ; cit.current(); ++cit ) + { + if( cit.current()->isOnline() && cit.current()->protocol()->pluginId() == protocolId ) + result.append( cit.current() ); + } + } + } + return result; +} + +QStringList Kopete::ContactList::fileTransferContacts() const +{ + QStringList contacts; + QPtrListIterator<Kopete::MetaContact> it( d->contacts ); + for( ; it.current(); ++it ) + { + if ( it.current()->canAcceptFiles() ) + contacts.append( it.current()->displayName() ); + } + return contacts; +} + +void Kopete::ContactList::sendFile( const QString &displayName, const KURL &sourceURL, + const QString &altFileName, const long unsigned int fileSize) +{ +// kdDebug(14010) << "Send To Display Name: " << displayName << "\n"; + + Kopete::MetaContact *c = findMetaContactByDisplayName( displayName ); + if( c ) + c->sendFile( sourceURL, altFileName, fileSize ); +} + +void Kopete::ContactList::messageContact( const QString &contactId, const QString &messageText ) +{ + Kopete::MetaContact *mc = findMetaContactByContactId( contactId ); + if (!mc) return; + + Kopete::Contact *c = mc->execute(); //We need to know which contact was chosen as the preferred in order to message it + if (!c) return; + + Kopete::Message msg(c->account()->myself(), c, messageText, Kopete::Message::Outbound); + c->manager(Contact::CanCreate)->sendMessage(msg); + +} + + +QStringList Kopete::ContactList::contactFileProtocols(const QString &displayName) +{ +// kdDebug(14010) << "Get contacts for: " << displayName << "\n"; + QStringList protocols; + + Kopete::MetaContact *c = findMetaContactByDisplayName( displayName ); + if( c ) + { + QPtrList<Kopete::Contact> mContacts = c->contacts(); + kdDebug(14010) << mContacts.count() << endl; + QPtrListIterator<Kopete::Contact> jt( mContacts ); + for ( ; jt.current(); ++jt ) + { + kdDebug(14010) << "1" << jt.current()->protocol()->pluginId() << endl; + if( jt.current()->canAcceptFiles() ) { + kdDebug(14010) << jt.current()->protocol()->pluginId() << endl; + protocols.append ( jt.current()->protocol()->pluginId() ); + } + } + return protocols; + } + return QStringList(); +} + + +void ContactList::slotSaveLater() +{ + // if we already have a save scheduled, it will be cancelled. either way, + // start a timer to save the contact list a bit later. + d->saveTimer->start( 17100 /* 17,1 seconds */, true /* single-shot */ ); +} + +void ContactList::slotKABCChanged() +{ + // TODO: react to changes in KABC, replacing this function, post 3.4 (Will) + // call syncWithKABC on each metacontact to check if its associated kabc entry has changed. +/* for ( MetaContact * mc = d->contacts.first(); mc; mc = d->contacts.next() ) + + mc->syncWithKABC();*/ +} + + +} //END namespace Kopete + +#include "kopetecontactlist.moc" + +// vim: set noet ts=4 sts=4 sw=4: + |