kpilot

abbrowser-conduit.cc

Go to the documentation of this file.
00001 /* KPilot
00002 **
00003 ** Copyright (C) 2000,2001 by Dan Pilone
00004 ** Copyright (C) 2002-2003 by Reinhold Kainhofer
00005 ** Copyright (C) 2007 by Adriaan de Groot <groot@kde.org>
00006 **
00007 ** The abbrowser conduit copies addresses from the Pilot's address book to
00008 ** the KDE addressbook maintained via the kabc library.
00009 */
00010 
00011 /*
00012 ** This program is free software; you can redistribute it and/or modify
00013 ** it under the terms of the GNU General Public License as published by
00014 ** the Free Software Foundation; either version 2 of the License, or
00015 ** (at your option) any later version.
00016 **
00017 ** This program is distributed in the hope that it will be useful,
00018 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
00019 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00020 ** GNU General Public License for more details.
00021 **
00022 ** You should have received a copy of the GNU General Public License
00023 ** along with this program in a file called COPYING; if not, write to
00024 ** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
00025 ** MA 02110-1301, USA.
00026 */
00027 
00028 /*
00029 ** Bug reports and questions can be sent to kde-pim@kde.org.
00030 */
00031 
00032 
00033 
00034 #include "options.h"
00035 
00036 #include <qtimer.h>
00037 #include <qtextcodec.h>
00038 #include <qfile.h>
00039 #include <qregexp.h>
00040 
00041 #include <kabc/stdaddressbook.h>
00042 #include <kabc/resourcefile.h>
00043 #include <kio/netaccess.h>
00044 #include <ksavefile.h>
00045 
00046 #include <pilotSerialDatabase.h>
00047 #include <pilotLocalDatabase.h>
00048 
00049 #include "resolutionDialog.h"
00050 #include "resolutionTable.h"
00051 #include "abbrowserSettings.h"
00052 #include "kabcRecord.h"
00053 
00054 #include "abbrowser-conduit.moc"
00055 
00056 // Something to allow us to check what revision
00057 // the modules are that make up a binary distribution.
00058 //
00059 //
00060 extern "C"
00061 {
00062 unsigned long version_conduit_address = Pilot::PLUGIN_API;
00063 }
00064 
00065 
00066 /* This is partly stolen from the boost libraries, partly from
00067 *  "Modern C++ design" for doing compile time checks; we need
00068 *  to make sure that the enum values in KABCSync:: and in the
00069 *  AbbrowserSettings class are the same so that both interpret
00070 *  configuration values the same way.
00071 */
00072 template<bool> struct EnumerationMismatch;
00073 template<> struct EnumerationMismatch<true>{};
00074 
00075 #define CHECK_ENUM(a) (void)sizeof(EnumerationMismatch<((int)KABCSync::a)==((int)AbbrowserSettings::a)>)
00076 
00077 static inline void compile_time_check()
00078 {
00079     // Mappings for other phone
00080     CHECK_ENUM(eOtherPhone);
00081     CHECK_ENUM(eOtherPhone);
00082     CHECK_ENUM(eAssistant);
00083     CHECK_ENUM(eBusinessFax);
00084     CHECK_ENUM(eCarPhone);
00085     CHECK_ENUM(eEmail2);
00086     CHECK_ENUM(eHomeFax);
00087     CHECK_ENUM(eTelex);
00088     CHECK_ENUM(eTTYTTDPhone);
00089 
00090     // Mappings for custom fields
00091     CHECK_ENUM(eCustomField);
00092     CHECK_ENUM(eCustomBirthdate);
00093     CHECK_ENUM(eCustomURL);
00094     CHECK_ENUM(eCustomIM);
00095 }
00096 
00097 inline int faxTypeOnPC()
00098 {
00099     return KABC::PhoneNumber::Fax |
00100         ( (AbbrowserSettings::pilotFax()==0) ?
00101             KABC::PhoneNumber::Home :
00102             KABC::PhoneNumber::Work );
00103 }
00104 
00105 
00106 using namespace KABC;
00107 
00108 /*********************************************************************
00109                         C O N S T R U C T O R
00110  *********************************************************************/
00111 
00112 
00113 AbbrowserConduit::AbbrowserConduit(KPilotLink * o, const char *n, const QStringList & a):
00114         ConduitAction(o, n, a),
00115         aBook(0L),
00116         fAddressAppInfo(0L),
00117         addresseeMap(),
00118         syncedIds(),
00119         abiter(),
00120         fTicket(0L),
00121         fCreatedBook(false),
00122         fBookResource(0L)
00123 {
00124     FUNCTIONSETUP;
00125     fConduitName=i18n("Addressbook");
00126 }
00127 
00128 
00129 
00130 AbbrowserConduit::~AbbrowserConduit()
00131 {
00132     FUNCTIONSETUP;
00133 
00134     if (fTicket)
00135     {
00136         DEBUGKPILOT << fname << ": Releasing ticket" << endl;
00137         aBook->releaseSaveTicket(fTicket);
00138         fTicket=0L;
00139     }
00140 
00141     _cleanupAddressBookPointer();
00142     // unused function warnings.
00143     compile_time_check();
00144 }
00145 
00146 
00147 
00148 /*********************************************************************
00149                 L O A D I N G   T H E   D A T A
00150  *********************************************************************/
00151 
00152 
00153 
00154 /* Builds the map which links record ids to uid's of Addressee
00155 */
00156 void AbbrowserConduit::_mapContactsToPilot(QMap < recordid_t, QString > &idContactMap)
00157 {
00158     FUNCTIONSETUP;
00159 
00160     idContactMap.clear();
00161 
00162     for(AddressBook::Iterator contactIter = aBook->begin();
00163         contactIter != aBook->end(); ++contactIter)
00164     {
00165         Addressee aContact = *contactIter;
00166         QString recid = aContact.custom(KABCSync::appString, KABCSync::idString);
00167         if(!recid.isEmpty())
00168         {
00169             recordid_t id = recid.toULong();
00170             // safety check:  make sure that we don't already have a map for this pilot id.
00171             // if we do (this can come from a copy/paste in kaddressbook, etc.), then we need
00172             // to reset our Addressee so that we can assign him a new pilot Id later and sync
00173             // him properly.  if we don't do this, we'll lose one of these on the pilot.
00174             if (!idContactMap.contains(id))
00175             {
00176                 idContactMap.insert(id, aContact.uid());
00177             }
00178             else
00179             {
00180         DEBUGKPILOT << fname << ": found duplicate pilot key: ["
00181                         << id << "], removing pilot id from addressee: ["
00182                         << aContact.realName() << "]" << endl;
00183                 aContact.removeCustom(KABCSync::appString, KABCSync::idString);
00184                 aBook->insertAddressee(aContact);
00185                 abChanged = true;
00186             }
00187         }
00188     }
00189     DEBUGKPILOT << fname << ": Loaded " << idContactMap.size() <<
00190         " addresses from the addressbook. " << endl;
00191 }
00192 
00193 
00194 
00195 bool AbbrowserConduit::_prepare()
00196 {
00197     FUNCTIONSETUP;
00198 
00199     readConfig();
00200     syncedIds.clear();
00201     pilotindex = 0;
00202 
00203     return true;
00204 }
00205 
00206 
00207 
00208 void AbbrowserConduit::readConfig()
00209 {
00210     FUNCTIONSETUP;
00211     AbbrowserSettings::self()->readConfig();
00212 
00213     // Conflict page
00214     SyncAction::ConflictResolution res = (SyncAction::ConflictResolution)AbbrowserSettings::conflictResolution();
00215     setConflictResolution(res);
00216 
00217     DEBUGKPILOT << fname
00218         << ": Reading addressbook "
00219         << ( AbbrowserSettings::addressbookType() == AbbrowserSettings::eAbookFile ?
00220             AbbrowserSettings::fileName() : CSL1("Standard") )
00221         << endl;
00222     DEBUGKPILOT << fname
00223         << ": "
00224         << " fConflictResolution=" << getConflictResolution()
00225         << " fArchive=" << AbbrowserSettings::archiveDeleted()
00226         << " fFirstTime=" << isFirstSync()
00227         << endl;
00228     DEBUGKPILOT << fname
00229         << ": "
00230         << " fPilotStreetHome=" << AbbrowserSettings::pilotStreet()
00231         << " fPilotFaxHome=" << AbbrowserSettings::pilotFax()
00232         << " eCustom[0]=" << AbbrowserSettings::custom0()
00233         << " eCustom[1]=" << AbbrowserSettings::custom1()
00234         << " eCustom[2]=" << AbbrowserSettings::custom2()
00235         << " eCustom[3]=" << AbbrowserSettings::custom3()
00236         << endl;
00237 }
00238 
00239 
00240 
00241 bool isDeleted(const PilotAddress *addr)
00242 {
00243     if (!addr)
00244     {
00245         return true;
00246     }
00247     if (addr->isDeleted() && !addr->isArchived())
00248     {
00249         return true;
00250     }
00251     if (addr->isArchived())
00252     {
00253         return !AbbrowserSettings::archiveDeleted();
00254     }
00255     return false;
00256 }
00257 
00258 bool isArchived(const PilotAddress *addr)
00259 {
00260     if (addr && addr->isArchived())
00261     {
00262         return AbbrowserSettings::archiveDeleted();
00263     }
00264     else
00265     {
00266         return false;
00267     }
00268 }
00269 
00270 
00271 
00272 bool AbbrowserConduit::_loadAddressBook()
00273 {
00274     FUNCTIONSETUP;
00275 
00276     startTickle();
00277     switch ( AbbrowserSettings::addressbookType() )
00278     {
00279         case AbbrowserSettings::eAbookResource:
00280             DEBUGKPILOT<<"Loading standard addressbook"<<endl;
00281             aBook = StdAddressBook::self( true );
00282             fCreatedBook=false;
00283             break;
00284         case AbbrowserSettings::eAbookFile:
00285         { // initialize the abook with the given file
00286             DEBUGKPILOT<<"Loading custom addressbook"<<endl;
00287             KURL kurl(AbbrowserSettings::fileName());
00288             if(!KIO::NetAccess::download(AbbrowserSettings::fileName(), fABookFile, 0L) &&
00289                 !kurl.isLocalFile())
00290             {
00291                 emit logError(i18n("You chose to sync with the file \"%1\", which "
00292                             "cannot be opened. Please make sure to supply a "
00293                             "valid file name in the conduit's configuration dialog. "
00294                             "Aborting the conduit.").arg(AbbrowserSettings::fileName()));
00295                 KIO::NetAccess::removeTempFile(fABookFile);
00296                 stopTickle();
00297                 return false;
00298             }
00299 
00300             aBook = new AddressBook();
00301             if (!aBook)
00302             {
00303                 stopTickle();
00304                 return false;
00305             }
00306             fBookResource = new ResourceFile(fABookFile, CSL1("vcard") );
00307 
00308             bool r = aBook->addResource( fBookResource );
00309             if ( !r )
00310             {
00311                 DEBUGKPILOT << "Unable to open resource for file " << fABookFile << endl;
00312                 KPILOT_DELETE( aBook );
00313                 stopTickle();
00314                 return false;
00315             }
00316             fCreatedBook=true;
00317             break;
00318         }
00319         default: break;
00320     }
00321     // find out if this can fail for reasons other than a non-existent
00322     // vcf file. If so, how can I determine if the missing file was the problem
00323     // or something more serious:
00324     if ( !aBook || !aBook->load() )
00325     {
00326         // Something went wrong, so tell the user and return false to exit the conduit
00327         emit logError(i18n("Unable to initialize and load the addressbook for the sync.") );
00328         addSyncLogEntry(i18n("Unable to initialize and load the addressbook for the sync.") );
00329         WARNINGKPILOT << "Unable to initialize the addressbook for the sync." << endl;
00330         _cleanupAddressBookPointer();
00331         stopTickle();
00332         return false;
00333     }
00334     abChanged = false;
00335 
00336     fTicket=aBook->requestSaveTicket();
00337     if (!fTicket)
00338     {
00339         WARNINGKPILOT << "Unable to lock addressbook for writing " << endl;
00340         emit logError(i18n("Unable to lock addressbook for writing.  Can't sync!"));
00341         addSyncLogEntry(i18n("Unable to lock addressbook for writing.  Can't sync!"));
00342         _cleanupAddressBookPointer();
00343         stopTickle();
00344         return false;
00345     }
00346 
00347     fCtrPC->setStartCount(aBook->allAddressees().count());
00348 
00349     // get the addresseMap which maps Pilot unique record(address) id's to
00350     // a Abbrowser Addressee; allows for easy lookup and comparisons
00351     if(aBook->begin() == aBook->end())
00352     {
00353         setFirstSync( true );
00354     }
00355     else
00356     {
00357         _mapContactsToPilot(addresseeMap);
00358     }
00359     stopTickle();
00360     return(aBook != 0L);
00361 }
00362 
00363 bool AbbrowserConduit::_saveAddressBook()
00364 {
00365     FUNCTIONSETUP;
00366 
00367     bool saveSuccessful = false;
00368 
00369     fCtrPC->setEndCount(aBook->allAddressees().count());
00370 
00371     Q_ASSERT(fTicket);
00372 
00373     if (abChanged)
00374     {
00375         saveSuccessful = aBook->save(fTicket);
00376     }
00377     else
00378     {
00379         DEBUGKPILOT << fname
00380             << "Addressbook not changed, no need to save it" << endl;
00381     }
00382     // XXX: KDE4: release ticket in all cases (save no longer releases it)
00383     if ( !saveSuccessful ) // didn't save, delete ticket manually
00384     {
00385         aBook->releaseSaveTicket(fTicket);
00386     }
00387     fTicket=0L;
00388 
00389     if ( AbbrowserSettings::addressbookType()!= AbbrowserSettings::eAbookResource )
00390     {
00391         KURL kurl(AbbrowserSettings::fileName());
00392         if(!kurl.isLocalFile())
00393         {
00394             DEBUGKPILOT << fname << "Deleting local addressbook tempfile" << endl;
00395             if(!KIO::NetAccess::upload(fABookFile, AbbrowserSettings::fileName(), 0L)) {
00396                 emit logError(i18n("An error occurred while uploading \"%1\". You can try to upload "
00397                     "the temporary local file \"%2\" manually")
00398                     .arg(AbbrowserSettings::fileName()).arg(fABookFile));
00399             }
00400             else {
00401                 KIO::NetAccess::removeTempFile(fABookFile);
00402             }
00403             QFile backup(fABookFile + CSL1("~"));
00404             backup.remove();
00405         }
00406 
00407     }
00408 
00409     // now try to remove the resource from the addressbook...
00410     if (fBookResource) 
00411     {
00412         bool r = aBook->removeResource( fBookResource );
00413         if ( !r )
00414         {
00415             DEBUGKPILOT << fname <<": Unable to close resource." << endl;
00416         }
00417     }
00418 
00419     return saveSuccessful;
00420 }
00421 
00422 
00423 
00424 void AbbrowserConduit::_getAppInfo()
00425 {
00426     FUNCTIONSETUP;
00427 
00428     delete fAddressAppInfo;
00429     fAddressAppInfo = new PilotAddressInfo(fDatabase);
00430     fAddressAppInfo->dump();
00431 }
00432 
00433 void AbbrowserConduit::_setAppInfo()
00434 {
00435     FUNCTIONSETUP;
00436     if (fDatabase) fAddressAppInfo->writeTo(fDatabase);
00437     if (fLocalDatabase) fAddressAppInfo->writeTo(fLocalDatabase);
00438 }
00439 
00440 
00441 void AbbrowserConduit::_cleanupAddressBookPointer()
00442 {
00443         if (fCreatedBook)
00444         {
00445                 KPILOT_DELETE(aBook);
00446                 fCreatedBook=false;
00447         }
00448         else
00449         {
00450                 aBook=0L;
00451     }                                                        
00452 }
00453 
00454 
00455 
00456 
00457 /*********************************************************************
00458                      D E B U G   O U T P U T
00459  *********************************************************************/
00460 
00461 
00462 
00463 
00464 
00465 void AbbrowserConduit::showPilotAddress(const PilotAddress *pilotAddress)
00466 {
00467     FUNCTIONSETUPL(3);
00468     if (debug_level < 3)
00469     {
00470         return;
00471     }
00472     if (!pilotAddress)
00473     {
00474         DEBUGKPILOT<< fname << "| EMPTY"<<endl;
00475         return;
00476     }
00477     DEBUGKPILOT << fname << "\n"
00478             << pilotAddress->getTextRepresentation(
00479                 fAddressAppInfo,Qt::PlainText) << endl;
00480 }
00481 
00482 
00483 void AbbrowserConduit::showAddresses(
00484     const Addressee &pcAddr,
00485     const PilotAddress *backupAddr,
00486     const PilotAddress *palmAddr)
00487 {
00488     FUNCTIONSETUPL(3);
00489     if (debug_level >= 3)
00490     {
00491         DEBUGKPILOT << fname << "abEntry:" << endl;
00492         KABCSync::showAddressee(pcAddr);
00493         DEBUGKPILOT << fname << "pilotAddress:" << endl;
00494         showPilotAddress(palmAddr);
00495         DEBUGKPILOT << fname << "backupAddress:" << endl;
00496         showPilotAddress(backupAddr);
00497         DEBUGKPILOT << fname << "------------------------------------------------" << endl;
00498     }
00499 }
00500 
00501 
00502 
00503 /*********************************************************************
00504                 S Y N C   S T R U C T U R E
00505  *********************************************************************/
00506 
00507 
00508 
00509 /* virtual */ bool AbbrowserConduit::exec()
00510 {
00511     FUNCTIONSETUP;
00512 
00513     _prepare();
00514 
00515     bool retrieved = false;
00516     if(!openDatabases(CSL1("AddressDB"), &retrieved))
00517     {
00518         emit logError(i18n("Unable to open the addressbook databases on the handheld."));
00519         return false;
00520     }
00521     setFirstSync( retrieved );
00522 
00523     _getAppInfo();
00524 
00525     // Local block
00526     {
00527         QString dbpath = fLocalDatabase->dbPathName();
00528         DEBUGKPILOT << fname << ": Local database path " << dbpath << endl;
00529     }
00530 
00531     if ( syncMode().isTest() )
00532     {
00533         QTimer::singleShot(0, this, SLOT(slotTestRecord()));
00534         return true;
00535     }
00536 
00537     if(!_loadAddressBook())
00538     {
00539         emit logError(i18n("Unable to open the addressbook."));
00540         return false;
00541     }
00542     setFirstSync( isFirstSync() || (aBook->begin() == aBook->end()) );
00543 
00544     DEBUGKPILOT << fname << ": First sync now " << isFirstSync()
00545         << " and addressbook is "
00546         << ((aBook->begin() == aBook->end()) ? "" : "non-")
00547         << "empty." << endl;
00548 
00549     // perform syncing from palm to abbrowser
00550     // iterate through all records in palm pilot
00551 
00552     DEBUGKPILOT << fname << ": fullsync=" << isFullSync() << ", firstSync=" <<    isFirstSync() << endl;
00553     DEBUGKPILOT << fname << ": "
00554         << "syncDirection=" << syncMode().name() << ", "
00555         << "archive = " << AbbrowserSettings::archiveDeleted() << endl;
00556     DEBUGKPILOT << fname << ": conflictRes="<< getConflictResolution() << endl;
00557     DEBUGKPILOT << fname << ": PilotStreetHome=" << AbbrowserSettings::pilotStreet() << ", PilotFaxHOme" << AbbrowserSettings::pilotFax() << endl;
00558 
00559     if (!isFirstSync())
00560     {
00561         allIds=fDatabase->idList();
00562     }
00563 
00564     QValueVector<int> v(4);
00565     v[0] = AbbrowserSettings::custom0();
00566     v[1] = AbbrowserSettings::custom1();
00567     v[2] = AbbrowserSettings::custom2();
00568     v[3] = AbbrowserSettings::custom3();
00569 
00570     fSyncSettings.setCustomMapping(v);
00571     fSyncSettings.setFieldForOtherPhone(AbbrowserSettings::pilotOther());
00572     fSyncSettings.setDateFormat(AbbrowserSettings::customDateFormat());
00573     fSyncSettings.setPreferHome(AbbrowserSettings::pilotStreet()==0);
00574     fSyncSettings.setFaxTypeOnPC(faxTypeOnPC());
00575 
00576     /* Note:
00577        if eCopyPCToHH or eCopyHHToPC, first sync everything, then lookup
00578        those entries on the receiving side that are not yet syncced and delete
00579        them. Use slotDeleteUnsyncedPCRecords and slotDeleteUnsyncedHHRecords
00580        for this, and no longer purge the whole addressbook before the sync to
00581        prevent data loss in case of connection loss. */
00582 
00583     QTimer::singleShot(0, this, SLOT(slotPalmRecToPC()));
00584 
00585     return true;
00586 }
00587 
00588 
00589 
00590 void AbbrowserConduit::slotPalmRecToPC()
00591 {
00592     FUNCTIONSETUP;
00593     PilotRecord *palmRec = 0L, *backupRec = 0L;
00594 
00595     if ( syncMode() == SyncMode::eCopyPCToHH )
00596     {
00597         DEBUGKPILOT << fname << ": Done; change to PCtoHH phase." << endl;
00598         abiter = aBook->begin();
00599         QTimer::singleShot(0, this, SLOT(slotPCRecToPalm()));
00600         return;
00601     }
00602 
00603     if(isFullSync())
00604     {
00605         palmRec = fDatabase->readRecordByIndex(pilotindex++);
00606     }
00607     else
00608     {
00609         palmRec = fDatabase->readNextModifiedRec();
00610     }
00611 
00612     // no record means we're done going in this direction, so switch to
00613     // PC->Palm
00614     if(!palmRec)
00615     {
00616         abiter = aBook->begin();
00617         QTimer::singleShot(0, this, SLOT(slotPCRecToPalm()));
00618         return;
00619     }
00620 
00621     // already synced, so skip:
00622     if(syncedIds.contains(palmRec->id()))
00623     {
00624         KPILOT_DELETE(palmRec);
00625         QTimer::singleShot(0, this, SLOT(slotPalmRecToPC()));
00626         return;
00627     }
00628 
00629     backupRec = fLocalDatabase->readRecordById(palmRec->id());
00630     PilotRecord*compareRec=(backupRec)?(backupRec):(palmRec);
00631     Addressee e = _findMatch(PilotAddress(compareRec));
00632 
00633     PilotAddress*backupAddr=0L;
00634     if (backupRec)
00635     {
00636         backupAddr=new PilotAddress(backupRec);
00637     }
00638 
00639     PilotAddress*palmAddr=0L;
00640     if (palmRec)
00641     {
00642         palmAddr=new PilotAddress(palmRec);
00643     }
00644 
00645     syncAddressee(e, backupAddr, palmAddr);
00646 
00647     syncedIds.append(palmRec->id());
00648     KPILOT_DELETE(palmAddr);
00649     KPILOT_DELETE(backupAddr);
00650     KPILOT_DELETE(palmRec);
00651     KPILOT_DELETE(backupRec);
00652 
00653     QTimer::singleShot(0, this, SLOT(slotPalmRecToPC()));
00654 }
00655 
00656 
00657 
00658 void AbbrowserConduit::slotPCRecToPalm()
00659 {
00660     FUNCTIONSETUP;
00661 
00662     if ( (syncMode()==SyncMode::eCopyHHToPC) ||
00663         abiter == aBook->end() || (*abiter).isEmpty() )
00664     {
00665         DEBUGKPILOT << fname << ": Done; change to delete records." << endl;
00666         pilotindex = 0;
00667         QTimer::singleShot(0, this, SLOT(slotDeletedRecord()));
00668         return;
00669     }
00670 
00671     PilotRecord *palmRec=0L, *backupRec=0L;
00672     Addressee ad = *abiter;
00673 
00674     abiter++;
00675 
00676     // If marked as archived, don't sync!
00677     if (KABCSync::isArchived(ad))
00678     {
00679         DEBUGKPILOT << fname << ": address with id " << ad.uid() <<
00680             " marked archived, so don't sync." << endl;
00681         QTimer::singleShot(0, this, SLOT(slotPCRecToPalm()));
00682         return;
00683     }
00684 
00685 
00686     QString recID(ad.custom(KABCSync::appString, KABCSync::idString));
00687     bool ok;
00688     recordid_t rid = recID.toLong(&ok);
00689     if (recID.isEmpty() || !ok || !rid)
00690     {
00691         DEBUGKPILOT << fname << ": This is a new record." << endl;
00692         // it's a new item(no record ID and not inserted by the Palm -> PC sync), so add it
00693         syncAddressee(ad, 0L, 0L);
00694         QTimer::singleShot(0, this, SLOT(slotPCRecToPalm()));
00695         return;
00696     }
00697 
00698     // look into the list of already synced record ids to see if the addressee hasn't already been synced
00699     if (syncedIds.contains(rid))
00700     {
00701         DEBUGKPILOT << ": address with id " << rid << " already synced." << endl;
00702         QTimer::singleShot(0, this, SLOT(slotPCRecToPalm()));
00703         return;
00704     }
00705 
00706 
00707     backupRec = fLocalDatabase->readRecordById(rid);
00708     // only update if no backup record or the backup record is not equal to the addressee
00709 
00710     PilotAddress*backupAddr=0L;
00711     if (backupRec)
00712     {
00713         backupAddr=new PilotAddress(backupRec);
00714     }
00715     if(!backupRec || isFirstSync() || !_equal(backupAddr, ad)  )
00716     {
00717         DEBUGKPILOT << fname << ": Updating entry." << endl;
00718         palmRec = fDatabase->readRecordById(rid);
00719         PilotAddress *palmAddr = 0L;
00720         if (palmRec)
00721         {
00722             palmAddr = new PilotAddress(palmRec);
00723         }
00724         else
00725         {
00726         DEBUGKPILOT << fname << ": No HH record with id " << rid << endl;
00727         }
00728         syncAddressee(ad, backupAddr, palmAddr);
00729         // update the id just in case it changed
00730         if (palmRec) rid=palmRec->id();
00731         KPILOT_DELETE(palmRec);
00732         KPILOT_DELETE(palmAddr);
00733     }
00734     else
00735     {
00736         DEBUGKPILOT << fname << ": Entry not updated." << endl;
00737     }
00738     KPILOT_DELETE(backupAddr);
00739     KPILOT_DELETE(backupRec);
00740     
00741     DEBUGKPILOT << fname << ": adding id:["<< rid << "] to syncedIds." << endl;
00742 
00743     syncedIds.append(rid);
00744     // done with the sync process, go on with the next one:
00745     QTimer::singleShot(0, this, SLOT(slotPCRecToPalm()));
00746 }
00747 
00748 
00749 
00750 void AbbrowserConduit::slotDeletedRecord()
00751 {
00752     FUNCTIONSETUP;
00753 
00754     PilotRecord *backupRec = fLocalDatabase->readRecordByIndex(pilotindex++);
00755     if(!backupRec || isFirstSync() )
00756     {
00757         KPILOT_DELETE(backupRec);
00758         QTimer::singleShot(0, this, SLOT(slotDeleteUnsyncedPCRecords()));
00759         return;
00760     }
00761 
00762     recordid_t id = backupRec->id();
00763 
00764     QString uid = addresseeMap[id];
00765     Addressee e = aBook->findByUid(uid);
00766 
00767         DEBUGKPILOT << fname << ": now looking at palm id: ["
00768                     << id << "], kabc uid: [" << uid << "]." << endl;
00769 
00770     PilotAddress*backupAddr=0L;
00771     if (backupRec)
00772     {
00773         backupAddr=new PilotAddress(backupRec);
00774     }
00775     PilotRecord*palmRec=fDatabase->readRecordById(id);
00776 
00777     if ( e.isEmpty() )
00778     {
00779         DEBUGKPILOT << fname << ": no Addressee found for this id." << endl;
00780         DEBUGKPILOT << fname << "\n"
00781             << backupAddr->getTextRepresentation( 
00782                 fAddressAppInfo,Qt::PlainText) << endl;
00783 
00784         if (palmRec) {
00785         DEBUGKPILOT << fname << ": deleting from database on palm." << endl;
00786             fDatabase->deleteRecord(id);
00787             fCtrHH->deleted();
00788         }
00789         DEBUGKPILOT << fname << ": deleting from backup database." << endl;
00790         fLocalDatabase->deleteRecord(id);
00791 
00792         // because we just deleted a record, we need to go back one
00793         pilotindex--;
00794     }
00795 
00796     KPILOT_DELETE(palmRec);
00797     KPILOT_DELETE(backupAddr);
00798     KPILOT_DELETE(backupRec);
00799     QTimer::singleShot(0, this, SLOT(slotDeletedRecord()));
00800 }
00801 
00802 
00803 
00804 void AbbrowserConduit::slotDeleteUnsyncedPCRecords()
00805 {
00806     FUNCTIONSETUP;
00807     if ( syncMode()==SyncMode::eCopyHHToPC )
00808     {
00809         QStringList uids;
00810         RecordIDList::iterator it;
00811         QString uid;
00812         for ( it = syncedIds.begin(); it != syncedIds.end(); ++it)
00813         {
00814             uid=addresseeMap[*it];
00815             if (!uid.isEmpty()) uids.append(uid);
00816         }
00817         // TODO: Does this speed up anything?
00818         // qHeapSort( uids );
00819         AddressBook::Iterator abit;
00820         for (abit = aBook->begin(); abit != aBook->end(); ++abit)
00821         {
00822             if (!uids.contains((*abit).uid()))
00823             {
00824                 DEBUGKPILOT<<"Deleting addressee "<<(*abit).realName()<<" from PC (is not on HH, and syncing with HH->PC direction)"<<endl;
00825                 abChanged = true;
00826                 // TODO: Can I really remove the current iterator???
00827                 aBook->removeAddressee(*abit);
00828                 fCtrPC->deleted();
00829             }
00830         }
00831     }
00832     QTimer::singleShot(0, this, SLOT(slotDeleteUnsyncedHHRecords()));
00833 }
00834 
00835 
00836 
00837 void AbbrowserConduit::slotDeleteUnsyncedHHRecords()
00838 {
00839     FUNCTIONSETUP;
00840     if ( syncMode()==SyncMode::eCopyPCToHH )
00841     {
00842         RecordIDList ids=fDatabase->idList();
00843         RecordIDList::iterator it;
00844         for ( it = ids.begin(); it != ids.end(); ++it )
00845         {
00846             if (!syncedIds.contains(*it))
00847             {
00848                 DEBUGKPILOT<<"Deleting record with ID "<<*it<<" from handheld (is not on PC, and syncing with PC->HH direction)"<<endl;
00849                 fDatabase->deleteRecord(*it);
00850                 fCtrHH->deleted();
00851                 fLocalDatabase->deleteRecord(*it);
00852             }
00853         }
00854     }
00855     QTimer::singleShot(0, this, SLOT(slotCleanup()));
00856 }
00857 
00858 
00859 void AbbrowserConduit::slotCleanup()
00860 {
00861     FUNCTIONSETUP;
00862 
00863     // Set the appInfoBlock, just in case the category labels changed
00864     _setAppInfo();
00865     if(fDatabase)
00866     {
00867         fDatabase->resetSyncFlags();
00868         fDatabase->cleanup();
00869     }
00870     if(fLocalDatabase)
00871     {
00872         fLocalDatabase->resetSyncFlags();
00873         fLocalDatabase->cleanup();
00874     }
00875 
00876     // Write out the sync maps
00877     QString syncFile = fLocalDatabase->dbPathName() + CSL1(".sync");
00878     DEBUGKPILOT << fname << ": Writing sync map to " << syncFile << endl;
00879     KSaveFile map( syncFile );
00880     if ( map.status() == 0 )
00881     {
00882         DEBUGKPILOT << fname << ": Writing sync map ..." << endl;
00883         (*map.dataStream()) << addresseeMap ;
00884         map.close();
00885     }
00886     // This also picks up errors from map.close()
00887     if ( map.status() != 0 )
00888     {
00889         WARNINGKPILOT << "Could not make backup of sync map." << endl;
00890     }
00891 
00892     _saveAddressBook();
00893     delayDone();
00894 }
00895 
00896 
00897 
00898 /*********************************************************************
00899               G E N E R A L   S Y N C   F U N C T I O N
00900          These functions modify the Handheld and the addressbook
00901  *********************************************************************/
00902 
00903 
00904 
00905 bool AbbrowserConduit::syncAddressee(Addressee &pcAddr, PilotAddress*backupAddr,
00906         PilotAddress*palmAddr)
00907 {
00908     FUNCTIONSETUP;
00909     showAddresses(pcAddr, backupAddr, palmAddr);
00910 
00911     if ( syncMode() == SyncMode::eCopyPCToHH )
00912     {
00913         if (pcAddr.isEmpty())
00914         {
00915             return _deleteAddressee(pcAddr, backupAddr, palmAddr);
00916         }
00917         else
00918         {
00919             return _copyToHH(pcAddr, backupAddr, palmAddr);
00920         }
00921     }
00922 
00923     if ( syncMode() == SyncMode::eCopyHHToPC )
00924     {
00925         if (!palmAddr)
00926         {
00927             return _deleteAddressee(pcAddr, backupAddr, palmAddr);
00928         }
00929         else
00930         {
00931             return _copyToPC(pcAddr, backupAddr, palmAddr);
00932         }
00933     }
00934 
00935     if ( !backupAddr || isFirstSync() )
00936     {
00937             DEBUGKPILOT<< fname << ": Special case: no backup." << endl;
00938         /*
00939         Resolution matrix (0..does not exist, E..exists, D..deleted flag set, A..archived):
00940           HH    PC  | Resolution
00941           ------------------------------------------------------------
00942            0     A  |  -
00943            0     E  |  PC -> HH, reset ID if not set correctly
00944            D     0  |  delete (error, should never occur!!!)
00945            D     E  |  CR (ERROR)
00946            E/A   0  |  HH -> PC
00947            E/A   E/A|  merge/CR
00948          */
00949         if  (!palmAddr && KABCSync::isArchived(pcAddr) )
00950         {
00951             return true;
00952         }
00953         else if (!palmAddr && !pcAddr.isEmpty())
00954         {
00955             DEBUGKPILOT << fname << ": case: 1a"<<endl;
00956             // PC->HH
00957             bool res=_copyToHH(pcAddr, 0L, 0L);
00958             return res;
00959         }
00960         else if (!palmAddr && pcAddr.isEmpty())
00961         {
00962             DEBUGKPILOT << fname << ": case: 1b"<<endl;
00963             // everything's empty -> ERROR
00964             return false;
00965         }
00966         else if ( (isDeleted(palmAddr) || isArchived(palmAddr)) && pcAddr.isEmpty())
00967         {
00968             DEBUGKPILOT << fname << ": case: 1c"<<endl;
00969             if (isArchived(palmAddr))
00970                 return _copyToPC(pcAddr, 0L, palmAddr);
00971             else
00972                 // this happens if you add a record on the handheld and delete it again before you do the next sync
00973                 return _deleteAddressee(pcAddr, 0L, palmAddr);
00974         }
00975         else if ((isDeleted(palmAddr)||isArchived(palmAddr)) && !pcAddr.isEmpty())
00976         {
00977             DEBUGKPILOT << fname << ": case: 1d"<<endl;
00978             // CR (ERROR)
00979             return _smartMergeAddressee(pcAddr, 0L, palmAddr);
00980         }
00981         else if (pcAddr.isEmpty())
00982         {
00983             DEBUGKPILOT << fname << ": case: 1e"<<endl;
00984             // HH->PC
00985             return _copyToPC(pcAddr, 0L, palmAddr);
00986         }
00987         else
00988         {
00989             DEBUGKPILOT << fname << ": case: 1f"<<endl;
00990             // Conflict Resolution
00991             return _smartMergeAddressee(pcAddr, 0L, palmAddr);
00992         }
00993     } // !backupAddr
00994     else
00995     {
00996             DEBUGKPILOT << fname << ": case: 2"<<endl;
00997         /*
00998         Resolution matrix:
00999           1) if HH.(empty| (deleted &! archived) ) -> { if (PC==B) -> delete, else -> CR }
01000              if HH.archied -> {if (PC==B) -> copyToPC, else -> CR }
01001              if PC.empty -> { if (HH==B) -> delete, else -> CR }
01002              if PC.archived -> {if (HH==B) -> delete on HH, else CR }
01003           2) if PC==HH -> { update B, update ID of PC if needed }
01004           3) if PC==B -> { HH!=PC, thus HH modified, so copy HH->PC }
01005              if HH==B -> { PC!=HH, thus PC modified, so copy PC->HH }
01006           4) else: all three addressees are different -> CR
01007         */
01008 
01009         if (!palmAddr || isDeleted(palmAddr) )
01010         {
01011             DEBUGKPILOT << fname << ": case: 2a"<<endl;
01012             if (_equal(backupAddr, pcAddr) || pcAddr.isEmpty())
01013             {
01014                 return _deleteAddressee(pcAddr, backupAddr, 0L);
01015             }
01016             else
01017             {
01018                 return _smartMergeAddressee(pcAddr, backupAddr, 0L);
01019             }
01020         }
01021         else if (pcAddr.isEmpty())
01022         {
01023             DEBUGKPILOT << fname << ": case: 2b"<<endl;
01024             if (*palmAddr == *backupAddr)
01025             {
01026                 return _deleteAddressee(pcAddr, backupAddr, palmAddr);
01027             }
01028             else
01029             {
01030                 return _smartMergeAddressee(pcAddr, backupAddr, palmAddr);
01031             }
01032         }
01033         else if (_equal(palmAddr, pcAddr))
01034         {
01035             DEBUGKPILOT << fname << ": case: 2c"<<endl;
01036             // update Backup, update ID of PC if neededd
01037             return _writeBackup(palmAddr);
01038         }
01039         else if (_equal(backupAddr, pcAddr))
01040         {
01041             DEBUGKPILOT << fname << ": case: 2d"<<endl;
01042             DEBUGKPILOT << fname << ": Flags: "<<palmAddr->attributes()<<", isDeleted="<<
01043                 isDeleted(palmAddr)<<", isArchived="<<isArchived(palmAddr)<<endl;
01044             if (isDeleted(palmAddr))
01045                 return _deleteAddressee(pcAddr, backupAddr, palmAddr);
01046             else
01047                 return _copyToPC(pcAddr, backupAddr, palmAddr);
01048         }
01049         else if (*palmAddr == *backupAddr)
01050         {
01051             DEBUGKPILOT << fname << ": case: 2e"<<endl;
01052             return _copyToHH(pcAddr, backupAddr, palmAddr);
01053         }
01054         else
01055         {
01056             DEBUGKPILOT << fname << ": case: 2f"<<endl;
01057             // CR, since all are different
01058             return _smartMergeAddressee(pcAddr, backupAddr, palmAddr);
01059         }
01060     } // backupAddr
01061     return false;
01062 }
01063 
01064 
01065 
01066 bool AbbrowserConduit::_copyToHH(Addressee &pcAddr, PilotAddress*backupAddr,
01067         PilotAddress*palmAddr)
01068 {
01069     FUNCTIONSETUP;
01070 
01071     if (pcAddr.isEmpty()) return false;
01072     PilotAddress*paddr=palmAddr;
01073     bool paddrcreated=false;
01074     if (!paddr)
01075     {
01076         paddr=new PilotAddress();
01077         paddrcreated=true;
01078         fCtrHH->created();
01079     }
01080     else
01081     {
01082         fCtrHH->updated();
01083     }
01084     KABCSync::copy(*paddr, pcAddr, *fAddressAppInfo, fSyncSettings);
01085 
01086     DEBUGKPILOT << fname << "palmAddr->id=" << paddr->id()
01087         << ", pcAddr.ID=" << pcAddr.custom(KABCSync::appString, KABCSync::idString) << endl;
01088 
01089     if(_savePalmAddr(paddr, pcAddr))
01090     {
01091         _savePCAddr(pcAddr, backupAddr, paddr);
01092     }
01093     if (paddrcreated) KPILOT_DELETE(paddr);
01094     return true;
01095 }
01096 
01097 
01098 
01099 bool AbbrowserConduit::_copyToPC(Addressee &pcAddr, PilotAddress*backupAddr,
01100         PilotAddress*palmAddr)
01101 {
01102     FUNCTIONSETUP;
01103     if (!palmAddr)
01104     {
01105         return false;
01106     }
01107     // keep track of CUD's...
01108     if (pcAddr.isEmpty())
01109     {
01110         fCtrPC->created();
01111     }
01112     else
01113     {
01114         fCtrPC->updated();
01115     }
01116     showPilotAddress(palmAddr);
01117 
01118     KABCSync::copy(pcAddr, *palmAddr, *fAddressAppInfo, fSyncSettings);
01119     if (isArchived(palmAddr))
01120     {
01121         KABCSync::makeArchived(pcAddr);
01122     }
01123 
01124     _savePCAddr(pcAddr, backupAddr, palmAddr);
01125     _writeBackup(palmAddr);
01126     return true;
01127 }
01128 
01129 
01130 
01131 bool AbbrowserConduit::_writeBackup(PilotAddress *backup)
01132 {
01133     FUNCTIONSETUP;
01134     if (!backup) return false;
01135 
01136     showPilotAddress(backup);
01137 
01138     PilotRecord *pilotRec = backup->pack();
01139     fLocalDatabase->writeRecord(pilotRec);
01140     KPILOT_DELETE(pilotRec);
01141     return true;
01142 }
01143 
01144 
01145 
01146 bool AbbrowserConduit::_deleteAddressee(Addressee &pcAddr, PilotAddress*backupAddr,
01147         PilotAddress*palmAddr)
01148 {
01149     FUNCTIONSETUP;
01150 
01151     if (palmAddr)
01152     {
01153         if (!syncedIds.contains(palmAddr->id())) {
01154         DEBUGKPILOT << fname << ": adding id:["<< palmAddr->id() << "] to syncedIds." << endl;
01155             syncedIds.append(palmAddr->id());
01156         }
01157         fDatabase->deleteRecord(palmAddr->id());
01158         fCtrHH->deleted();
01159         fLocalDatabase->deleteRecord(palmAddr->id());
01160     }
01161     else if (backupAddr)
01162     {
01163         if (!syncedIds.contains(backupAddr->id())) {
01164         DEBUGKPILOT << fname << ": adding id:["<< backupAddr->id() << "] to syncedIds." << endl;
01165             syncedIds.append(backupAddr->id());
01166         }
01167         fLocalDatabase->deleteRecord(backupAddr->id());
01168     }
01169     if (!pcAddr.isEmpty())
01170     {
01171         DEBUGKPILOT << fname << " removing " << pcAddr.formattedName() << endl;
01172         abChanged = true;
01173         aBook->removeAddressee(pcAddr);
01174         fCtrPC->deleted();
01175     }
01176     return true;
01177 }
01178 
01179 
01180 
01181 /*********************************************************************
01182                  l o w - l e v e l   f u n c t i o n s   f o r
01183                    adding / removing palm/pc records
01184  *********************************************************************/
01185 
01186 
01187 
01188 bool AbbrowserConduit::_savePalmAddr(PilotAddress *palmAddr, Addressee &pcAddr)
01189 {
01190     FUNCTIONSETUP;
01191 
01192     DEBUGKPILOT << fname << ": Saving to pilot " << palmAddr->id()
01193         << " " << palmAddr->getField(entryFirstname)
01194         << " " << palmAddr->getField(entryLastname)<< endl;
01195 
01196     PilotRecord *pilotRec = palmAddr->pack();
01197     DEBUGKPILOT << fname << ": record with id=" << pilotRec->id()
01198         << " len=" << pilotRec->size() << endl;
01199     recordid_t pilotId = fDatabase->writeRecord(pilotRec);
01200     DEBUGKPILOT << fname << ": Wrote "<<pilotId<<": ID="<<pilotRec->id()<<endl;
01201     fLocalDatabase->writeRecord(pilotRec);
01202     KPILOT_DELETE(pilotRec);
01203 
01204     // pilotId == 0 if using local db, so don't overwrite the valid id
01205     if(pilotId != 0)
01206     {
01207         palmAddr->setID(pilotId);
01208         if (!syncedIds.contains(pilotId)) {
01209         DEBUGKPILOT << fname << ": adding id:["<< pilotId << "] to syncedIds." << endl;
01210             syncedIds.append(pilotId);
01211         }
01212     }
01213 
01214     recordid_t abId = 0;
01215     abId = pcAddr.custom(KABCSync::appString, KABCSync::idString).toUInt();
01216     if(abId != pilotId)
01217     {
01218         pcAddr.insertCustom(KABCSync::appString, KABCSync::idString, QString::number(pilotId));
01219         return true;
01220     }
01221 
01222     return false;
01223 }
01224 
01225 
01226 
01227 bool AbbrowserConduit::_savePCAddr(Addressee &pcAddr, PilotAddress*,
01228     PilotAddress*)
01229 {
01230     FUNCTIONSETUP;
01231 
01232     DEBUGKPILOT<<"Before _savePCAddr, pcAddr.custom="<<pcAddr.custom(KABCSync::appString, KABCSync::idString)<<endl;
01233     QString pilotId = pcAddr.custom(KABCSync::appString, KABCSync::idString);
01234     long pilotIdL = pilotId.toLong();
01235     if(!pilotId.isEmpty())
01236     {
01237         // because we maintain a mapping between pilotId -> kabc uid, whenever we add
01238         // a new relationship, we have to remove any old mapping that would tie a different
01239         // pilot id -> this kabc uid
01240         QMap < recordid_t, QString>::iterator it;
01241         for ( it = addresseeMap.begin(); it != addresseeMap.end(); ++it ) {
01242             QString kabcUid = it.data();
01243             if (kabcUid == pcAddr.uid()) {
01244                 addresseeMap.remove(it);
01245                 break;
01246             }
01247         }
01248 
01249         // now put the new mapping in
01250         addresseeMap.insert(pilotIdL,  pcAddr.uid());
01251     }
01252 
01253     aBook->insertAddressee(pcAddr);
01254 
01255     abChanged = true;
01256     return true;
01257 }
01258 
01259 
01260 
01261 
01262 /*********************************************************************
01263                    C O P Y   R E C O R D S
01264  *********************************************************************/
01265 
01266 
01267 
01268 bool AbbrowserConduit::_equal(const PilotAddress *piAddress, const Addressee &abEntry,
01269     enum eqFlagsType flags) const
01270 {
01271     FUNCTIONSETUP;
01272 
01273     // empty records are never equal!
01274     if (!piAddress) {
01275         DEBUGKPILOT << fname  << ": no pilot address passed" << endl;
01276         return false;
01277     }
01278     if (abEntry.isEmpty()) {
01279         DEBUGKPILOT << fname  << ":abEntry.isEmpty()" << endl;
01280         return false;
01281     }
01282     //  Archived records match anything so they won't be copied to the HH again
01283     if (flags & eqFlagsFlags)
01284         if (isArchived(piAddress) && KABCSync::isArchived(abEntry) ) return true;
01285 
01286     if (flags & eqFlagsName)
01287     {
01288         if(!_equal(abEntry.familyName(), piAddress->getField(entryLastname)))
01289         {
01290         DEBUGKPILOT << fname  << ": last name not equal" << endl;
01291             return false;
01292         }
01293         if(!_equal(abEntry.givenName(), piAddress->getField(entryFirstname)))
01294         {
01295         DEBUGKPILOT << fname  << ": first name not equal" << endl;
01296             return false;
01297         }
01298         if(!_equal(abEntry.prefix(), piAddress->getField(entryTitle)))
01299         {
01300         DEBUGKPILOT << fname  << ": title/prefix not equal" << endl;
01301             return false;
01302         }
01303         if(!_equal(abEntry.organization(), piAddress->getField(entryCompany)))
01304         {
01305         DEBUGKPILOT << fname  << ": company/organization not equal" << endl;
01306             return false;
01307         }
01308     }
01309     if (flags & eqFlagsNote)
01310         if(!_equal(abEntry.note(), piAddress->getField(entryNote)))
01311     {
01312         DEBUGKPILOT << fname  << ": note not equal" << endl;
01313             return false;
01314     }
01315 
01316     if (flags & eqFlagsCategory)
01317     {
01318         // Check that the name of the category of the HH record
01319         // is one matching the PC record.
01320         QString addressCategoryLabel = fAddressAppInfo->categoryName(piAddress->category());
01321         QString cat = KABCSync::bestMatchedCategoryName(abEntry.categories(),
01322             *fAddressAppInfo, piAddress->category());
01323         if(!_equal(cat, addressCategoryLabel))
01324         {
01325             DEBUGKPILOT << fname  << ": category not equal" << endl;
01326             return false;
01327         }
01328     }
01329 
01330     if (flags & eqFlagsPhones)
01331     {
01332         // first, look for missing e-mail addresses on either side
01333         QStringList abEmails(abEntry.emails());
01334         QStringList piEmails(piAddress->getEmails());
01335 
01336         if (abEmails.count() != piEmails.count())
01337         {
01338         DEBUGKPILOT << fname  << ": email count not equal" << endl;
01339             return false;
01340         }
01341         for (QStringList::Iterator it = abEmails.begin(); it != abEmails.end(); it++) {
01342             if (!piEmails.contains(*it))
01343             {
01344         DEBUGKPILOT << fname  << ": pilot e-mail missing" << endl;
01345             return false;
01346             }
01347         }
01348         for (QStringList::Iterator it = piEmails.begin(); it != piEmails.end(); it++) {
01349             if (!abEmails.contains(*it))
01350             {
01351         DEBUGKPILOT << fname  << ": kabc e-mail missing" << endl;
01352             return false;
01353             }
01354         }
01355 
01356         // now look for differences in phone numbers.  Note:  we can't just compare one
01357         // of each kind of phone number, because there's no guarantee that if the user
01358         // has more than one of a given type, we're comparing the correct two.
01359 
01360         PhoneNumber::List abPhones(abEntry.phoneNumbers());
01361         PhoneNumber::List piPhones = KABCSync::getPhoneNumbers(*piAddress);
01362         // first make sure that all of the pilot phone numbers are in kabc
01363         for (PhoneNumber::List::Iterator it = piPhones.begin(); it != piPhones.end(); it++) {
01364             PhoneNumber piPhone = *it;
01365             bool found=false;
01366             for (PhoneNumber::List::Iterator it = abPhones.begin(); it != abPhones.end(); it++) {
01367                 PhoneNumber abPhone = *it;
01368                 // see if we have the same number here...
01369                 // * Note * We used to check for preferred number matching, but
01370                 //     this seems to have broke in kdepim 3.5 and I don't have time to
01371                 //     figure out why, so we won't check to see if preferred number match
01372                 if ( _equal(piPhone.number(), abPhone.number()) ) {
01373                     found = true;
01374                     break;
01375                 }
01376             }
01377             if (!found) {
01378         DEBUGKPILOT << fname  << ": not equal because kabc phone not found." << endl;
01379                 return false;
01380             }
01381         }
01382         // now the other way.  *cringe*  kabc has the capacity to store way more addresses
01383         // than the Pilot, so this might give false positives more than we'd want....
01384         for (PhoneNumber::List::Iterator it = abPhones.begin(); it != abPhones.end(); it++) {
01385             PhoneNumber abPhone = *it;
01386             bool found=false;
01387             for (PhoneNumber::List::Iterator it = piPhones.begin(); it != piPhones.end(); it++) {
01388                 PhoneNumber piPhone = *it;
01389                 if ( _equal(piPhone.number(), abPhone.number()) ) {
01390                     found = true;
01391                     break;
01392                 }
01393             }
01394             if (!found)
01395             {
01396                 DEBUGKPILOT << fname  << ": not equal because pilot phone not found." << endl;
01397                 return false;
01398             }
01399         }
01400 
01401         if(!_equal(KABCSync::getFieldForHHOtherPhone(abEntry,fSyncSettings),
01402             piAddress->getPhoneField(PilotAddressInfo::eOther)))
01403         {
01404             DEBUGKPILOT << fname  << ": not equal because of other phone field." << endl;
01405             return false;
01406         }
01407     }
01408 
01409     if (flags & eqFlagsAdress)
01410     {
01411         KABC::Address address = KABCSync::getAddress(abEntry,fSyncSettings);
01412         if(!_equal(address.street(), piAddress->getField(entryAddress)))
01413         {
01414             DEBUGKPILOT << fname  << ": address not equal" << endl;
01415             return false;
01416         }
01417         if(!_equal(address.locality(), piAddress->getField(entryCity)))
01418         {
01419             DEBUGKPILOT << fname  << ": city not equal" << endl;
01420             return false;
01421         }
01422         if(!_equal(address.region(), piAddress->getField(entryState)))
01423         {
01424         DEBUGKPILOT << fname  << ": state not equal" << endl;
01425             return false;
01426         }
01427         if(!_equal(address.postalCode(), piAddress->getField(entryZip)))
01428         {
01429         DEBUGKPILOT << fname  << ": zip not equal" << endl;
01430             return false;
01431         }
01432         if(!_equal(address.country(), piAddress->getField(entryCountry)))
01433         {
01434         DEBUGKPILOT << fname  << ": country not equal" << endl;
01435             return false;
01436         }
01437     }
01438 
01439     if (flags & eqFlagsCustom)
01440     {
01441         unsigned int customIndex = 0;
01442         unsigned int hhField = entryCustom1;
01443 
01444         for ( ; customIndex<4; ++customIndex,++hhField )
01445         {
01446             if (!_equal(KABCSync::getFieldForHHCustom(customIndex, abEntry, fSyncSettings),
01447                 piAddress->getField(hhField)))
01448             {
01449                 DEBUGKPILOT << fname << ": Custom field " << customIndex
01450                     << " (HH field " << hhField << ") differs." << endl;
01451                 return false;
01452             }
01453         }
01454     }
01455 
01456     // if any side is marked archived, but the other is not, the two
01457     // are not equal.
01458     if ( (flags & eqFlagsFlags) && (isArchived(piAddress) || KABCSync::isArchived(abEntry) ) )
01459     {
01460         DEBUGKPILOT << fname  << ": archived flags don't match" << endl;
01461         return false;
01462     }
01463 
01464     return true;
01465 }
01466 
01467 
01468 
01469 
01470 
01471 
01472 
01473 
01474 
01475 
01476 /*********************************************************************
01477  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
01478  *********************************************************************/
01479 
01480 
01481 
01486 QString AbbrowserConduit::_smartMergeString(const QString &pc, const QString & backup,
01487     const QString & palm, ConflictResolution confRes)
01488 {
01489     FUNCTIONSETUP;
01490 
01491     // if both entries are already the same, no need to do anything
01492     if(pc == palm) return pc;
01493 
01494     // If this is a first sync, we don't have a backup record, so
01495     if(isFirstSync() || backup.isEmpty()) {
01496         if (pc.isEmpty() && palm.isEmpty() ) return QString::null;
01497         if(pc.isEmpty()) return palm;
01498         if(palm.isEmpty()) return pc;
01499     } else {
01500         // only one side modified, so return that string, no conflict
01501         if(palm == backup) return pc;
01502         if(pc == backup) return palm;
01503     }
01504 
01505     DEBUGKPILOT<<"pc="<<pc<<", backup="<<backup<<", palm="<<
01506         palm<<", ConfRes="<<confRes<<endl;
01507     DEBUGKPILOT<<"Use conflict resolution :"<<confRes<<
01508         ", PC="<<SyncAction::ePCOverrides<<endl;
01509     switch(confRes) {
01510         case SyncAction::ePCOverrides: return pc; break;
01511         case SyncAction::eHHOverrides: return palm; break;
01512         case SyncAction::ePreviousSyncOverrides: return backup; break;
01513         default: break;
01514     }
01515     return QString::null;
01516 }
01517 
01518 
01519 
01520 bool AbbrowserConduit::_buildResolutionTable(ResolutionTable*tab, const Addressee &pcAddr,
01521     PilotAddress *backupAddr, PilotAddress *palmAddr)
01522 {
01523     FUNCTIONSETUP;
01524     if (!tab) return false;
01525     tab->setAutoDelete( TRUE );
01526     tab->labels[0]=i18n("Item on PC");
01527     tab->labels[1]=i18n("Handheld");
01528     tab->labels[2]=i18n("Last sync");
01529     if (!pcAddr.isEmpty())
01530         tab->fExistItems=(eExistItems)(tab->fExistItems|eExistsPC);
01531     if (backupAddr)
01532         tab->fExistItems=(eExistItems)(tab->fExistItems|eExistsBackup);
01533     if (palmAddr)
01534         tab->fExistItems=(eExistItems)(tab->fExistItems|eExistsPalm);
01535 
01536 #define appendGen(desc, abfield, palmfield) \
01537     tab->append(new ResolutionItem(desc, tab->fExistItems, \
01538         (!pcAddr.isEmpty())?(abfield):(QString::null), \
01539         (palmAddr)?(palmAddr->palmfield):(QString::null), \
01540         (backupAddr)?(backupAddr->palmfield):(QString::null) ))
01541 #define appendAddr(desc, abfield, palmfield) \
01542     appendGen(desc, abfield, getField(palmfield))
01543 #define appendGenPhone(desc, abfield, palmfield) \
01544     appendGen(desc, abfield, getPhoneField(PilotAddressInfo::palmfield))
01545 #define appendPhone(desc, abfield, palmfield) \
01546     appendGenPhone(desc, pcAddr.phoneNumber(PhoneNumber::abfield).number(), palmfield)
01547 
01548 
01549     appendAddr(i18n("Last name"), pcAddr.familyName(), entryLastname);
01550     appendAddr(i18n("First name"), pcAddr.givenName(), entryFirstname);
01551     appendAddr(i18n("Organization"), pcAddr.organization(), entryCompany);
01552     appendAddr(i18n("Title"), pcAddr.prefix(), entryTitle);
01553     appendAddr(i18n("Note"), pcAddr.note(), entryNote);
01554 
01555     appendAddr(i18n("Custom 1"), KABCSync::getFieldForHHCustom(0, pcAddr, fSyncSettings), entryCustom1);
01556     appendAddr(i18n("Custom 2"), KABCSync::getFieldForHHCustom(1, pcAddr, fSyncSettings), entryCustom2);
01557     appendAddr(i18n("Custom 3"), KABCSync::getFieldForHHCustom(2, pcAddr, fSyncSettings), entryCustom3);
01558     appendAddr(i18n("Custom 4"), KABCSync::getFieldForHHCustom(3, pcAddr, fSyncSettings), entryCustom4);
01559 
01560     appendPhone(i18n("Work Phone"), Work, eWork);
01561     appendPhone(i18n("Home Phone"), Home, eHome);
01562     appendPhone(i18n("Mobile Phone"), Cell, eMobile);
01563     appendGenPhone(i18n("Fax"), pcAddr.phoneNumber(faxTypeOnPC()).number(), eFax);
01564     appendPhone(i18n("Pager"), Pager, ePager);
01565     appendGenPhone(i18n("Other"), KABCSync::getFieldForHHOtherPhone(pcAddr,fSyncSettings), eOther);
01566     appendGenPhone(i18n("Email"), pcAddr.preferredEmail(), eEmail);
01567 
01568     KABC::Address abAddress = KABCSync::getAddress(pcAddr,fSyncSettings);
01569     appendAddr(i18n("Address"), abAddress.street(), entryAddress);
01570     appendAddr(i18n("City"), abAddress.locality(), entryCity);
01571     appendAddr(i18n("Region"), abAddress.region(), entryState);
01572     appendAddr(i18n("Postal code"), abAddress.postalCode(), entryZip);
01573     appendAddr(i18n("Country"), abAddress.country(), entryCountry);
01574 
01575     QString palmAddrCategoryLabel;
01576     if (palmAddr)
01577     {
01578          palmAddrCategoryLabel = fAddressAppInfo->categoryName(palmAddr->category());
01579     }
01580     QString backupAddrCategoryLabel;
01581     if (backupAddr)
01582     {
01583         backupAddrCategoryLabel = fAddressAppInfo->categoryName(backupAddr->category());
01584     }
01585     int category = palmAddr ? palmAddr->category() : 0;
01586     tab->append(new ResolutionItem(
01587         i18n("Category"),
01588         tab->fExistItems,
01589         !pcAddr.isEmpty() ?
01590             KABCSync::bestMatchedCategoryName(pcAddr.categories(), *fAddressAppInfo, category) :
01591             QString::null,
01592         palmAddrCategoryLabel,
01593         backupAddrCategoryLabel));
01594 #undef appendGen
01595 #undef appendAddr
01596 #undef appendGenPhone
01597 #undef appendPhone
01598 
01599     return true;
01600 }
01601 
01602 
01604 static inline void setPhoneNumber(Addressee &abEntry, int type, const QString &nr)
01605 {
01606     PhoneNumber phone = abEntry.phoneNumber(type);
01607     phone.setNumber(nr);
01608     abEntry.insertPhoneNumber(phone);
01609 }
01610 
01611 
01612 bool AbbrowserConduit::_applyResolutionTable(ResolutionTable*tab, Addressee &pcAddr,
01613     PilotAddress *backupAddr, PilotAddress *palmAddr)
01614 {
01615     FUNCTIONSETUP;
01616     if (!tab) return false;
01617     if (!palmAddr) {
01618         WARNINGKPILOT << "Empty palmAddr after conflict resolution." << endl;
01619         return false;
01620     }
01621 
01622     ResolutionItem*item=tab->first();
01623 #define SETGENFIELD(abfield, palmfield) \
01624     if (item) {\
01625         abfield; \
01626         palmAddr->setField(palmfield, item->fResolved); \
01627     }\
01628     item=tab->next();
01629 #define SETFIELD(abfield, palmfield) \
01630     SETGENFIELD(pcAddr.set##abfield(item->fResolved), palmfield)
01631 #define SETCUSTOMFIELD(abfield, palmfield) \
01632     SETGENFIELD(KABCSync::setFieldFromHHCustom(abfield, pcAddr, item->fResolved, fSyncSettings), palmfield)
01633 #define SETGENPHONE(abfield, palmfield) \
01634     if (item) { \
01635         abfield; \
01636         palmAddr->setPhoneField(PilotAddressInfo::palmfield, item->fResolved, PilotAddress::Replace); \
01637     }\
01638     item=tab->next();
01639 #define SETPHONEFIELD(abfield, palmfield) \
01640     SETGENPHONE(setPhoneNumber(pcAddr, PhoneNumber::abfield, item->fResolved), palmfield)
01641 #define SETADDRESSFIELD(abfield, palmfield) \
01642     SETGENFIELD(abAddress.abfield(item->fResolved), palmfield)
01643 
01644     SETFIELD(FamilyName, entryLastname);
01645     SETFIELD(GivenName, entryFirstname);
01646     SETFIELD(Organization, entryCompany);
01647     SETFIELD(Prefix, entryTitle);
01648     SETFIELD(Note, entryNote);
01649 
01650     SETCUSTOMFIELD(0, entryCustom1);
01651     SETCUSTOMFIELD(1, entryCustom2);
01652     SETCUSTOMFIELD(2, entryCustom3);
01653     SETCUSTOMFIELD(3, entryCustom4);
01654 
01655     SETPHONEFIELD(Work, eWork);
01656     SETPHONEFIELD(Home, eHome);
01657     SETPHONEFIELD(Cell, eMobile);
01658     SETGENPHONE(setPhoneNumber(pcAddr, faxTypeOnPC(), item->fResolved), eFax);
01659     SETPHONEFIELD(Pager, ePager);
01660     SETGENPHONE(KABCSync::setFieldFromHHOtherPhone(pcAddr, item->fResolved, fSyncSettings), eOther);
01661 
01662     // TODO: fix email
01663     if (item)
01664     {
01665         palmAddr->setPhoneField(PilotAddressInfo::eEmail, item->fResolved, PilotAddress::Replace);
01666         if (backupAddr)
01667         {
01668             pcAddr.removeEmail(backupAddr->getPhoneField(PilotAddressInfo::eEmail));
01669         }
01670         pcAddr.removeEmail(palmAddr->getPhoneField(PilotAddressInfo::eEmail));
01671         pcAddr.insertEmail(item->fResolved, true);
01672     }
01673     item=tab->next();
01674 
01675     KABC::Address abAddress = KABCSync::getAddress(pcAddr, fSyncSettings);
01676     SETADDRESSFIELD(setStreet, entryAddress);
01677     SETADDRESSFIELD(setLocality, entryCity);
01678     SETADDRESSFIELD(setRegion, entryState);
01679     SETADDRESSFIELD(setPostalCode, entryZip);
01680     SETADDRESSFIELD(setCountry, entryCountry);
01681     pcAddr.insertAddress(abAddress);
01682 
01683     // TODO: Is this correct?
01684     if (item)
01685     {
01686         palmAddr->setCategory( fAddressAppInfo->findCategory(item->fResolved) );
01687         KABCSync::setCategory(pcAddr, item->fResolved);
01688     }
01689 
01690 
01691 #undef SETGENFIELD
01692 #undef SETFIELD
01693 #undef SETCUSTOMFIELD
01694 #undef SETGENPHONE
01695 #undef SETPHONEFIELD
01696 #undef SETADDRESSFIELD
01697 
01698     return true;
01699 }
01700 
01701 
01702 
01703 bool AbbrowserConduit::_smartMergeTable(ResolutionTable*tab)
01704 {
01705     FUNCTIONSETUP;
01706     if (!tab) return false;
01707     bool noconflict=true;
01708     ResolutionItem*item;
01709     for ( item = tab->first(); item; item = tab->next() )
01710     {
01711         // try to merge the three strings
01712         item->fResolved=_smartMergeString(item->fEntries[0],
01713             item->fEntries[2], item->fEntries[1], getConflictResolution());
01714         // if a conflict occurred, set the default to something sensitive:
01715         if (item->fResolved.isNull() && !(item->fEntries[0].isEmpty() &&
01716             item->fEntries[1].isEmpty() && item->fEntries[2].isEmpty() ) )
01717         {
01718             item->fResolved=item->fEntries[0];
01719             noconflict=false;
01720         }
01721         if (item->fResolved.isNull()) item->fResolved=item->fEntries[1];
01722         if (item->fResolved.isNull()) item->fResolved=item->fEntries[2];
01723     }
01724     return  noconflict;
01725 }
01726 
01727 
01728 
01733 bool AbbrowserConduit::_smartMergeAddressee(Addressee &pcAddr,
01734     PilotAddress *backupAddr, PilotAddress *palmAddr)
01735 {
01736     FUNCTIONSETUP;
01737 
01738     // Merge them, then look which records have to be written to device or abook
01739     int res = SyncAction::eAskUser;
01740     bool result=true;
01741     ResolutionTable tab;
01742 
01743     result &= _buildResolutionTable(&tab, pcAddr, backupAddr, palmAddr);
01744     // Now attempt a smart merge. If that fails, let conflict resolution do the job
01745     bool mergeOk=_smartMergeTable(&tab);
01746 
01747     if (!mergeOk)
01748     {
01749         QString dlgText;
01750         if (!palmAddr)
01751         {
01752             dlgText=i18n("The following address entry was changed, but does no longer exist on the handheld. Please resolve this conflict:");
01753         }
01754         else if (pcAddr.isEmpty())
01755         {
01756             dlgText=i18n("The following address entry was changed, but does no longer exist on the PC. Please resolve this conflict:");
01757         }
01758         else
01759         {
01760             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:");
01761         }
01762         ResolutionDlg*resdlg=new ResolutionDlg(0L, fHandle, i18n("Address conflict"), dlgText, &tab);
01763         resdlg->exec();
01764         KPILOT_DELETE(resdlg);
01765     }
01766     res=tab.fResolution;
01767 
01768     // Disallow some resolution under certain conditions, fix wrong values:
01769     switch (res) {
01770         case SyncAction::eHHOverrides:
01771             if (!palmAddr) res=SyncAction::eDelete;
01772             break;
01773         case SyncAction::ePCOverrides:
01774             if (pcAddr.isEmpty()) res=SyncAction::eDelete;
01775             break;
01776         case SyncAction::ePreviousSyncOverrides:
01777             if (!backupAddr) res=SyncAction::eDoNothing;
01778             break;
01779     }
01780 
01781     PilotAddress*pAddr=palmAddr;
01782     bool pAddrCreated=false;
01783     // Now that we have done a possible conflict resolution, apply the changes
01784     switch (res) {
01785         case SyncAction::eDuplicate:
01786             // Set the Palm ID to 0 so we don't overwrite the existing record.
01787             pcAddr.removeCustom(KABCSync::appString, KABCSync::idString);
01788             result &= _copyToHH(pcAddr, 0L, 0L);
01789             {
01790             Addressee pcadr;
01791             result &= _copyToPC(pcadr, backupAddr, palmAddr);
01792             }
01793             break;
01794         case SyncAction::eDoNothing:
01795             break;
01796         case SyncAction::eHHOverrides:
01797                 result &= _copyToPC(pcAddr, backupAddr, palmAddr);
01798                 break;
01799         case SyncAction::ePCOverrides:
01800             result &= _copyToHH(pcAddr, backupAddr, pAddr);
01801             break;
01802         case SyncAction::ePreviousSyncOverrides:
01803             KABCSync::copy(pcAddr, *backupAddr, *fAddressAppInfo, fSyncSettings);
01804             if (palmAddr && backupAddr) *palmAddr=*backupAddr;
01805             result &= _savePalmAddr(backupAddr, pcAddr);
01806             result &= _savePCAddr(pcAddr, backupAddr, backupAddr);
01807             break;
01808         case SyncAction::eDelete:
01809             result &= _deleteAddressee(pcAddr, backupAddr, palmAddr);
01810             break;
01811         case SyncAction::eAskUser:
01812         default:
01813             if (!pAddr)
01814             {
01815                 pAddr=new PilotAddress();
01816                 pAddrCreated=true;
01817             }
01818             result &= _applyResolutionTable(&tab, pcAddr, backupAddr, pAddr);
01819 showAddresses(pcAddr, backupAddr, pAddr);
01820             // savePalmAddr sets the RecordID custom field already
01821             result &= _savePalmAddr(pAddr, pcAddr);
01822             result &= _savePCAddr(pcAddr, backupAddr, pAddr);
01823             if (pAddrCreated) KPILOT_DELETE(pAddr);
01824             break;
01825     }
01826 
01827     return result;
01828 }
01829 
01830 
01831 
01832 // TODO: right now entries are equal if both first/last name and organization are
01833 //  equal. This rules out two entries for the same person(e.g. real home and weekend home)
01834 //  or two persons with the same name where you don't know the organization.!!!
01835 Addressee AbbrowserConduit::_findMatch(const PilotAddress & pilotAddress) const
01836 {
01837     FUNCTIONSETUP;
01838     // TODO: also search with the pilotID
01839     // first, use the pilotID to UID map to find the appropriate record
01840     if( !isFirstSync() && (pilotAddress.id() > 0) )
01841     {
01842         QString id(addresseeMap[pilotAddress.id()]);
01843         DEBUGKPILOT << fname << ": PilotRecord has id " << pilotAddress.id() << ", mapped to " << id << endl;
01844         if(!id.isEmpty())
01845         {
01846             Addressee res(aBook->findByUid(id));
01847             if(!res.isEmpty()) return res;
01848             DEBUGKPILOT << fname << ": PilotRecord has id " << pilotAddress.id() << ", but could not be found in the addressbook" << endl;
01849         }
01850     }
01851 
01852     for(AddressBook::Iterator iter = aBook->begin(); iter != aBook->end(); ++iter)
01853     {
01854         Addressee abEntry = *iter;
01855         QString recID(abEntry.custom(KABCSync::appString, KABCSync::idString));
01856         bool ok;
01857         if (!recID.isEmpty() )
01858         {
01859             recordid_t rid = recID.toLong(&ok);
01860             if (ok && rid)
01861             {
01862                 if (rid==pilotAddress.id()) return abEntry;// yes, we found it
01863                 // skip this addressee, as it can an other corresponding address on the handheld
01864                 if (allIds.contains(rid)) continue;
01865             }
01866         }
01867 
01868         if (_equal(&pilotAddress, abEntry, eqFlagsAlmostAll))
01869         {
01870             return abEntry;
01871         }
01872     }
01873     DEBUGKPILOT << fname << ": Could not find any addressbook enty matching " << pilotAddress.getField(entryLastname) << endl;
01874     return Addressee();
01875 }
01876 
01877 void AbbrowserConduit::slotTestRecord()
01878 {
01879     FUNCTIONSETUP;
01880 
01881     // Get a record and interpret it as an address.
01882     PilotRecord *r = fDatabase->readRecordByIndex( pilotindex );
01883     if (!r)
01884     {
01885         delayDone();
01886         return;
01887     }
01888     PilotAddress a(r);
01889     KPILOT_DELETE(r);
01890 
01891     // Process this record.
01892     showPilotAddress(&a);
01893 
01894     // Schedule more work.
01895     ++pilotindex;
01896     QTimer::singleShot(0, this, SLOT(slotTestRecord()));
01897 }