/* addressbooklink.cpp - Manages operations involving the KDE Address Book Copyright (c) 2005 Will Stephenson Kopete (c) 2002-2004 by the Kopete developers ************************************************************************* * * * 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 #include #include #include #include #include // UI related includes used for importing from KABC #include #include #include #include "accountselector.h" #include "kopeteuiglobal.h" #include #include "kopeteaccount.h" #include "kopeteaccountmanager.h" #include "kopetecontact.h" #include "kopetemetacontact.h" #include "kopetepluginmanager.h" #include "kopeteprotocol.h" #include "kabcpersistence.h" namespace Kopete { /** * utility function to merge two TQStrings containing individual elements separated by 0xE000 */ static TQString unionContents( TQString arg1, TQString arg2 ) { TQChar separator( 0xE000 ); TQStringList outList = TQStringList::split( separator, arg1 ); TQStringList arg2List = TQStringList::split( separator, arg2 ); for ( TQStringList::iterator it = arg2List.begin(); it != arg2List.end(); ++it ) if ( !outList.contains( *it ) ) outList.append( *it ); TQString out = outList.join( separator ); return out; } KABCPersistence::KABCPersistence( TQObject * parent, const char * name ) : TQObject( parent, name ) { s_pendingResources.setAutoDelete( false ); } KABCPersistence::~KABCPersistence() { } KABCPersistence *KABCPersistence::s_self = 0L; bool KABCPersistence::s_addrBookWritePending = false; TQPtrList KABCPersistence::s_pendingResources; KABC::AddressBook* KABCPersistence::s_addressBook = 0; KABCPersistence *KABCPersistence::self() { static KStaticDeleter deleter; if(!s_self) deleter.setObject( s_self, new KABCPersistence() ); return s_self; } KABC::AddressBook* KABCPersistence::addressBook() { if ( s_addressBook == 0L ) { s_addressBook = KABC::StdAddressBook::self(); KABC::StdAddressBook::setAutomaticSave( false ); } return s_addressBook; } void KABCPersistence::write( MetaContact * mc ) { // Save any changes in each contact's addressBookFields to KABC KABC::AddressBook* ab = addressBook(); kdDebug( 14010 ) << k_funcinfo << "looking up Addressee for " << mc->displayName() << "..." << endl; // Look up the address book entry KABC::Addressee theAddressee = ab->findByUid( mc->metaContactId() ); // Check that if addressee is not deleted or if the link is spurious // (inherited from Kopete < 0.8, where all metacontacts had random ids) if ( theAddressee.isEmpty() ) { // not found in currently enabled addressbooks - may be in a disabled resource... return; } else { // collate the instant messaging data to be inserted into the address book TQMap addressMap; TQPtrList contacts = mc->contacts(); TQPtrListIterator cIt( contacts ); while ( Contact * c = cIt.current() ) { TQStringList addresses = addressMap[ c->protocol()->addressBookIndexField() ]; addresses.append( c->contactId() ); addressMap.insert( c->protocol()->addressBookIndexField(), addresses ); ++cIt; } // insert a custom field for each protocol TQMap::ConstIterator it = addressMap.begin(); for ( ; it != addressMap.end(); ++it ) { // read existing data for this key TQString currentCustomForProtocol = theAddressee.custom( it.key(), TQString::fromLatin1( "All" ) ); // merge without duplicating TQString toWrite = unionContents( currentCustomForProtocol, it.data().join( TQChar( 0xE000 ) ) ); // Note if nothing ends up in the KABC data, this is because insertCustom does nothing if any param is empty. kdDebug( 14010 ) << k_funcinfo << "Writing: " << it.key() << ", " << "All" << ", " << toWrite << endl; theAddressee.insertCustom( it.key(), TQString::fromLatin1( "All" ), toWrite ); TQString check = theAddressee.custom( it.key(), TQString::fromLatin1( "All" ) ); } ab->insertAddressee( theAddressee ); //kdDebug( 14010 ) << k_funcinfo << "dumping addressbook before write " << endl; //dumpAB(); writeAddressBook( theAddressee.resource() ); //theAddressee.dump(); } /* // Wipe out the existing addressBook entries d->addressBook.clear(); // This causes each Kopete::Protocol subclass to serialise its contacts' data into the metacontact's plugin data and address book data emit aboutToSave(this); kdDebug( 14010 ) << k_funcinfo << "...FOUND ONE!" << endl; // Store address book fields TQMap >::ConstIterator appIt = d->addressBook.begin(); for( ; appIt != d->addressBook.end(); ++appIt ) { TQMap::ConstIterator addrIt = appIt.data().begin(); for( ; addrIt != appIt.data().end(); ++addrIt ) { // read existing data for this key TQString currentCustom = theAddressee.custom( appIt.key(), addrIt.key() ); // merge without duplicating TQString toWrite = unionContents( currentCustom, addrIt.data() ); // write the result // Note if nothing ends up in the KABC data, this is because insertCustom does nothing if any param is empty. kdDebug( 14010 ) << k_funcinfo << "Writing: " << appIt.key() << ", " << addrIt.key() << ", " << toWrite << endl; theAddressee.insertCustom( appIt.key(), addrIt.key(), toWrite ); } } ab->insertAddressee( theAddressee ); writeAddressBook(); }*/ } void KABCPersistence::writeAddressBook( const KABC::Resource * res) { if ( !s_pendingResources.containsRef( res ) ) s_pendingResources.append( res ); if ( !s_addrBookWritePending ) { s_addrBookWritePending = true; TQTimer::singleShot( 2000, this, TQT_SLOT( slotWriteAddressBook() ) ); } } void KABCPersistence::slotWriteAddressBook() { //kdDebug( 14010 ) << k_funcinfo << endl; KABC::AddressBook* ab = addressBook(); TQPtrListIterator it( s_pendingResources ); for ( ; it.current(); ++it ) { //kdDebug( 14010 ) << "Writing resource " << it.current()->resourceName() << endl; KABC::Ticket *ticket = ab->requestSaveTicket( it.current() ); if ( !ticket ) kdWarning( 14010 ) << "WARNING: Resource is locked by other application!" << endl; else { if ( !ab->save( ticket ) ) { kdWarning( 14010 ) << "ERROR: Saving failed!" << endl; ab->releaseSaveTicket( ticket ); } } //kdDebug( 14010 ) << "Finished writing KABC" << endl; } s_pendingResources.clear(); s_addrBookWritePending = false; } void KABCPersistence::removeKABC( MetaContact *) { /* // remove any data this KMC has written to the TDE address book // Save any changes in each contact's addressBookFields to KABC KABC::AddressBook* ab = addressBook(); // Wipe out the existing addressBook entries d->addressBook.clear(); // This causes each Kopete::Protocol subclass to serialise its contacts' data into the metacontact's plugin data and address book data emit aboutToSave(this); // If the metacontact is linked to a kabc entry if ( !d->metaContactId.isEmpty() ) { //kdDebug( 14010 ) << k_funcinfo << "looking up Addressee for " << displayName() << "..." << endl; // Look up the address book entry KABC::Addressee theAddressee = ab->findByUid( metaContactId() ); if ( theAddressee.isEmpty() ) { // remove the link //kdDebug( 14010 ) << k_funcinfo << "...not found." << endl; d->metaContactId=TQString(); } else { //kdDebug( 14010 ) << k_funcinfo << "...FOUND ONE!" << endl; // Remove address book fields TQMap >::ConstIterator appIt = d->addressBook.begin(); for( ; appIt != d->addressBook.end(); ++appIt ) { TQMap::ConstIterator addrIt = appIt.data().begin(); for( ; addrIt != appIt.data().end(); ++addrIt ) { // FIXME: This assumes Kopete is the only app writing these fields kdDebug( 14010 ) << k_funcinfo << "Removing: " << appIt.key() << ", " << addrIt.key() << endl; theAddressee.removeCustom( appIt.key(), addrIt.key() ); } } ab->insertAddressee( theAddressee ); writeAddressBook(); } } // kdDebug(14010) << k_funcinfo << kdBacktrace() <findByUid( mc->metaContactId() ); if ( !addr.isEmpty() ) // if we are associated with KABC { // load the set of addresses from KABC TQStringList customs = addr.customs(); TQStringList::ConstIterator it; for ( it = customs.begin(); it != customs.end(); ++it ) { TQString app, name, value; splitField( *it, app, name, value ); kdDebug( 14010 ) << "app=" << app << " name=" << name << " value=" << value << endl; if ( app.startsWith( TQString::fromLatin1( "messaging/" ) ) ) { if ( name == TQString::fromLatin1( "All" ) ) { kdDebug( 14010 ) << " syncing \"" << app << ":" << name << " with contactlist " << endl; // Get the protocol name from the custom field // by chopping the 'messaging/' prefix from the custom field app name TQString protocolName = app.right( app.length() - 10 ); // munge Jabber hack if ( protocolName == TQString::fromLatin1( "xmpp" ) ) protocolName = TQString::fromLatin1( "jabber" ); // Check Kopete supports it Protocol * proto = dynamic_cast( PluginManager::self()->loadPlugin( TQString::fromLatin1( "kopete_" ) + protocolName ) ); if ( !proto ) { KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, i18n( "\"%1\" is not supported by Kopete." ).arg( protocolName ), i18n( "Could Not Sync with TDE Address Book" ) ); continue; } // See if we need to add each contact in this protocol TQStringList addresses = TQStringList::split( TQChar( 0xE000 ), value ); TQStringList::iterator end = addresses.end(); for ( TQStringList::iterator it = addresses.begin(); it != end; ++it ) { // check whether each one is present in Kopete // Is it in the contact list? // First discard anything after an 0xE120, this is used by IRC to separate nick and server group name, but // IRC doesn't support this properly yet, so the user will have to select an appropriate account manually int separatorPos = (*it).find( TQChar( 0xE120 ) ); if ( separatorPos != -1 ) *it = (*it).left( separatorPos ); TQDict accounts = Kopete::AccountManager::self()->accounts( proto ); TQDictIterator acs(accounts); Kopete::MetaContact *otherMc = 0; for ( acs.toFirst(); acs.current(); ++acs ) { Kopete::Contact *c= acs.current()->contacts()[*it]; if(c) { otherMc=c->metaContact(); break; } } if ( otherMc ) // Is it in another metacontact? { // Is it already in this metacontact? If so, we needn't do anything if ( otherMc == mc ) { kdDebug( 14010 ) << *it << " already a child of this metacontact." << endl; continue; } kdDebug( 14010 ) << *it << " already exists in OTHER metacontact, move here?" << endl; // find the Kopete::Contact and attempt to move it to this metacontact. otherMc->findContact( proto->pluginId(), TQString(), *it )->setMetaContact( mc ); } else { // if not, prompt to add it kdDebug( 14010 ) << proto->pluginId() << "://" << *it << " was not found in the contact list. Prompting to add..." << endl; if ( KMessageBox::Yes == KMessageBox::questionYesNo( Kopete::UI::Global::mainWidget(), i18n( "An address was added to this contact by another application.
Would you like to use it in Kopete?
Protocol: %1
Address: %2
" ).arg( proto->displayName() ).arg( *it ), i18n( "Import Address From Address Book" ), i18n("Use"), i18n("Do Not Use"), TQString::fromLatin1( "ImportFromKABC" ) ) ) { // Check the accounts for this protocol are all connected // Most protocols do not allow you to add contacts while offline // Would be better to have a virtual bool Kopete::Account::readyToAddContact() bool allAccountsConnected = true; for ( acs.toFirst(); acs.current(); ++acs ) if ( !acs.current()->isConnected() ) { allAccountsConnected = false; break; } if ( !allAccountsConnected ) { KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, i18n( "One or more of your accounts using %1 are offline. Most systems have to be connected to add contacts. Please connect these accounts and try again." ).arg( protocolName ), i18n( "Not Connected" ) ); continue; } // we have got a contact to add, our accounts are connected, so add it. // Do we need to choose an account Kopete::Account *chosen = 0; if ( accounts.count() > 1 ) { // if we have >1 account in this protocol, prompt for the protocol. KDialogBase *chooser = new KDialogBase(0, "chooser", true, i18n("Choose Account"), KDialogBase::Ok|KDialogBase::Cancel, KDialogBase::Ok, false); AccountSelector *accSelector = new AccountSelector(proto, chooser, "accSelector"); chooser->setMainWidget(accSelector); if ( chooser->exec() == TQDialog::Rejected ) continue; chosen = accSelector->selectedItem(); delete chooser; } else if ( accounts.isEmpty() ) { KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, i18n( "You do not have an account configured for %1 yet. Please create an account, connect it, and try again." ).arg( protocolName ), i18n( "No Account Found" ) ); continue; } else // if we have 1 account in this protocol, choose it { chosen = acs.toFirst(); } // add the contact to the chosen account if ( chosen ) { kdDebug( 14010 ) << "Adding " << *it << " to " << chosen->accountId() << endl; if ( chosen->addContact( *it, mc ) ) contactAdded = true; else KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, i18n( "It was not possible to add the contact." ), i18n( "Could Not Add Contact") ) ; } } else kdDebug( 14010 ) << " user declined to add " << *it << " to contactlist " << endl; } } kdDebug( 14010 ) << " all " << addresses.count() << " contacts in " << proto->pluginId() << " checked " << endl; } else kdDebug( 14010 ) << "not interested in name=" << name << endl; } else kdDebug( 14010 ) << "not interested in app=" << app << endl; } } return contactAdded; return false; } // FIXME: Remove when IM address API is in KABC (KDE 4) void KABCPersistence::splitField( const TQString &str, TQString &app, TQString &name, TQString &value ) { int colon = str.find( ':' ); if ( colon != -1 ) { TQString tmp = str.left( colon ); value = str.mid( colon + 1 ); int dash = tmp.find( '-' ); if ( dash != -1 ) { app = tmp.left( dash ); name = tmp.mid( dash + 1 ); } } } void KABCPersistence::dumpAB() { KABC::AddressBook * ab = addressBook(); kdDebug( 14010 ) << k_funcinfo << " DUMPING ADDRESSBOOK" << endl; KABC::AddressBook::ConstIterator dumpit = ab->begin(); for ( ; dumpit != ab->end(); ++dumpit ) { (*dumpit).dump(); } } } // end namespace Kopete // dump addressbook contents #include "kabcpersistence.moc"