diff options
Diffstat (limited to 'conduits/abbrowserconduit/abbrowser-conduit.cc')
-rw-r--r-- | conduits/abbrowserconduit/abbrowser-conduit.cc | 1897 |
1 files changed, 1897 insertions, 0 deletions
diff --git a/conduits/abbrowserconduit/abbrowser-conduit.cc b/conduits/abbrowserconduit/abbrowser-conduit.cc new file mode 100644 index 0000000..e4f674a --- /dev/null +++ b/conduits/abbrowserconduit/abbrowser-conduit.cc @@ -0,0 +1,1897 @@ +/* KPilot +** +** Copyright (C) 2000,2001 by Dan Pilone +** Copyright (C) 2002-2003 by Reinhold Kainhofer +** Copyright (C) 2007 by Adriaan de Groot <[email protected]> +** +** The abbrowser conduit copies addresses from the Pilot's address book to +** the KDE addressbook maintained via the kabc library. +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to [email protected]. +*/ + + + +#include "options.h" + +#include <tqtimer.h> +#include <tqtextcodec.h> +#include <tqfile.h> +#include <tqregexp.h> + +#include <kabc/stdaddressbook.h> +#include <kabc/resourcefile.h> +#include <kio/netaccess.h> +#include <ksavefile.h> + +#include <pilotSerialDatabase.h> +#include <pilotLocalDatabase.h> + +#include "resolutionDialog.h" +#include "resolutionTable.h" +#include "abbrowserSettings.h" +#include "kabcRecord.h" + +#include "abbrowser-conduit.moc" + +// Something to allow us to check what revision +// the modules are that make up a binary distribution. +// +// +extern "C" +{ +unsigned long version_conduit_address = Pilot::PLUGIN_API; +} + + +/* This is partly stolen from the boost libraries, partly from +* "Modern C++ design" for doing compile time checks; we need +* to make sure that the enum values in KABCSync:: and in the +* AbbrowserSettings class are the same so that both interpret +* configuration values the same way. +*/ +template<bool> struct EnumerationMismatch; +template<> struct EnumerationMismatch<true>{}; + +#define CHECK_ENUM(a) (void)sizeof(EnumerationMismatch<((int)KABCSync::a)==((int)AbbrowserSettings::a)>) + +static inline void compile_time_check() +{ + // Mappings for other phone + CHECK_ENUM(eOtherPhone); + CHECK_ENUM(eOtherPhone); + CHECK_ENUM(eAssistant); + CHECK_ENUM(eBusinessFax); + CHECK_ENUM(eCarPhone); + CHECK_ENUM(eEmail2); + CHECK_ENUM(eHomeFax); + CHECK_ENUM(eTelex); + CHECK_ENUM(eTTYTTDPhone); + + // Mappings for custom fields + CHECK_ENUM(eCustomField); + CHECK_ENUM(eCustomBirthdate); + CHECK_ENUM(eCustomURL); + CHECK_ENUM(eCustomIM); +} + +inline int faxTypeOnPC() +{ + return KABC::PhoneNumber::Fax | + ( (AbbrowserSettings::pilotFax()==0) ? + KABC::PhoneNumber::Home : + KABC::PhoneNumber::Work ); +} + + +using namespace KABC; + +/********************************************************************* + C O N S T R U C T O R + *********************************************************************/ + + +AbbrowserConduit::AbbrowserConduit(KPilotLink * o, const char *n, const TQStringList & a): + ConduitAction(o, n, a), + aBook(0L), + fAddressAppInfo(0L), + addresseeMap(), + syncedIds(), + abiter(), + fTicket(0L), + fCreatedBook(false), + fBookResource(0L) +{ + FUNCTIONSETUP; + fConduitName=i18n("Addressbook"); +} + + + +AbbrowserConduit::~AbbrowserConduit() +{ + FUNCTIONSETUP; + + if (fTicket) + { + DEBUGKPILOT << fname << ": Releasing ticket" << endl; + aBook->releaseSaveTicket(fTicket); + fTicket=0L; + } + + _cleanupAddressBookPointer(); + // unused function warnings. + compile_time_check(); +} + + + +/********************************************************************* + L O A D I N G T H E D A T A + *********************************************************************/ + + + +/* Builds the map which links record ids to uid's of Addressee +*/ +void AbbrowserConduit::_mapContactsToPilot(TQMap < recordid_t, TQString > &idContactMap) +{ + FUNCTIONSETUP; + + idContactMap.clear(); + + for(AddressBook::Iterator contactIter = aBook->begin(); + contactIter != aBook->end(); ++contactIter) + { + Addressee aContact = *contactIter; + TQString recid = aContact.custom(KABCSync::appString, KABCSync::idString); + if(!recid.isEmpty()) + { + recordid_t id = recid.toULong(); + // safety check: make sure that we don't already have a map for this pilot id. + // if we do (this can come from a copy/paste in kaddressbook, etc.), then we need + // to reset our Addressee so that we can assign him a new pilot Id later and sync + // him properly. if we don't do this, we'll lose one of these on the pilot. + if (!idContactMap.tqcontains(id)) + { + idContactMap.insert(id, aContact.uid()); + } + else + { + DEBUGKPILOT << fname << ": found duplicate pilot key: [" + << id << "], removing pilot id from addressee: [" + << aContact.realName() << "]" << endl; + aContact.removeCustom(KABCSync::appString, KABCSync::idString); + aBook->insertAddressee(aContact); + abChanged = true; + } + } + } + DEBUGKPILOT << fname << ": Loaded " << idContactMap.size() << + " addresses from the addressbook. " << endl; +} + + + +bool AbbrowserConduit::_prepare() +{ + FUNCTIONSETUP; + + readConfig(); + syncedIds.clear(); + pilotindex = 0; + + return true; +} + + + +void AbbrowserConduit::readConfig() +{ + FUNCTIONSETUP; + AbbrowserSettings::self()->readConfig(); + + // Conflict page + SyncAction::ConflictResolution res = (SyncAction::ConflictResolution)AbbrowserSettings::conflictResolution(); + setConflictResolution(res); + + DEBUGKPILOT << fname + << ": Reading addressbook " + << ( AbbrowserSettings::addressbookType() == AbbrowserSettings::eAbookFile ? + AbbrowserSettings::fileName() : CSL1("Standard") ) + << endl; + DEBUGKPILOT << fname + << ": " + << " fConflictResolution=" << getConflictResolution() + << " fArchive=" << AbbrowserSettings::archiveDeleted() + << " fFirstTime=" << isFirstSync() + << endl; + DEBUGKPILOT << fname + << ": " + << " fPilotStreetHome=" << AbbrowserSettings::pilotStreet() + << " fPilotFaxHome=" << AbbrowserSettings::pilotFax() + << " eCustom[0]=" << AbbrowserSettings::custom0() + << " eCustom[1]=" << AbbrowserSettings::custom1() + << " eCustom[2]=" << AbbrowserSettings::custom2() + << " eCustom[3]=" << AbbrowserSettings::custom3() + << endl; +} + + + +bool isDeleted(const PilotAddress *addr) +{ + if (!addr) + { + return true; + } + if (addr->isDeleted() && !addr->isArchived()) + { + return true; + } + if (addr->isArchived()) + { + return !AbbrowserSettings::archiveDeleted(); + } + return false; +} + +bool isArchived(const PilotAddress *addr) +{ + if (addr && addr->isArchived()) + { + return AbbrowserSettings::archiveDeleted(); + } + else + { + return false; + } +} + + + +bool AbbrowserConduit::_loadAddressBook() +{ + FUNCTIONSETUP; + + startTickle(); + switch ( AbbrowserSettings::addressbookType() ) + { + case AbbrowserSettings::eAbookResource: + DEBUGKPILOT<<"Loading standard addressbook"<<endl; + aBook = StdAddressBook::self( true ); + fCreatedBook=false; + break; + case AbbrowserSettings::eAbookFile: + { // initialize the abook with the given file + DEBUGKPILOT<<"Loading custom addressbook"<<endl; + KURL kurl(AbbrowserSettings::fileName()); + if(!KIO::NetAccess::download(AbbrowserSettings::fileName(), fABookFile, 0L) && + !kurl.isLocalFile()) + { + emit logError(i18n("You chose to sync with the file \"%1\", which " + "cannot be opened. Please make sure to supply a " + "valid file name in the conduit's configuration dialog. " + "Aborting the conduit.").arg(AbbrowserSettings::fileName())); + KIO::NetAccess::removeTempFile(fABookFile); + stopTickle(); + return false; + } + + aBook = new AddressBook(); + if (!aBook) + { + stopTickle(); + return false; + } + fBookResource = new ResourceFile(fABookFile, CSL1("vcard") ); + + bool r = aBook->addResource( fBookResource ); + if ( !r ) + { + DEBUGKPILOT << "Unable to open resource for file " << fABookFile << endl; + KPILOT_DELETE( aBook ); + stopTickle(); + return false; + } + fCreatedBook=true; + break; + } + default: break; + } + // find out if this can fail for reasons other than a non-existent + // vcf file. If so, how can I determine if the missing file was the problem + // or something more serious: + if ( !aBook || !aBook->load() ) + { + // Something went wrong, so tell the user and return false to exit the conduit + emit logError(i18n("Unable to initialize and load the addressbook for the sync.") ); + addSyncLogEntry(i18n("Unable to initialize and load the addressbook for the sync.") ); + WARNINGKPILOT << "Unable to initialize the addressbook for the sync." << endl; + _cleanupAddressBookPointer(); + stopTickle(); + return false; + } + abChanged = false; + + fTicket=aBook->requestSaveTicket(); + if (!fTicket) + { + WARNINGKPILOT << "Unable to lock addressbook for writing " << endl; + emit logError(i18n("Unable to lock addressbook for writing. Can't sync!")); + addSyncLogEntry(i18n("Unable to lock addressbook for writing. Can't sync!")); + _cleanupAddressBookPointer(); + stopTickle(); + return false; + } + + fCtrPC->setStartCount(aBook->allAddressees().count()); + + // get the addresseMap which maps Pilot unique record(address) id's to + // a Abbrowser Addressee; allows for easy lookup and comparisons + if(aBook->begin() == aBook->end()) + { + setFirstSync( true ); + } + else + { + _mapContactsToPilot(addresseeMap); + } + stopTickle(); + return(aBook != 0L); +} + +bool AbbrowserConduit::_saveAddressBook() +{ + FUNCTIONSETUP; + + bool saveSuccessful = false; + + fCtrPC->setEndCount(aBook->allAddressees().count()); + + Q_ASSERT(fTicket); + + if (abChanged) + { + saveSuccessful = aBook->save(fTicket); + } + else + { + DEBUGKPILOT << fname + << "Addressbook not changed, no need to save it" << endl; + } + // XXX: KDE4: release ticket in all cases (save no longer releases it) + if ( !saveSuccessful ) // didn't save, delete ticket manually + { + aBook->releaseSaveTicket(fTicket); + } + fTicket=0L; + + if ( AbbrowserSettings::addressbookType()!= AbbrowserSettings::eAbookResource ) + { + KURL kurl(AbbrowserSettings::fileName()); + if(!kurl.isLocalFile()) + { + DEBUGKPILOT << fname << "Deleting local addressbook tempfile" << endl; + if(!KIO::NetAccess::upload(fABookFile, AbbrowserSettings::fileName(), 0L)) { + emit logError(i18n("An error occurred while uploading \"%1\". You can try to upload " + "the temporary local file \"%2\" manually") + .arg(AbbrowserSettings::fileName()).arg(fABookFile)); + } + else { + KIO::NetAccess::removeTempFile(fABookFile); + } + TQFile backup(fABookFile + CSL1("~")); + backup.remove(); + } + + } + + // now try to remove the resource from the addressbook... + if (fBookResource) + { + bool r = aBook->removeResource( fBookResource ); + if ( !r ) + { + DEBUGKPILOT << fname <<": Unable to close resource." << endl; + } + } + + return saveSuccessful; +} + + + +void AbbrowserConduit::_getAppInfo() +{ + FUNCTIONSETUP; + + delete fAddressAppInfo; + fAddressAppInfo = new PilotAddressInfo(fDatabase); + fAddressAppInfo->dump(); +} + +void AbbrowserConduit::_setAppInfo() +{ + FUNCTIONSETUP; + if (fDatabase) fAddressAppInfo->writeTo(fDatabase); + if (fLocalDatabase) fAddressAppInfo->writeTo(fLocalDatabase); +} + + +void AbbrowserConduit::_cleanupAddressBookPointer() +{ + if (fCreatedBook) + { + KPILOT_DELETE(aBook); + fCreatedBook=false; + } + else + { + aBook=0L; + } +} + + + + +/********************************************************************* + D E B U G O U T P U T + *********************************************************************/ + + + + + +void AbbrowserConduit::showPilotAddress(const PilotAddress *pilotAddress) +{ + FUNCTIONSETUPL(3); + if (debug_level < 3) + { + return; + } + if (!pilotAddress) + { + DEBUGKPILOT<< fname << "| EMPTY"<<endl; + return; + } + DEBUGKPILOT << fname << "\n" + << pilotAddress->getTextRepresentation( + fAddressAppInfo,Qt::PlainText) << endl; +} + + +void AbbrowserConduit::showAddresses( + const Addressee &pcAddr, + const PilotAddress *backupAddr, + const PilotAddress *palmAddr) +{ + FUNCTIONSETUPL(3); + if (debug_level >= 3) + { + DEBUGKPILOT << fname << "abEntry:" << endl; + KABCSync::showAddressee(pcAddr); + DEBUGKPILOT << fname << "pilotAddress:" << endl; + showPilotAddress(palmAddr); + DEBUGKPILOT << fname << "backupAddress:" << endl; + showPilotAddress(backupAddr); + DEBUGKPILOT << fname << "------------------------------------------------" << endl; + } +} + + + +/********************************************************************* + S Y N C S T R U C T U R E + *********************************************************************/ + + + +/* virtual */ bool AbbrowserConduit::exec() +{ + FUNCTIONSETUP; + + _prepare(); + + bool retrieved = false; + if(!openDatabases(CSL1("AddressDB"), &retrieved)) + { + emit logError(i18n("Unable to open the addressbook databases on the handheld.")); + return false; + } + setFirstSync( retrieved ); + + _getAppInfo(); + + // Local block + { + TQString dbpath = fLocalDatabase->dbPathName(); + DEBUGKPILOT << fname << ": Local database path " << dbpath << endl; + } + + if ( syncMode().isTest() ) + { + TQTimer::singleShot(0, this, TQT_SLOT(slotTestRecord())); + return true; + } + + if(!_loadAddressBook()) + { + emit logError(i18n("Unable to open the addressbook.")); + return false; + } + setFirstSync( isFirstSync() || (aBook->begin() == aBook->end()) ); + + DEBUGKPILOT << fname << ": First sync now " << isFirstSync() + << " and addressbook is " + << ((aBook->begin() == aBook->end()) ? "" : "non-") + << "empty." << endl; + + // perform syncing from palm to abbrowser + // iterate through all records in palm pilot + + DEBUGKPILOT << fname << ": fullsync=" << isFullSync() << ", firstSync=" << isFirstSync() << endl; + DEBUGKPILOT << fname << ": " + << "syncDirection=" << syncMode().name() << ", " + << "archive = " << AbbrowserSettings::archiveDeleted() << endl; + DEBUGKPILOT << fname << ": conflictRes="<< getConflictResolution() << endl; + DEBUGKPILOT << fname << ": PilotStreetHome=" << AbbrowserSettings::pilotStreet() << ", PilotFaxHOme" << AbbrowserSettings::pilotFax() << endl; + + if (!isFirstSync()) + { + allIds=fDatabase->idList(); + } + + TQValueVector<int> v(4); + v[0] = AbbrowserSettings::custom0(); + v[1] = AbbrowserSettings::custom1(); + v[2] = AbbrowserSettings::custom2(); + v[3] = AbbrowserSettings::custom3(); + + fSyncSettings.setCustomMapping(v); + fSyncSettings.setFieldForOtherPhone(AbbrowserSettings::pilotOther()); + fSyncSettings.setDateFormat(AbbrowserSettings::customDateFormat()); + fSyncSettings.setPreferHome(AbbrowserSettings::pilotStreet()==0); + fSyncSettings.setFaxTypeOnPC(faxTypeOnPC()); + + /* Note: + if eCopyPCToHH or eCopyHHToPC, first sync everything, then lookup + those entries on the receiving side that are not yet syncced and delete + them. Use slotDeleteUnsyncedPCRecords and slotDeleteUnsyncedHHRecords + for this, and no longer purge the whole addressbook before the sync to + prevent data loss in case of connection loss. */ + + TQTimer::singleShot(0, this, TQT_SLOT(slotPalmRecToPC())); + + return true; +} + + + +void AbbrowserConduit::slotPalmRecToPC() +{ + FUNCTIONSETUP; + PilotRecord *palmRec = 0L, *backupRec = 0L; + + if ( syncMode() == SyncMode::eCopyPCToHH ) + { + DEBUGKPILOT << fname << ": Done; change to PCtoHH phase." << endl; + abiter = aBook->begin(); + TQTimer::singleShot(0, this, TQT_SLOT(slotPCRecToPalm())); + return; + } + + if(isFullSync()) + { + palmRec = fDatabase->readRecordByIndex(pilotindex++); + } + else + { + palmRec = fDatabase->readNextModifiedRec(); + } + + // no record means we're done going in this direction, so switch to + // PC->Palm + if(!palmRec) + { + abiter = aBook->begin(); + TQTimer::singleShot(0, this, TQT_SLOT(slotPCRecToPalm())); + return; + } + + // already synced, so skip: + if(syncedIds.tqcontains(palmRec->id())) + { + KPILOT_DELETE(palmRec); + TQTimer::singleShot(0, this, TQT_SLOT(slotPalmRecToPC())); + return; + } + + backupRec = fLocalDatabase->readRecordById(palmRec->id()); + PilotRecord*compareRec=(backupRec)?(backupRec):(palmRec); + Addressee e = _findMatch(PilotAddress(compareRec)); + + PilotAddress*backupAddr=0L; + if (backupRec) + { + backupAddr=new PilotAddress(backupRec); + } + + PilotAddress*palmAddr=0L; + if (palmRec) + { + palmAddr=new PilotAddress(palmRec); + } + + syncAddressee(e, backupAddr, palmAddr); + + syncedIds.append(palmRec->id()); + KPILOT_DELETE(palmAddr); + KPILOT_DELETE(backupAddr); + KPILOT_DELETE(palmRec); + KPILOT_DELETE(backupRec); + + TQTimer::singleShot(0, this, TQT_SLOT(slotPalmRecToPC())); +} + + + +void AbbrowserConduit::slotPCRecToPalm() +{ + FUNCTIONSETUP; + + if ( (syncMode()==SyncMode::eCopyHHToPC) || + abiter == aBook->end() || (*abiter).isEmpty() ) + { + DEBUGKPILOT << fname << ": Done; change to delete records." << endl; + pilotindex = 0; + TQTimer::singleShot(0, this, TQT_SLOT(slotDeletedRecord())); + return; + } + + PilotRecord *palmRec=0L, *backupRec=0L; + Addressee ad = *abiter; + + abiter++; + + // If marked as archived, don't sync! + if (KABCSync::isArchived(ad)) + { + DEBUGKPILOT << fname << ": address with id " << ad.uid() << + " marked archived, so don't sync." << endl; + TQTimer::singleShot(0, this, TQT_SLOT(slotPCRecToPalm())); + return; + } + + + TQString recID(ad.custom(KABCSync::appString, KABCSync::idString)); + bool ok; + recordid_t rid = recID.toLong(&ok); + if (recID.isEmpty() || !ok || !rid) + { + DEBUGKPILOT << fname << ": This is a new record." << endl; + // it's a new item(no record ID and not inserted by the Palm -> PC sync), so add it + syncAddressee(ad, 0L, 0L); + TQTimer::singleShot(0, this, TQT_SLOT(slotPCRecToPalm())); + return; + } + + // look into the list of already synced record ids to see if the addressee hasn't already been synced + if (syncedIds.tqcontains(rid)) + { + DEBUGKPILOT << ": address with id " << rid << " already synced." << endl; + TQTimer::singleShot(0, this, TQT_SLOT(slotPCRecToPalm())); + return; + } + + + backupRec = fLocalDatabase->readRecordById(rid); + // only update if no backup record or the backup record is not equal to the addressee + + PilotAddress*backupAddr=0L; + if (backupRec) + { + backupAddr=new PilotAddress(backupRec); + } + if(!backupRec || isFirstSync() || !_equal(backupAddr, ad) ) + { + DEBUGKPILOT << fname << ": Updating entry." << endl; + palmRec = fDatabase->readRecordById(rid); + PilotAddress *palmAddr = 0L; + if (palmRec) + { + palmAddr = new PilotAddress(palmRec); + } + else + { + DEBUGKPILOT << fname << ": No HH record with id " << rid << endl; + } + syncAddressee(ad, backupAddr, palmAddr); + // update the id just in case it changed + if (palmRec) rid=palmRec->id(); + KPILOT_DELETE(palmRec); + KPILOT_DELETE(palmAddr); + } + else + { + DEBUGKPILOT << fname << ": Entry not updated." << endl; + } + KPILOT_DELETE(backupAddr); + KPILOT_DELETE(backupRec); + + DEBUGKPILOT << fname << ": adding id:["<< rid << "] to syncedIds." << endl; + + syncedIds.append(rid); + // done with the sync process, go on with the next one: + TQTimer::singleShot(0, this, TQT_SLOT(slotPCRecToPalm())); +} + + + +void AbbrowserConduit::slotDeletedRecord() +{ + FUNCTIONSETUP; + + PilotRecord *backupRec = fLocalDatabase->readRecordByIndex(pilotindex++); + if(!backupRec || isFirstSync() ) + { + KPILOT_DELETE(backupRec); + TQTimer::singleShot(0, this, TQT_SLOT(slotDeleteUnsyncedPCRecords())); + return; + } + + recordid_t id = backupRec->id(); + + TQString uid = addresseeMap[id]; + Addressee e = aBook->tqfindByUid(uid); + + DEBUGKPILOT << fname << ": now looking at palm id: [" + << id << "], kabc uid: [" << uid << "]." << endl; + + PilotAddress*backupAddr=0L; + if (backupRec) + { + backupAddr=new PilotAddress(backupRec); + } + PilotRecord*palmRec=fDatabase->readRecordById(id); + + if ( e.isEmpty() ) + { + DEBUGKPILOT << fname << ": no Addressee found for this id." << endl; + DEBUGKPILOT << fname << "\n" + << backupAddr->getTextRepresentation( + fAddressAppInfo,Qt::PlainText) << endl; + + if (palmRec) { + DEBUGKPILOT << fname << ": deleting from database on palm." << endl; + fDatabase->deleteRecord(id); + fCtrHH->deleted(); + } + DEBUGKPILOT << fname << ": deleting from backup database." << endl; + fLocalDatabase->deleteRecord(id); + + // because we just deleted a record, we need to go back one + pilotindex--; + } + + KPILOT_DELETE(palmRec); + KPILOT_DELETE(backupAddr); + KPILOT_DELETE(backupRec); + TQTimer::singleShot(0, this, TQT_SLOT(slotDeletedRecord())); +} + + + +void AbbrowserConduit::slotDeleteUnsyncedPCRecords() +{ + FUNCTIONSETUP; + if ( syncMode()==SyncMode::eCopyHHToPC ) + { + TQStringList uids; + RecordIDList::iterator it; + TQString uid; + for ( it = syncedIds.begin(); it != syncedIds.end(); ++it) + { + uid=addresseeMap[*it]; + if (!uid.isEmpty()) uids.append(uid); + } + // TODO: Does this speed up anything? + // qHeapSort( uids ); + AddressBook::Iterator abit; + for (abit = aBook->begin(); abit != aBook->end(); ++abit) + { + if (!uids.tqcontains((*abit).uid())) + { + DEBUGKPILOT<<"Deleting addressee "<<(*abit).realName()<<" from PC (is not on HH, and syncing with HH->PC direction)"<<endl; + abChanged = true; + // TODO: Can I really remove the current iterator??? + aBook->removeAddressee(*abit); + fCtrPC->deleted(); + } + } + } + TQTimer::singleShot(0, this, TQT_SLOT(slotDeleteUnsyncedHHRecords())); +} + + + +void AbbrowserConduit::slotDeleteUnsyncedHHRecords() +{ + FUNCTIONSETUP; + if ( syncMode()==SyncMode::eCopyPCToHH ) + { + RecordIDList ids=fDatabase->idList(); + RecordIDList::iterator it; + for ( it = ids.begin(); it != ids.end(); ++it ) + { + if (!syncedIds.tqcontains(*it)) + { + DEBUGKPILOT<<"Deleting record with ID "<<*it<<" from handheld (is not on PC, and syncing with PC->HH direction)"<<endl; + fDatabase->deleteRecord(*it); + fCtrHH->deleted(); + fLocalDatabase->deleteRecord(*it); + } + } + } + TQTimer::singleShot(0, this, TQT_SLOT(slotCleanup())); +} + + +void AbbrowserConduit::slotCleanup() +{ + FUNCTIONSETUP; + + // Set the appInfoBlock, just in case the category labels changed + _setAppInfo(); + if(fDatabase) + { + fDatabase->resetSyncFlags(); + fDatabase->cleanup(); + } + if(fLocalDatabase) + { + fLocalDatabase->resetSyncFlags(); + fLocalDatabase->cleanup(); + } + + // Write out the sync maps + TQString syncFile = fLocalDatabase->dbPathName() + CSL1(".sync"); + DEBUGKPILOT << fname << ": Writing sync map to " << syncFile << endl; + KSaveFile map( syncFile ); + if ( map.status() == 0 ) + { + DEBUGKPILOT << fname << ": Writing sync map ..." << endl; + (*map.dataStream()) << addresseeMap ; + map.close(); + } + // This also picks up errors from map.close() + if ( map.status() != 0 ) + { + WARNINGKPILOT << "Could not make backup of sync map." << endl; + } + + _saveAddressBook(); + delayDone(); +} + + + +/********************************************************************* + G E N E R A L S Y N C F U N C T I O N + These functions modify the Handheld and the addressbook + *********************************************************************/ + + + +bool AbbrowserConduit::syncAddressee(Addressee &pcAddr, PilotAddress*backupAddr, + PilotAddress*palmAddr) +{ + FUNCTIONSETUP; + showAddresses(pcAddr, backupAddr, palmAddr); + + if ( syncMode() == SyncMode::eCopyPCToHH ) + { + if (pcAddr.isEmpty()) + { + return _deleteAddressee(pcAddr, backupAddr, palmAddr); + } + else + { + return _copyToHH(pcAddr, backupAddr, palmAddr); + } + } + + if ( syncMode() == SyncMode::eCopyHHToPC ) + { + if (!palmAddr) + { + return _deleteAddressee(pcAddr, backupAddr, palmAddr); + } + else + { + return _copyToPC(pcAddr, backupAddr, palmAddr); + } + } + + if ( !backupAddr || isFirstSync() ) + { + DEBUGKPILOT<< fname << ": Special case: no backup." << endl; + /* + Resolution matrix (0..does not exist, E..exists, D..deleted flag set, A..archived): + HH PC | Resolution + ------------------------------------------------------------ + 0 A | - + 0 E | PC -> HH, reset ID if not set correctly + D 0 | delete (error, should never occur!!!) + D E | CR (ERROR) + E/A 0 | HH -> PC + E/A E/A| merge/CR + */ + if (!palmAddr && KABCSync::isArchived(pcAddr) ) + { + return true; + } + else if (!palmAddr && !pcAddr.isEmpty()) + { + DEBUGKPILOT << fname << ": case: 1a"<<endl; + // PC->HH + bool res=_copyToHH(pcAddr, 0L, 0L); + return res; + } + else if (!palmAddr && pcAddr.isEmpty()) + { + DEBUGKPILOT << fname << ": case: 1b"<<endl; + // everything's empty -> ERROR + return false; + } + else if ( (isDeleted(palmAddr) || isArchived(palmAddr)) && pcAddr.isEmpty()) + { + DEBUGKPILOT << fname << ": case: 1c"<<endl; + if (isArchived(palmAddr)) + return _copyToPC(pcAddr, 0L, palmAddr); + else + // this happens if you add a record on the handheld and delete it again before you do the next sync + return _deleteAddressee(pcAddr, 0L, palmAddr); + } + else if ((isDeleted(palmAddr)||isArchived(palmAddr)) && !pcAddr.isEmpty()) + { + DEBUGKPILOT << fname << ": case: 1d"<<endl; + // CR (ERROR) + return _smartMergeAddressee(pcAddr, 0L, palmAddr); + } + else if (pcAddr.isEmpty()) + { + DEBUGKPILOT << fname << ": case: 1e"<<endl; + // HH->PC + return _copyToPC(pcAddr, 0L, palmAddr); + } + else + { + DEBUGKPILOT << fname << ": case: 1f"<<endl; + // Conflict Resolution + return _smartMergeAddressee(pcAddr, 0L, palmAddr); + } + } // !backupAddr + else + { + DEBUGKPILOT << fname << ": case: 2"<<endl; + /* + Resolution matrix: + 1) if HH.(empty| (deleted &! archived) ) -> { if (PC==B) -> delete, else -> CR } + if HH.archied -> {if (PC==B) -> copyToPC, else -> CR } + if PC.empty -> { if (HH==B) -> delete, else -> CR } + if PC.archived -> {if (HH==B) -> delete on HH, else CR } + 2) if PC==HH -> { update B, update ID of PC if needed } + 3) if PC==B -> { HH!=PC, thus HH modified, so copy HH->PC } + if HH==B -> { PC!=HH, thus PC modified, so copy PC->HH } + 4) else: all three addressees are different -> CR + */ + + if (!palmAddr || isDeleted(palmAddr) ) + { + DEBUGKPILOT << fname << ": case: 2a"<<endl; + if (_equal(backupAddr, pcAddr) || pcAddr.isEmpty()) + { + return _deleteAddressee(pcAddr, backupAddr, 0L); + } + else + { + return _smartMergeAddressee(pcAddr, backupAddr, 0L); + } + } + else if (pcAddr.isEmpty()) + { + DEBUGKPILOT << fname << ": case: 2b"<<endl; + if (*palmAddr == *backupAddr) + { + return _deleteAddressee(pcAddr, backupAddr, palmAddr); + } + else + { + return _smartMergeAddressee(pcAddr, backupAddr, palmAddr); + } + } + else if (_equal(palmAddr, pcAddr)) + { + DEBUGKPILOT << fname << ": case: 2c"<<endl; + // update Backup, update ID of PC if neededd + return _writeBackup(palmAddr); + } + else if (_equal(backupAddr, pcAddr)) + { + DEBUGKPILOT << fname << ": case: 2d"<<endl; + DEBUGKPILOT << fname << ": Flags: "<<palmAddr->attributes()<<", isDeleted="<< + isDeleted(palmAddr)<<", isArchived="<<isArchived(palmAddr)<<endl; + if (isDeleted(palmAddr)) + return _deleteAddressee(pcAddr, backupAddr, palmAddr); + else + return _copyToPC(pcAddr, backupAddr, palmAddr); + } + else if (*palmAddr == *backupAddr) + { + DEBUGKPILOT << fname << ": case: 2e"<<endl; + return _copyToHH(pcAddr, backupAddr, palmAddr); + } + else + { + DEBUGKPILOT << fname << ": case: 2f"<<endl; + // CR, since all are different + return _smartMergeAddressee(pcAddr, backupAddr, palmAddr); + } + } // backupAddr + return false; +} + + + +bool AbbrowserConduit::_copyToHH(Addressee &pcAddr, PilotAddress*backupAddr, + PilotAddress*palmAddr) +{ + FUNCTIONSETUP; + + if (pcAddr.isEmpty()) return false; + PilotAddress*paddr=palmAddr; + bool paddrcreated=false; + if (!paddr) + { + paddr=new PilotAddress(); + paddrcreated=true; + fCtrHH->created(); + } + else + { + fCtrHH->updated(); + } + KABCSync::copy(*paddr, pcAddr, *fAddressAppInfo, fSyncSettings); + + DEBUGKPILOT << fname << "palmAddr->id=" << paddr->id() + << ", pcAddr.ID=" << pcAddr.custom(KABCSync::appString, KABCSync::idString) << endl; + + if(_savePalmAddr(paddr, pcAddr)) + { + _savePCAddr(pcAddr, backupAddr, paddr); + } + if (paddrcreated) KPILOT_DELETE(paddr); + return true; +} + + + +bool AbbrowserConduit::_copyToPC(Addressee &pcAddr, PilotAddress*backupAddr, + PilotAddress*palmAddr) +{ + FUNCTIONSETUP; + if (!palmAddr) + { + return false; + } + // keep track of CUD's... + if (pcAddr.isEmpty()) + { + fCtrPC->created(); + } + else + { + fCtrPC->updated(); + } + showPilotAddress(palmAddr); + + KABCSync::copy(pcAddr, *palmAddr, *fAddressAppInfo, fSyncSettings); + if (isArchived(palmAddr)) + { + KABCSync::makeArchived(pcAddr); + } + + _savePCAddr(pcAddr, backupAddr, palmAddr); + _writeBackup(palmAddr); + return true; +} + + + +bool AbbrowserConduit::_writeBackup(PilotAddress *backup) +{ + FUNCTIONSETUP; + if (!backup) return false; + + showPilotAddress(backup); + + PilotRecord *pilotRec = backup->pack(); + fLocalDatabase->writeRecord(pilotRec); + KPILOT_DELETE(pilotRec); + return true; +} + + + +bool AbbrowserConduit::_deleteAddressee(Addressee &pcAddr, PilotAddress*backupAddr, + PilotAddress*palmAddr) +{ + FUNCTIONSETUP; + + if (palmAddr) + { + if (!syncedIds.tqcontains(palmAddr->id())) { + DEBUGKPILOT << fname << ": adding id:["<< palmAddr->id() << "] to syncedIds." << endl; + syncedIds.append(palmAddr->id()); + } + fDatabase->deleteRecord(palmAddr->id()); + fCtrHH->deleted(); + fLocalDatabase->deleteRecord(palmAddr->id()); + } + else if (backupAddr) + { + if (!syncedIds.tqcontains(backupAddr->id())) { + DEBUGKPILOT << fname << ": adding id:["<< backupAddr->id() << "] to syncedIds." << endl; + syncedIds.append(backupAddr->id()); + } + fLocalDatabase->deleteRecord(backupAddr->id()); + } + if (!pcAddr.isEmpty()) + { + DEBUGKPILOT << fname << " removing " << pcAddr.formattedName() << endl; + abChanged = true; + aBook->removeAddressee(pcAddr); + fCtrPC->deleted(); + } + return true; +} + + + +/********************************************************************* + l o w - l e v e l f u n c t i o n s f o r + adding / removing palm/pc records + *********************************************************************/ + + + +bool AbbrowserConduit::_savePalmAddr(PilotAddress *palmAddr, Addressee &pcAddr) +{ + FUNCTIONSETUP; + + DEBUGKPILOT << fname << ": Saving to pilot " << palmAddr->id() + << " " << palmAddr->getField(entryFirstname) + << " " << palmAddr->getField(entryLastname)<< endl; + + PilotRecord *pilotRec = palmAddr->pack(); + DEBUGKPILOT << fname << ": record with id=" << pilotRec->id() + << " len=" << pilotRec->size() << endl; + recordid_t pilotId = fDatabase->writeRecord(pilotRec); + DEBUGKPILOT << fname << ": Wrote "<<pilotId<<": ID="<<pilotRec->id()<<endl; + fLocalDatabase->writeRecord(pilotRec); + KPILOT_DELETE(pilotRec); + + // pilotId == 0 if using local db, so don't overwrite the valid id + if(pilotId != 0) + { + palmAddr->setID(pilotId); + if (!syncedIds.tqcontains(pilotId)) { + DEBUGKPILOT << fname << ": adding id:["<< pilotId << "] to syncedIds." << endl; + syncedIds.append(pilotId); + } + } + + recordid_t abId = 0; + abId = pcAddr.custom(KABCSync::appString, KABCSync::idString).toUInt(); + if(abId != pilotId) + { + pcAddr.insertCustom(KABCSync::appString, KABCSync::idString, TQString::number(pilotId)); + return true; + } + + return false; +} + + + +bool AbbrowserConduit::_savePCAddr(Addressee &pcAddr, PilotAddress*, + PilotAddress*) +{ + FUNCTIONSETUP; + + DEBUGKPILOT<<"Before _savePCAddr, pcAddr.custom="<<pcAddr.custom(KABCSync::appString, KABCSync::idString)<<endl; + TQString pilotId = pcAddr.custom(KABCSync::appString, KABCSync::idString); + long pilotIdL = pilotId.toLong(); + if(!pilotId.isEmpty()) + { + // because we maintain a mapping between pilotId -> kabc uid, whenever we add + // a new relationship, we have to remove any old mapping that would tie a different + // pilot id -> this kabc uid + TQMap < recordid_t, TQString>::iterator it; + for ( it = addresseeMap.begin(); it != addresseeMap.end(); ++it ) { + TQString kabcUid = it.data(); + if (kabcUid == pcAddr.uid()) { + addresseeMap.remove(it); + break; + } + } + + // now put the new mapping in + addresseeMap.insert(pilotIdL, pcAddr.uid()); + } + + aBook->insertAddressee(pcAddr); + + abChanged = true; + return true; +} + + + + +/********************************************************************* + C O P Y R E C O R D S + *********************************************************************/ + + + +bool AbbrowserConduit::_equal(const PilotAddress *piAddress, const Addressee &abEntry, + enum eqFlagsType flags) const +{ + FUNCTIONSETUP; + + // empty records are never equal! + if (!piAddress) { + DEBUGKPILOT << fname << ": no pilot address passed" << endl; + return false; + } + if (abEntry.isEmpty()) { + DEBUGKPILOT << fname << ":abEntry.isEmpty()" << endl; + return false; + } + // Archived records match anything so they won't be copied to the HH again + if (flags & eqFlagsFlags) + if (isArchived(piAddress) && KABCSync::isArchived(abEntry) ) return true; + + if (flags & eqFlagsName) + { + if(!_equal(abEntry.familyName(), piAddress->getField(entryLastname))) + { + DEBUGKPILOT << fname << ": last name not equal" << endl; + return false; + } + if(!_equal(abEntry.givenName(), piAddress->getField(entryFirstname))) + { + DEBUGKPILOT << fname << ": first name not equal" << endl; + return false; + } + if(!_equal(abEntry.prefix(), piAddress->getField(entryTitle))) + { + DEBUGKPILOT << fname << ": title/prefix not equal" << endl; + return false; + } + if(!_equal(abEntry.organization(), piAddress->getField(entryCompany))) + { + DEBUGKPILOT << fname << ": company/organization not equal" << endl; + return false; + } + } + if (flags & eqFlagsNote) + if(!_equal(abEntry.note(), piAddress->getField(entryNote))) + { + DEBUGKPILOT << fname << ": note not equal" << endl; + return false; + } + + if (flags & eqFlagsCategory) + { + // Check that the name of the category of the HH record + // is one matching the PC record. + TQString addressCategoryLabel = fAddressAppInfo->categoryName(piAddress->category()); + TQString cat = KABCSync::bestMatchedCategoryName(abEntry.categories(), + *fAddressAppInfo, piAddress->category()); + if(!_equal(cat, addressCategoryLabel)) + { + DEBUGKPILOT << fname << ": category not equal" << endl; + return false; + } + } + + if (flags & eqFlagsPhones) + { + // first, look for missing e-mail addresses on either side + TQStringList abEmails(abEntry.emails()); + TQStringList piEmails(piAddress->getEmails()); + + if (abEmails.count() != piEmails.count()) + { + DEBUGKPILOT << fname << ": email count not equal" << endl; + return false; + } + for (TQStringList::Iterator it = abEmails.begin(); it != abEmails.end(); it++) { + if (!piEmails.tqcontains(*it)) + { + DEBUGKPILOT << fname << ": pilot e-mail missing" << endl; + return false; + } + } + for (TQStringList::Iterator it = piEmails.begin(); it != piEmails.end(); it++) { + if (!abEmails.tqcontains(*it)) + { + DEBUGKPILOT << fname << ": kabc e-mail missing" << endl; + return false; + } + } + + // now look for differences in phone numbers. Note: we can't just compare one + // of each kind of phone number, because there's no guarantee that if the user + // has more than one of a given type, we're comparing the correct two. + + PhoneNumber::List abPhones(abEntry.phoneNumbers()); + PhoneNumber::List piPhones = KABCSync::getPhoneNumbers(*piAddress); + // first make sure that all of the pilot phone numbers are in kabc + for (PhoneNumber::List::Iterator it = piPhones.begin(); it != piPhones.end(); it++) { + PhoneNumber piPhone = *it; + bool found=false; + for (PhoneNumber::List::Iterator it = abPhones.begin(); it != abPhones.end(); it++) { + PhoneNumber abPhone = *it; + // see if we have the same number here... + // * Note * We used to check for preferred number matching, but + // this seems to have broke in kdepim 3.5 and I don't have time to + // figure out why, so we won't check to see if preferred number match + if ( _equal(piPhone.number(), abPhone.number()) ) { + found = true; + break; + } + } + if (!found) { + DEBUGKPILOT << fname << ": not equal because kabc phone not found." << endl; + return false; + } + } + // now the other way. *cringe* kabc has the capacity to store way more addresses + // than the Pilot, so this might give false positives more than we'd want.... + for (PhoneNumber::List::Iterator it = abPhones.begin(); it != abPhones.end(); it++) { + PhoneNumber abPhone = *it; + bool found=false; + for (PhoneNumber::List::Iterator it = piPhones.begin(); it != piPhones.end(); it++) { + PhoneNumber piPhone = *it; + if ( _equal(piPhone.number(), abPhone.number()) ) { + found = true; + break; + } + } + if (!found) + { + DEBUGKPILOT << fname << ": not equal because pilot phone not found." << endl; + return false; + } + } + + if(!_equal(KABCSync::getFieldForHHOtherPhone(abEntry,fSyncSettings), + piAddress->getPhoneField(PilotAddressInfo::eOther))) + { + DEBUGKPILOT << fname << ": not equal because of other phone field." << endl; + return false; + } + } + + if (flags & eqFlagsAdress) + { + KABC::Address address = KABCSync::getAddress(abEntry,fSyncSettings); + if(!_equal(address.street(), piAddress->getField(entryAddress))) + { + DEBUGKPILOT << fname << ": address not equal" << endl; + return false; + } + if(!_equal(address.locality(), piAddress->getField(entryCity))) + { + DEBUGKPILOT << fname << ": city not equal" << endl; + return false; + } + if(!_equal(address.region(), piAddress->getField(entryState))) + { + DEBUGKPILOT << fname << ": state not equal" << endl; + return false; + } + if(!_equal(address.postalCode(), piAddress->getField(entryZip))) + { + DEBUGKPILOT << fname << ": zip not equal" << endl; + return false; + } + if(!_equal(address.country(), piAddress->getField(entryCountry))) + { + DEBUGKPILOT << fname << ": country not equal" << endl; + return false; + } + } + + if (flags & eqFlagsCustom) + { + unsigned int customIndex = 0; + unsigned int hhField = entryCustom1; + + for ( ; customIndex<4; ++customIndex,++hhField ) + { + if (!_equal(KABCSync::getFieldForHHCustom(customIndex, abEntry, fSyncSettings), + piAddress->getField(hhField))) + { + DEBUGKPILOT << fname << ": Custom field " << customIndex + << " (HH field " << hhField << ") differs." << endl; + return false; + } + } + } + + // if any side is marked archived, but the other is not, the two + // are not equal. + if ( (flags & eqFlagsFlags) && (isArchived(piAddress) || KABCSync::isArchived(abEntry) ) ) + { + DEBUGKPILOT << fname << ": archived flags don't match" << endl; + return false; + } + + return true; +} + + + + + + + + + + +/********************************************************************* + C O N F L I C T R E S O L U T I O N a n d M E R G I N G + *********************************************************************/ + + + +/** smartly merge the given field for the given entry. use the backup record to determine which record has been modified + @pc, @backup, @palm ... entries of the according databases + @returns string of the merged entries. +*/ +TQString AbbrowserConduit::_smartMergeString(const TQString &pc, const TQString & backup, + const TQString & palm, ConflictResolution confRes) +{ + FUNCTIONSETUP; + + // if both entries are already the same, no need to do anything + if(pc == palm) return pc; + + // If this is a first sync, we don't have a backup record, so + if(isFirstSync() || backup.isEmpty()) { + if (pc.isEmpty() && palm.isEmpty() ) return TQString::null; + if(pc.isEmpty()) return palm; + if(palm.isEmpty()) return pc; + } else { + // only one side modified, so return that string, no conflict + if(palm == backup) return pc; + if(pc == backup) return palm; + } + + DEBUGKPILOT<<"pc="<<pc<<", backup="<<backup<<", palm="<< + palm<<", ConfRes="<<confRes<<endl; + DEBUGKPILOT<<"Use conflict resolution :"<<confRes<< + ", PC="<<SyncAction::ePCOverrides<<endl; + switch(confRes) { + case SyncAction::ePCOverrides: return pc; break; + case SyncAction::eHHOverrides: return palm; break; + case SyncAction::ePreviousSyncOverrides: return backup; break; + default: break; + } + return TQString::null; +} + + + +bool AbbrowserConduit::_buildResolutionTable(ResolutionTable*tab, const Addressee &pcAddr, + PilotAddress *backupAddr, PilotAddress *palmAddr) +{ + FUNCTIONSETUP; + if (!tab) return false; + tab->setAutoDelete( TRUE ); + tab->labels[0]=i18n("Item on PC"); + tab->labels[1]=i18n("Handheld"); + tab->labels[2]=i18n("Last sync"); + if (!pcAddr.isEmpty()) + tab->fExistItems=(eExistItems)(tab->fExistItems|eExistsPC); + if (backupAddr) + tab->fExistItems=(eExistItems)(tab->fExistItems|eExistsBackup); + if (palmAddr) + tab->fExistItems=(eExistItems)(tab->fExistItems|eExistsPalm); + +#define appendGen(desc, abfield, palmfield) \ + tab->append(new ResolutionItem(desc, tab->fExistItems, \ + (!pcAddr.isEmpty())?(abfield):(TQString::null), \ + (palmAddr)?(palmAddr->palmfield):(TQString::null), \ + (backupAddr)?(backupAddr->palmfield):(TQString::null) )) +#define appendAddr(desc, abfield, palmfield) \ + appendGen(desc, abfield, getField(palmfield)) +#define appendGenPhone(desc, abfield, palmfield) \ + appendGen(desc, abfield, getPhoneField(PilotAddressInfo::palmfield)) +#define appendPhone(desc, abfield, palmfield) \ + appendGenPhone(desc, pcAddr.phoneNumber(PhoneNumber::abfield).number(), palmfield) + + + appendAddr(i18n("Last name"), pcAddr.familyName(), entryLastname); + appendAddr(i18n("First name"), pcAddr.givenName(), entryFirstname); + appendAddr(i18n("Organization"), pcAddr.organization(), entryCompany); + appendAddr(i18n("Title"), pcAddr.prefix(), entryTitle); + appendAddr(i18n("Note"), pcAddr.note(), entryNote); + + appendAddr(i18n("Custom 1"), KABCSync::getFieldForHHCustom(0, pcAddr, fSyncSettings), entryCustom1); + appendAddr(i18n("Custom 2"), KABCSync::getFieldForHHCustom(1, pcAddr, fSyncSettings), entryCustom2); + appendAddr(i18n("Custom 3"), KABCSync::getFieldForHHCustom(2, pcAddr, fSyncSettings), entryCustom3); + appendAddr(i18n("Custom 4"), KABCSync::getFieldForHHCustom(3, pcAddr, fSyncSettings), entryCustom4); + + appendPhone(i18n("Work Phone"), Work, eWork); + appendPhone(i18n("Home Phone"), Home, eHome); + appendPhone(i18n("Mobile Phone"), Cell, eMobile); + appendGenPhone(i18n("Fax"), pcAddr.phoneNumber(faxTypeOnPC()).number(), eFax); + appendPhone(i18n("Pager"), Pager, ePager); + appendGenPhone(i18n("Other"), KABCSync::getFieldForHHOtherPhone(pcAddr,fSyncSettings), eOther); + appendGenPhone(i18n("Email"), pcAddr.preferredEmail(), eEmail); + + KABC::Address abAddress = KABCSync::getAddress(pcAddr,fSyncSettings); + appendAddr(i18n("Address"), abAddress.street(), entryAddress); + appendAddr(i18n("City"), abAddress.locality(), entryCity); + appendAddr(i18n("Region"), abAddress.region(), entryState); + appendAddr(i18n("Postal code"), abAddress.postalCode(), entryZip); + appendAddr(i18n("Country"), abAddress.country(), entryCountry); + + TQString palmAddrCategoryLabel; + if (palmAddr) + { + palmAddrCategoryLabel = fAddressAppInfo->categoryName(palmAddr->category()); + } + TQString backupAddrCategoryLabel; + if (backupAddr) + { + backupAddrCategoryLabel = fAddressAppInfo->categoryName(backupAddr->category()); + } + int category = palmAddr ? palmAddr->category() : 0; + tab->append(new ResolutionItem( + i18n("Category"), + tab->fExistItems, + !pcAddr.isEmpty() ? + KABCSync::bestMatchedCategoryName(pcAddr.categories(), *fAddressAppInfo, category) : + TQString::null, + palmAddrCategoryLabel, + backupAddrCategoryLabel)); +#undef appendGen +#undef appendAddr +#undef appendGenPhone +#undef appendPhone + + return true; +} + + +/// This function just sets the phone number of type "type" to "phone" +static inline void setPhoneNumber(Addressee &abEntry, int type, const TQString &nr) +{ + PhoneNumber phone = abEntry.phoneNumber(type); + phone.setNumber(nr); + abEntry.insertPhoneNumber(phone); +} + + +bool AbbrowserConduit::_applyResolutionTable(ResolutionTable*tab, Addressee &pcAddr, + PilotAddress *backupAddr, PilotAddress *palmAddr) +{ + FUNCTIONSETUP; + if (!tab) return false; + if (!palmAddr) { + WARNINGKPILOT << "Empty palmAddr after conflict resolution." << endl; + return false; + } + + ResolutionItem*item=tab->first(); +#define SETGENFIELD(abfield, palmfield) \ + if (item) {\ + abfield; \ + palmAddr->setField(palmfield, item->fResolved); \ + }\ + item=tab->next(); +#define SETFIELD(abfield, palmfield) \ + SETGENFIELD(pcAddr.set##abfield(item->fResolved), palmfield) +#define SETCUSTOMFIELD(abfield, palmfield) \ + SETGENFIELD(KABCSync::setFieldFromHHCustom(abfield, pcAddr, item->fResolved, fSyncSettings), palmfield) +#define SETGENPHONE(abfield, palmfield) \ + if (item) { \ + abfield; \ + palmAddr->setPhoneField(PilotAddressInfo::palmfield, item->fResolved, PilotAddress::Replace); \ + }\ + item=tab->next(); +#define SETPHONEFIELD(abfield, palmfield) \ + SETGENPHONE(setPhoneNumber(pcAddr, PhoneNumber::abfield, item->fResolved), palmfield) +#define SETADDRESSFIELD(abfield, palmfield) \ + SETGENFIELD(abAddress.abfield(item->fResolved), palmfield) + + SETFIELD(FamilyName, entryLastname); + SETFIELD(GivenName, entryFirstname); + SETFIELD(Organization, entryCompany); + SETFIELD(Prefix, entryTitle); + SETFIELD(Note, entryNote); + + SETCUSTOMFIELD(0, entryCustom1); + SETCUSTOMFIELD(1, entryCustom2); + SETCUSTOMFIELD(2, entryCustom3); + SETCUSTOMFIELD(3, entryCustom4); + + SETPHONEFIELD(Work, eWork); + SETPHONEFIELD(Home, eHome); + SETPHONEFIELD(Cell, eMobile); + SETGENPHONE(setPhoneNumber(pcAddr, faxTypeOnPC(), item->fResolved), eFax); + SETPHONEFIELD(Pager, ePager); + SETGENPHONE(KABCSync::setFieldFromHHOtherPhone(pcAddr, item->fResolved, fSyncSettings), eOther); + + // TODO: fix email + if (item) + { + palmAddr->setPhoneField(PilotAddressInfo::eEmail, item->fResolved, PilotAddress::Replace); + if (backupAddr) + { + pcAddr.removeEmail(backupAddr->getPhoneField(PilotAddressInfo::eEmail)); + } + pcAddr.removeEmail(palmAddr->getPhoneField(PilotAddressInfo::eEmail)); + pcAddr.insertEmail(item->fResolved, true); + } + item=tab->next(); + + KABC::Address abAddress = KABCSync::getAddress(pcAddr, fSyncSettings); + SETADDRESSFIELD(setStreet, entryAddress); + SETADDRESSFIELD(setLocality, entryCity); + SETADDRESSFIELD(setRegion, entryState); + SETADDRESSFIELD(setPostalCode, entryZip); + SETADDRESSFIELD(setCountry, entryCountry); + pcAddr.insertAddress(abAddress); + + // TODO: Is this correct? + if (item) + { + palmAddr->setCategory( fAddressAppInfo->findCategory(item->fResolved) ); + KABCSync::setCategory(pcAddr, item->fResolved); + } + + +#undef SETGENFIELD +#undef SETFIELD +#undef SETCUSTOMFIELD +#undef SETGENPHONE +#undef SETPHONEFIELD +#undef SETADDRESSFIELD + + return true; +} + + + +bool AbbrowserConduit::_smartMergeTable(ResolutionTable*tab) +{ + FUNCTIONSETUP; + if (!tab) return false; + bool noconflict=true; + ResolutionItem*item; + for ( item = tab->first(); item; item = tab->next() ) + { + // try to merge the three strings + item->fResolved=_smartMergeString(item->fEntries[0], + item->fEntries[2], item->fEntries[1], getConflictResolution()); + // if a conflict occurred, set the default to something sensitive: + if (item->fResolved.isNull() && !(item->fEntries[0].isEmpty() && + item->fEntries[1].isEmpty() && item->fEntries[2].isEmpty() ) ) + { + item->fResolved=item->fEntries[0]; + noconflict=false; + } + if (item->fResolved.isNull()) item->fResolved=item->fEntries[1]; + if (item->fResolved.isNull()) item->fResolved=item->fEntries[2]; + } + return noconflict; +} + + + +/** Merge the palm and the pc entries with the additional information of + * the backup. + * return value: no meaning yet + */ +bool AbbrowserConduit::_smartMergeAddressee(Addressee &pcAddr, + PilotAddress *backupAddr, PilotAddress *palmAddr) +{ + FUNCTIONSETUP; + + // Merge them, then look which records have to be written to device or abook + int res = SyncAction::eAskUser; + bool result=true; + ResolutionTable tab; + + result &= _buildResolutionTable(&tab, pcAddr, backupAddr, palmAddr); + // Now attempt a smart merge. If that fails, let conflict resolution do the job + bool mergeOk=_smartMergeTable(&tab); + + if (!mergeOk) + { + TQString dlgText; + if (!palmAddr) + { + dlgText=i18n("The following address entry was changed, but does no longer exist on the handheld. Please resolve this conflict:"); + } + else if (pcAddr.isEmpty()) + { + dlgText=i18n("The following address entry was changed, but does no longer exist on the PC. Please resolve this conflict:"); + } + else + { + dlgText=i18n("The following address entry was changed on the handheld as well as on the PC side. The changes could not be merged automatically, so please resolve the conflict yourself:"); + } + ResolutionDlg*resdlg=new ResolutionDlg(0L, fHandle, i18n("Address conflict"), dlgText, &tab); + resdlg->exec(); + KPILOT_DELETE(resdlg); + } + res=tab.fResolution; + + // Disallow some resolution under certain conditions, fix wrong values: + switch (res) { + case SyncAction::eHHOverrides: + if (!palmAddr) res=SyncAction::eDelete; + break; + case SyncAction::ePCOverrides: + if (pcAddr.isEmpty()) res=SyncAction::eDelete; + break; + case SyncAction::ePreviousSyncOverrides: + if (!backupAddr) res=SyncAction::eDoNothing; + break; + } + + PilotAddress*pAddr=palmAddr; + bool pAddrCreated=false; + // Now that we have done a possible conflict resolution, apply the changes + switch (res) { + case SyncAction::eDuplicate: + // Set the Palm ID to 0 so we don't overwrite the existing record. + pcAddr.removeCustom(KABCSync::appString, KABCSync::idString); + result &= _copyToHH(pcAddr, 0L, 0L); + { + Addressee pcadr; + result &= _copyToPC(pcadr, backupAddr, palmAddr); + } + break; + case SyncAction::eDoNothing: + break; + case SyncAction::eHHOverrides: + result &= _copyToPC(pcAddr, backupAddr, palmAddr); + break; + case SyncAction::ePCOverrides: + result &= _copyToHH(pcAddr, backupAddr, pAddr); + break; + case SyncAction::ePreviousSyncOverrides: + KABCSync::copy(pcAddr, *backupAddr, *fAddressAppInfo, fSyncSettings); + if (palmAddr && backupAddr) *palmAddr=*backupAddr; + result &= _savePalmAddr(backupAddr, pcAddr); + result &= _savePCAddr(pcAddr, backupAddr, backupAddr); + break; + case SyncAction::eDelete: + result &= _deleteAddressee(pcAddr, backupAddr, palmAddr); + break; + case SyncAction::eAskUser: + default: + if (!pAddr) + { + pAddr=new PilotAddress(); + pAddrCreated=true; + } + result &= _applyResolutionTable(&tab, pcAddr, backupAddr, pAddr); +showAddresses(pcAddr, backupAddr, pAddr); + // savePalmAddr sets the RecordID custom field already + result &= _savePalmAddr(pAddr, pcAddr); + result &= _savePCAddr(pcAddr, backupAddr, pAddr); + if (pAddrCreated) KPILOT_DELETE(pAddr); + break; + } + + return result; +} + + + +// TODO: right now entries are equal if both first/last name and organization are +// equal. This rules out two entries for the same person(e.g. real home and weekend home) +// or two persons with the same name where you don't know the organization.!!! +Addressee AbbrowserConduit::_findMatch(const PilotAddress & pilotAddress) const +{ + FUNCTIONSETUP; + // TODO: also search with the pilotID + // first, use the pilotID to UID map to find the appropriate record + if( !isFirstSync() && (pilotAddress.id() > 0) ) + { + TQString id(addresseeMap[pilotAddress.id()]); + DEBUGKPILOT << fname << ": PilotRecord has id " << pilotAddress.id() << ", mapped to " << id << endl; + if(!id.isEmpty()) + { + Addressee res(aBook->tqfindByUid(id)); + if(!res.isEmpty()) return res; + DEBUGKPILOT << fname << ": PilotRecord has id " << pilotAddress.id() << ", but could not be found in the addressbook" << endl; + } + } + + for(AddressBook::Iterator iter = aBook->begin(); iter != aBook->end(); ++iter) + { + Addressee abEntry = *iter; + TQString recID(abEntry.custom(KABCSync::appString, KABCSync::idString)); + bool ok; + if (!recID.isEmpty() ) + { + recordid_t rid = recID.toLong(&ok); + if (ok && rid) + { + if (rid==pilotAddress.id()) return abEntry;// yes, we found it + // skip this addressee, as it can an other corresponding address on the handheld + if (allIds.tqcontains(rid)) continue; + } + } + + if (_equal(&pilotAddress, abEntry, eqFlagsAlmostAll)) + { + return abEntry; + } + } + DEBUGKPILOT << fname << ": Could not find any addressbook enty matching " << pilotAddress.getField(entryLastname) << endl; + return Addressee(); +} + +void AbbrowserConduit::slotTestRecord() +{ + FUNCTIONSETUP; + + // Get a record and interpret it as an address. + PilotRecord *r = fDatabase->readRecordByIndex( pilotindex ); + if (!r) + { + delayDone(); + return; + } + PilotAddress a(r); + KPILOT_DELETE(r); + + // Process this record. + showPilotAddress(&a); + + // Schedule more work. + ++pilotindex; + TQTimer::singleShot(0, this, TQT_SLOT(slotTestRecord())); +} |