kpilot

recordConduit.cc

Go to the documentation of this file.
00001 /* KPilot
00002 **
00003 ** Copyright (C) 2004 by Reinhold Kainhofer
00004 ** Based on the addressbook conduit:
00005 ** Copyright (C) 2000,2001 by Dan Pilone
00006 ** Copyright (C) 2000 Gregory Stern
00007 ** Copyright (C) 2002-2003 by Reinhold Kainhofer
00008 **
00009 ** This conduit is the base class for all record-based conduits.
00010 ** all the sync logic is included in this class, and all child classes
00011 ** just have to implement some specific copying and conflict resolution
00012 ** methods.
00013 */
00014 
00015 /*
00016 ** This program is free software; you can redistribute it and/or modify
00017 ** it under the terms of the GNU General Public License as published by
00018 ** the Free Software Foundation; either version 2 of the License, or
00019 ** (at your option) any later version.
00020 **
00021 ** This program is distributed in the hope that it will be useful,
00022 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
00023 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00024 ** GNU General Public License for more details.
00025 **
00026 ** You should have received a copy of the GNU General Public License
00027 ** along with this program in a file called COPYING; if not, write to
00028 ** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
00029 ** MA 02110-1301, USA.
00030 */
00031 
00032 /*
00033 ** Bug reports and questions can be sent to kde-pim@kde.org.
00034 */
00035 
00036 
00037 
00038 #include "options.h"
00039 
00040 #include <qtimer.h>
00041 #include <qfile.h>
00042 
00043 #include "pilotAppCategory.h"
00044 #include "pilotSerialDatabase.h"
00045 #include "pilotLocalDatabase.h"
00046 #include "recordConduit.h"
00047 
00048 
00049 // Something to allow us to check what revision
00050 // the modules are that make up a binary distribution.
00051 //
00052 //
00053 extern "C"
00054 {
00055 long version_record_conduit = Pilot::PLUGIN_API;
00056 }
00057 
00058 
00059 /* virtual */ bool RecordConduitBase::exec()
00060 {
00061     FUNCTIONSETUP;
00062     fState = Initialize;
00063 
00064     setFirstSync(false);
00065 
00066     bool retrieved = false;
00067     if (!openDatabases( fDBName, &retrieved))
00068     {
00069         emit logError(i18n("Unable to open the %1 database on the handheld.").arg( fDBName ) );
00070         return false;
00071     }
00072     if (retrieved) setFirstSync(true);
00073 
00074     if (isFirstSync()) fIDList=fDatabase->idList();
00075     else fIDList=fDatabase->modifiedIDList();
00076     fIDListIterator = fIDList.begin();
00077 
00078     fTimer = new QTimer(this);
00079     connect(fTimer,SIGNAL(timeout()),this,SLOT(process()));
00080     fTimer->start(0,false); // Fire as often as possible to prompt processing
00081     return true;
00082 }
00083 
00084 /* virtual */ void RecordConduitBase::process()
00085 {
00086     FUNCTIONSETUP;
00087     SyncProgress p = Error;
00088 
00089 #ifdef DEBUG
00090     DEBUGKPILOT << fname << ": From state " << name(fState) << endl;
00091 #endif
00092 
00093     switch(fState)
00094     {
00095     case Initialize :
00096         p = loadPC();
00097         break;
00098     case PalmToPC :
00099         p = palmRecToPC();
00100         break;
00101     case PCToPalm :
00102         p = pcRecToPalm();
00103         break;
00104     case Cleanup :
00105         p = cleanup();
00106         break;
00107     }
00108 
00109 #ifdef DEBUG
00110     DEBUGKPILOT << fname << ": Step returned " << name(p) << endl;
00111 #endif
00112 
00113     switch(p)
00114     {
00115     case Error :
00116         fTimer->stop();
00117         delayDone();
00118         return;
00119     case NotDone :
00120         // Return so we get called again.
00121         return;
00122     case Done :
00123         // Get on with it.
00124         break;
00125     }
00126 
00127 #ifdef DEBUG
00128     DEBUGKPILOT << fname << ": Step is done, moving to next state." << endl;
00129 #endif
00130 
00131     // Here the previous call was done.
00132     switch(fState)
00133     {
00134     case Initialize :
00135         switch (syncMode().mode())
00136         {
00137         case SyncMode::eRestore :
00138         case SyncMode::eCopyPCToHH : /* These two don't copy Palm records to the PC */
00139             fState = PCToPalm;
00140             break;
00141         default :
00142             fState = PalmToPC;
00143         }
00144         break;
00145     case PalmToPC :
00146         switch (syncMode().mode())
00147         {
00148         case SyncMode::eBackup :
00149         case SyncMode::eCopyHHToPC : /* These modes don't copy PC records back */
00150             fState = Cleanup;
00151             break;
00152         default :
00153             fState = PCToPalm;
00154         }
00155         break;
00156     case PCToPalm :
00157         fState = Cleanup;
00158         break;
00159     case Cleanup :
00160         fTimer->stop();
00161         delayDone();
00162         // No change in state, timer stopped and we're done.
00163         break;
00164     }
00165 
00166 #ifdef DEBUG
00167     DEBUGKPILOT << fname << ": Next state is " << name(fState) << endl;
00168 #endif
00169 
00170 }
00171 
00172 
00173 QString RecordConduitBase::name(RecordConduitBase::SyncProgress s)
00174 {
00175     switch(s)
00176     {
00177     case RecordConduitBase::NotDone:
00178         return CSL1("NotDone");
00179     case RecordConduitBase::Done:
00180         return CSL1("Done");
00181     case RecordConduitBase::Error:
00182         return CSL1("Error");
00183     }
00184 }
00185 
00186 
00187 QString RecordConduitBase::name(RecordConduitBase::States s)
00188 {
00189     switch(s)
00190     {
00191     case RecordConduitBase::Initialize:
00192         return CSL1("Initialize");
00193     case RecordConduitBase::PalmToPC:
00194         return CSL1("Handheld-to-PC");
00195     case RecordConduitBase::PCToPalm:
00196         return CSL1("PC-to-Handheld");
00197     case RecordConduitBase::Cleanup:
00198         return CSL1("Cleanup");
00199     }
00200 }
00201 
00202 
00203 #if 0
00204 
00206 bool RecordConduit::PCData::makeArchived( RecordConduit::PCEntry *pcEntry )
00207 {
00208     if ( pcEntry ) {
00209         pcEntry->makeArchived();
00210         setChanged( true );
00211         return true;
00212     } else return false;
00213 }
00214 
00215 
00216 /* Builds the map which links record ids to uid's of PCEntry. This is the slow implementation,
00217  * that should always work. subclasses should reimplement it to speed things up.
00218 */
00219 bool RecordConduit::PCData::mapContactsToPilot( QMap<recordid_t,QString> &idContactMap )
00220 {
00221     FUNCTIONSETUP;
00222 
00223     idContactMap.clear();
00224 
00225     Iterator it = begin();
00226     PCEntry *ent;
00227     while ( !atEnd( it ) ) {
00228         ent = *it;
00229         recordid_t id( ent->recid() );
00230         if ( id != 0 ) {
00231             idContactMap.insert( id, ent->uid() );
00232         }
00233         ++it;
00234     }
00235 #ifdef DEBUG
00236     DEBUGKPILOT << fname << ": Loaded " << idContactMap.size() <<
00237         " Entries on the pc and mapped them to records on the handheld. " << endl;
00238 #endif
00239     return true;
00240 }
00241 
00242 
00243 
00244 /*********************************************************************
00245                         C O N S T R U C T O R
00246  *********************************************************************/
00247 
00248 
00249 
00250 bool RecordConduit::mArchiveDeleted = false;
00251 
00252 RecordConduit::RecordConduit(QString name, KPilotDeviceLink * o, const char *n, const QStringList & a):
00253         ConduitAction(o, n, a),
00254         mPCData(0), mPalmIndex(0),
00255         mEntryMap(), mSyncedIds(), mAllIds()
00256 {
00257     FUNCTIONSETUP;
00258     fConduitName = name;
00259 }
00260 
00261 
00262 
00263 RecordConduit::~RecordConduit()
00264 {
00265     if ( mPCData ) KPILOT_DELETE(mPCData);
00266 }
00267 
00268 
00269 
00270 
00271 
00272 
00273 /*********************************************************************
00274                 S Y N C   S T R U C T U R E
00275  *********************************************************************/
00276 
00277 
00278 
00279 /* virtual */ bool RecordConduit::exec()
00280 {
00281     FUNCTIONSETUP;
00282 
00283     if ( !_prepare() ) return false;
00284 
00285     fFirstSync = false;
00286     // Database names probably in latin1.
00287     if( !openDatabases( dbName(), &fFirstSync ) )
00288     {
00289         emit logError(i18n("Unable to open the %1 database on the handheld.").arg( dbName() ) );
00290         return false;
00291     }
00292     _getAppInfo();
00293     if( !mPCData->loadData() )
00294     {
00295         emit logError( i18n("Unable to open %1.").arg( mPCData->description() ) );
00296         return false;
00297     }
00298     // get the addresseMap which maps Pilot unique record(address) id's to
00299     // a Abbrowser Addressee; allows for easy lookup and comparisons
00300     if ( mPCData->isEmpty() )
00301         fFirstSync = true;
00302     else
00303         mPCData->mapContactsToPilot( mEntryMap );
00304     fFirstSync = fFirstSync || ( mPCData->isEmpty() );
00305 
00306     // perform syncing from palm to abbrowser
00307     // iterate through all records in palm pilot
00308     mPalmIndex = 0;
00309 
00310 #ifdef DEBUG
00311     DEBUGKPILOT << fname << ": fullsync=" << isFullSync() << ", firstSync=" << isFirstSync() << endl;
00312     DEBUGKPILOT << fname << ": "
00313         << "syncDirection=" << getSyncDirection() << ", "
00314 //      << "archive = " << AbbrowserSettings::archiveDeleted()
00315         << endl;
00316     DEBUGKPILOT << fname << ": conflictRes="<< getConflictResolution() << endl;
00317 //  DEBUGKPILOT << fname << ": PilotStreetHome=" << AbbrowserSettings::pilotStreet() << ", PilotFaxHOme" << AbbrowserSettings::pilotFax() << endl;
00318 #endif
00319 
00320     if ( !isFirstSync() )
00321         mAllIds=fDatabase->idList();
00322 
00323     /* Note:
00324        if eCopyPCToHH or eCopyHHToPC, first sync everything, then lookup
00325        those entries on the receiving side that are not yet syncced and delete
00326        them. Use slotDeleteUnsyncedPCRecords and slotDeleteUnsyncedHHRecords
00327        for this, and no longer purge the whole addressbook before the sync to
00328        prevent data loss in case of connection loss. */
00329 
00330     QTimer::singleShot(0, this, SLOT(slotPalmRecToPC()));
00331 
00332     return true;
00333 }
00334 
00335 
00336 
00337 void RecordConduit::slotPalmRecToPC()
00338 {
00339     FUNCTIONSETUP;
00340     PilotRecord *palmRec = 0L, *backupRec = 0L;
00341 
00342     if ( getSyncDirection() == SyncAction::eCopyPCToHH )
00343     {
00344         mPCIter = mPCData->begin();
00345         QTimer::singleShot(0, this, SLOT(slotPCRecToPalm()));
00346         return;
00347     }
00348 
00349     if ( isFullSync() )
00350         palmRec = fDatabase->readRecordByIndex( mPalmIndex++ );
00351     else
00352         palmRec = dynamic_cast <PilotSerialDatabase * >(fDatabase)->readNextModifiedRec();
00353 
00354     if ( !palmRec )
00355     {
00356         mPCIter = mPCData->begin();
00357         QTimer::singleShot( 0, this, SLOT( slotPCRecToPalm() ) );
00358         return;
00359     }
00360 
00361     // already synced, so skip:
00362     if ( mSyncedIds.contains( palmRec->id() ) )
00363     {
00364         KPILOT_DELETE( palmRec );
00365         QTimer::singleShot( 0, this, SLOT( slotPalmRecToPC() ) );
00366         return;
00367     }
00368 
00369     backupRec = fLocalDatabase->readRecordById( palmRec->id() );
00370     PilotRecord *compareRec = backupRec ? backupRec : palmRec;
00371     PilotAppCategory *compareEntry = createPalmEntry( compareRec );
00372     PCEntry *pcEntry = findMatch( compareEntry );
00373     KPILOT_DELETE( compareEntry );
00374 
00375     PilotAppCategory *backupEntry=0L;
00376     if ( backupRec )
00377         backupEntry = createPalmEntry( backupRec );
00378     PilotAppCategory *palmEntry=0L;
00379     if ( palmRec )
00380         palmEntry = createPalmEntry( palmRec );
00381 
00382     syncEntry( pcEntry, backupEntry, palmEntry );
00383 
00384     mSyncedIds.append( palmRec->id() );
00385 
00386     KPILOT_DELETE( pcEntry );
00387     KPILOT_DELETE( palmEntry );
00388     KPILOT_DELETE( backupEntry );
00389     KPILOT_DELETE( palmRec );
00390     KPILOT_DELETE( backupRec );
00391 
00392     QTimer::singleShot(0, this, SLOT(slotPalmRecToPC()));
00393 }
00394 
00395 
00396 
00397 void RecordConduit::slotPCRecToPalm()
00398 {
00399     FUNCTIONSETUP;
00400 
00401     if ( ( getSyncDirection()==SyncAction::eCopyHHToPC ) ||
00402         mPCData->atEnd( mPCIter ) )
00403     {
00404         mPalmIndex = 0;
00405         QTimer::singleShot( 0, this, SLOT( slotDeletedRecord() ) );
00406         return;
00407     }
00408 
00409     PilotRecord *backupRec=0L;
00410     PCEntry *pcEntry = *mPCIter;
00411     ++mPCIter;
00412 
00413     // If marked as archived, don't sync!
00414     if ( isArchived( pcEntry ) )
00415     {
00416 #ifdef DEBUG
00417         DEBUGKPILOT << fname << ": address with id " << pcEntry->uid() <<
00418             " marked archived, so don't sync." << endl;
00419 #endif
00420         KPILOT_DELETE( pcEntry );
00421         QTimer::singleShot( 0, this, SLOT( slotPCRecToPalm() ) );
00422         return;
00423     }
00424 
00425     recordid_t recID( pcEntry->recid() );
00426     if ( recID == 0 )
00427     {
00428         // it's a new item(no record ID and not inserted by the Palm -> PC sync), so add it
00429         syncEntry( pcEntry, 0L, 0L );
00430         KPILOT_DELETE( pcEntry );
00431         QTimer::singleShot( 0, this, SLOT( slotPCRecToPalm() ) );
00432         return;
00433     }
00434 
00435     // look into the list of already synced record ids to see if the PCEntry hasn't already been synced
00436     if ( mSyncedIds.contains( recID ) )
00437     {
00438 #ifdef DEBUG
00439         DEBUGKPILOT << ": address with id " << recID << " already synced." << endl;
00440 #endif
00441         KPILOT_DELETE( pcEntry );
00442         QTimer::singleShot( 0, this, SLOT( slotPCRecToPalm() ) );
00443         return;
00444     }
00445 
00446 
00447     backupRec = fLocalDatabase->readRecordById( recID );
00448     // only update if no backup record or the backup record is not equal to the PCEntry
00449 
00450     PilotAppCategory*backupEntry=0L;
00451     if ( backupRec )
00452         backupEntry = createPalmEntry( backupRec );
00453     if( !backupRec || isFirstSync() || !_equal( backupEntry, pcEntry ) )
00454     {
00455         PilotRecord *palmRec = fDatabase->readRecordById( recID );
00456         PilotAppCategory *palmEntry=0L;
00457         if (palmRec)
00458             palmEntry = createPalmEntry( palmRec );
00459         syncEntry( pcEntry, backupEntry, palmEntry );
00460         // update the id just in case it changed
00461         if ( palmRec )
00462             recID = palmRec->id();
00463         KPILOT_DELETE( palmRec );
00464         KPILOT_DELETE( palmEntry );
00465     }
00466 
00467     KPILOT_DELETE( pcEntry );
00468     KPILOT_DELETE( backupEntry );
00469     KPILOT_DELETE( backupRec );
00470     mSyncedIds.append( recID );
00471 
00472     // done with the sync process, go on with the next one:
00473     QTimer::singleShot( 0, this, SLOT( slotPCRecToPalm() ) );
00474 }
00475 
00476 
00477 
00478 void RecordConduit::slotDeletedRecord()
00479 {
00480     FUNCTIONSETUP;
00481 
00482     PilotRecord *backupRec = fLocalDatabase->readRecordByIndex( mPalmIndex++ );
00483     if( !backupRec || isFirstSync() )
00484     {
00485         KPILOT_DELETE(backupRec);
00486         QTimer::singleShot( 0, this, SLOT( slotDeleteUnsyncedPCRecords() ) );
00487         return;
00488     }
00489 
00490     // already synced, so skip this record:
00491     if ( mSyncedIds.contains( backupRec->id() ) )
00492     {
00493         KPILOT_DELETE( backupRec );
00494         QTimer::singleShot( 0, this, SLOT( slotDeletedRecord() ) );
00495         return;
00496     }
00497 
00498     QString uid = mEntryMap[ backupRec->id() ];
00499     PCEntry *pcEntry = mPCData->findByUid( uid );
00500     PilotRecord *palmRec = fDatabase->readRecordById( backupRec->id() );
00501     PilotAppCategory *backupEntry = 0L;
00502     if (backupRec)
00503         backupEntry = createPalmEntry( backupRec );
00504     PilotAppCategory*palmEntry=0L;
00505     if (palmRec)
00506         palmEntry = createPalmEntry( palmRec );
00507 
00508     mSyncedIds.append( backupRec->id() );
00509     syncEntry( pcEntry, backupEntry, palmEntry );
00510 
00511     KPILOT_DELETE( pcEntry );
00512     KPILOT_DELETE( palmEntry );
00513     KPILOT_DELETE( backupEntry );
00514     KPILOT_DELETE( palmRec );
00515     KPILOT_DELETE( backupRec );
00516     QTimer::singleShot( 0, this, SLOT( slotDeletedRecord() ) );
00517 }
00518 
00519 
00520 
00521 void RecordConduit::slotDeleteUnsyncedPCRecords()
00522 {
00523     FUNCTIONSETUP;
00524     if ( getSyncDirection() == SyncAction::eCopyHHToPC )
00525     {
00526         QStringList uids;
00527         RecordIDList::iterator it;
00528         QString uid;
00529         for ( it = mSyncedIds.begin(); it != mSyncedIds.end(); ++it)
00530         {
00531             uid = mEntryMap[ *it ];
00532             if ( !uid.isEmpty() ) uids.append( uid );
00533         }
00534         // TODO: Does this speed up anything?
00535         // qHeapSort( uids );
00536         const QStringList alluids( mPCData->uids() );
00537         QStringList::ConstIterator uidit;
00538         for ( uidit = alluids.constBegin(); uidit != alluids.constEnd(); ++uidit )
00539         {
00540             if ( !uids.contains( *uidit ) )
00541             {
00542 #ifdef DEBUG
00543                 DEBUGKPILOT << "Deleting PCEntry with uid " << (*uidit) << " from PC (is not on HH, and syncing with HH->PC direction)" << endl;
00544 #endif
00545                 mPCData->removeEntry( *uidit );
00546             }
00547         }
00548     }
00549     QTimer::singleShot(0, this, SLOT(slotDeleteUnsyncedHHRecords()));
00550 }
00551 
00552 
00553 
00554 void RecordConduit::slotDeleteUnsyncedHHRecords()
00555 {
00556     FUNCTIONSETUP;
00557     if ( getSyncDirection() == SyncAction::eCopyPCToHH )
00558     {
00559         RecordIDList ids = fDatabase->idList();
00560         RecordIDList::iterator it;
00561         for ( it = ids.begin(); it != ids.end(); ++it )
00562         {
00563             if ( !mSyncedIds.contains(*it) )
00564             {
00565 #ifdef DEBUG
00566                 DEBUGKPILOT << "Deleting record with ID " << *it << " from handheld (is not on PC, and syncing with PC->HH direction)" << endl;
00567 #endif
00568                 fDatabase->deleteRecord(*it);
00569                 fLocalDatabase->deleteRecord(*it);
00570             }
00571         }
00572     }
00573     QTimer::singleShot( 0, this, SLOT( slotCleanup() ) );
00574 }
00575 
00576 
00577 void RecordConduit::slotCleanup()
00578 {
00579     FUNCTIONSETUP;
00580 
00581     // Set the appInfoBlock, just in case the category labels changed
00582     _setAppInfo();
00583     doPostSync();
00584     if(fDatabase)
00585     {
00586         fDatabase->resetSyncFlags();
00587         fDatabase->cleanup();
00588     }
00589     if(fLocalDatabase)
00590     {
00591         fLocalDatabase->resetSyncFlags();
00592         fLocalDatabase->cleanup();
00593     }
00594     KPILOT_DELETE( fDatabase );
00595     KPILOT_DELETE( fLocalDatabase );
00596     // TODO: do something if saving fails!
00597     mPCData->saveData();
00598     mPCData->cleanup();
00599     emit syncDone(this);
00600 }
00601 
00602 
00605 const QStringList RecordConduit::categories() const
00606 {
00607     QStringList cats;
00608     for ( unsigned int j = 0; j < Pilot::CATEGORY_COUNT; j++ ) {
00609         QString catName( category( j ) );
00610         if ( !catName.isEmpty() ) cats << catName;
00611     }
00612     return cats;
00613 }
00614 int RecordConduit::findFlags() const
00615 {
00616     return eqFlagsAlmostAll;
00617 }
00618 
00619 
00620 bool RecordConduit::isDeleted( const PilotAppCategory *palmEntry )
00621 {
00622     if ( !palmEntry )
00623         return true;
00624     if ( palmEntry->isDeleted() && !palmEntry->isArchived() )
00625         return true;
00626     if ( palmEntry->isArchived() )
00627         return !archiveDeleted();
00628     return false;
00629 }
00630 bool RecordConduit::isArchived( const PilotAppCategory *palmEntry )
00631 {
00632     if ( palmEntry && palmEntry->isArchived() )
00633         return archiveDeleted();
00634     else
00635         return false;
00636 }
00637 
00638 
00639 
00640 
00641 /*********************************************************************
00642                 L O A D I N G   T H E   D A T A
00643  *********************************************************************/
00644 
00645 
00646 
00647 bool RecordConduit::_prepare()
00648 {
00649     FUNCTIONSETUP;
00650 
00651     readConfig();
00652     mSyncedIds.clear();
00653     mPCData = initializePCData();
00654 
00655     return mPCData && doPrepare();
00656 }
00657 
00658 
00659 void RecordConduit::_getAppInfo()
00660 {
00661     FUNCTIONSETUP;
00662     // get the address application header information
00663     unsigned char *buffer = new unsigned char[Pilot::MAX_APPINFO_SIZE];
00664     int appLen=fDatabase->readAppBlock(buffer, Pilot::MAX_APPINFO_SIZE);
00665 
00666     doUnpackAppInfo( buffer, appLen );
00667     delete[] buffer;
00668     buffer = 0;
00669 }
00670 
00671 void RecordConduit::_setAppInfo()
00672 {
00673     FUNCTIONSETUP;
00674     // get the address application header information
00675     int appLen = 0;
00676     unsigned char *buffer = doPackAppInfo( &appLen );
00677     if ( buffer )
00678     {   if (fDatabase)
00679             fDatabase->writeAppBlock( buffer, appLen );
00680         if (fLocalDatabase)
00681             fLocalDatabase->writeAppBlock( buffer, appLen );
00682         delete[] buffer;
00683     }
00684 }
00685 
00686 
00687 int RecordConduit::compareStr( const QString & str1, const QString & str2 )
00688 {
00689 //  FUNCTIONSETUP;
00690     if ( str1.isEmpty() && str2.isEmpty() )
00691         return 0;
00692     else
00693         return str1.compare( str2 );
00694 }
00695 
00696 
00704 QString RecordConduit::getCatForHH( const QStringList cats, const QString curr ) const
00705 {
00706     FUNCTIONSETUP;
00707     if ( cats.size() < 1 )
00708         return QString::null;
00709     if ( cats.contains( curr ) )
00710         return curr;
00711     for ( QStringList::ConstIterator it = cats.begin(); it != cats.end(); ++it)
00712     {
00713         for ( unsigned int j = 0; j < Pilot::CATEGORY_COUNT; j++ )
00714         {
00715             QString catnm( category( j ) );
00716             if ( !(*it).isEmpty() && ( (*it)==catnm ) )
00717             {
00718                 return catnm;
00719             }
00720         }
00721     }
00722     // If we have a free label, return the first possible cat
00723     QString lastCat( category( Pilot::CATEGORY_COUNT-1 ) );
00724     return ( lastCat.isEmpty() ) ? ( cats.first() ) : ( QString::null );
00725 }
00726 
00727 void RecordConduit::setCategory(PCEntry * pcEntry, QString cat)
00728 {
00729     if ( !cat.isEmpty() && cat!=category( 0 ) )
00730         pcEntry->insertCategory(cat);
00731 }
00732 
00733 
00734 
00735 
00736 
00737 
00738 /*********************************************************************
00739               G E N E R A L   S Y N C   F U N C T I O N
00740          These functions modify the Handheld and the addressbook
00741  *********************************************************************/
00742 
00743 
00744 
00745 bool RecordConduit::syncEntry( PCEntry *pcEntry, PilotAppCategory*backupEntry,
00746     PilotAppCategory*palmEntry)
00747 {
00748     FUNCTIONSETUP;
00749 
00750     if ( getSyncDirection() == SyncAction::eCopyPCToHH )
00751     {
00752         if ( pcEntry->isEmpty() )
00753         {
00754             return pcDeleteEntry( pcEntry, backupEntry, palmEntry );
00755         }
00756         else
00757         {
00758             return pcCopyToPalm( pcEntry, backupEntry, palmEntry );
00759         }
00760     }
00761 
00762     if ( getSyncDirection() == SyncAction::eCopyHHToPC )
00763     {
00764         if (!palmEntry)
00765             return pcDeleteEntry(pcEntry, backupEntry, palmEntry);
00766         else
00767             return palmCopyToPC(pcEntry, backupEntry, palmEntry);
00768     }
00769 
00770     if ( !backupEntry || isFirstSync() )
00771     {
00772         /*
00773         Resolution matrix (0..does not exist, E..exists, D..deleted flag set, A..archived):
00774           HH    PC  | Resolution
00775           ------------------------------------------------------------
00776            0     A  |  -
00777            0     E  |  PC -> HH, reset ID if not set correctly
00778            D     0  |  delete (error, should never occur!!!)
00779            D     E  |  CR (ERROR)
00780            E/A   0  |  HH -> PC
00781            E/A   E/A|  merge/CR
00782          */
00783         if  ( !palmEntry && isArchived( pcEntry ) )
00784         {
00785             return true;
00786         }
00787         else if ( !palmEntry && !pcEntry->isEmpty() )
00788         {
00789             // PC->HH
00790             bool res = pcCopyToPalm( pcEntry, 0L, 0L );
00791             return res;
00792         }
00793         else if ( !palmEntry && pcEntry->isEmpty() )
00794         {
00795             // everything's empty -> ERROR
00796             return false;
00797         }
00798         else if ( ( isDeleted( palmEntry ) || isArchived( palmEntry ) ) && pcEntry->isEmpty())
00799         {
00800             if ( isArchived( palmEntry ) )
00801                 return palmCopyToPC( pcEntry, 0L, palmEntry );
00802             else
00803                 // this happens if you add a record on the handheld and delete it again before you do the next sync
00804                 return pcDeleteEntry( pcEntry, 0L, palmEntry );
00805         }
00806         else if ( ( isDeleted(palmEntry) || isArchived( palmEntry ) ) && !pcEntry->isEmpty() )
00807         {
00808             // CR (ERROR)
00809             return smartMergeEntry( pcEntry, 0L, palmEntry );
00810         }
00811         else if ( pcEntry->isEmpty() )
00812         {
00813             // HH->PC
00814             return palmCopyToPC( pcEntry, 0L, palmEntry );
00815         }
00816         else
00817         {
00818             // Conflict Resolution
00819             return smartMergeEntry( pcEntry, 0L, palmEntry );
00820         }
00821     } // !backupEntry
00822     else
00823     {
00824         /*
00825         Resolution matrix:
00826           1) if HH.(empty| (deleted &! archived) ) -> { if (PC==B) -> delete, else -> CR }
00827              if HH.archived -> {if (PC==B) -> copyToPC, else -> CR }
00828              if PC.empty -> { if (HH==B) -> delete, else -> CR }
00829              if PC.archived -> {if (HH==B) -> delete on HH, else CR }
00830           2) if PC==HH -> { update B, update ID of PC if needed }
00831           3) if PC==B -> { HH!=PC, thus HH modified, so copy HH->PC }
00832              if HH==B -> { PC!=HH, thus PC modified, so copy PC->HH }
00833           4) else: all three PCEntrys are different -> CR
00834         */
00835 
00836         if ( !palmEntry || isDeleted(palmEntry) )
00837         {
00838             if ( _equal( backupEntry, pcEntry ) || pcEntry->isEmpty() )
00839             {
00840                 return pcDeleteEntry( pcEntry, backupEntry, 0L );
00841             }
00842             else
00843             {
00844                 return smartMergeEntry( pcEntry, backupEntry, 0L );
00845             }
00846         }
00847         else if ( pcEntry->isEmpty() )
00848         {
00849             if (*palmEntry == *backupEntry)
00850             {
00851                 return pcDeleteEntry( pcEntry, backupEntry, palmEntry );
00852             }
00853             else
00854             {
00855                 return smartMergeEntry( pcEntry, backupEntry, palmEntry );
00856             }
00857         }
00858         else if ( _equal( palmEntry, pcEntry ) )
00859         {
00860             // update Backup, update ID of PC if neededd
00861             return backupSaveEntry( palmEntry );
00862         }
00863         else if ( _equal( backupEntry, pcEntry ) )
00864         {
00865 #ifdef DEBUG
00866             DEBUGKPILOT << "Flags: " << palmEntry->getAttrib() << ", isDeleted=" <<
00867                 isDeleted( palmEntry ) << ", isArchived=" << isArchived( palmEntry )
00868                 << endl;
00869 #endif
00870             if ( isDeleted( palmEntry ) )
00871             {
00872                 return pcDeleteEntry( pcEntry, backupEntry, palmEntry );
00873             }
00874             else
00875             {
00876                 return palmCopyToPC( pcEntry, backupEntry, palmEntry );
00877             }
00878         }
00879         else if ( *palmEntry == *backupEntry )
00880         {
00881             return pcCopyToPalm( pcEntry, backupEntry, palmEntry );
00882         }
00883         else
00884         {
00885             // CR, since all are different
00886             return smartMergeEntry( pcEntry, backupEntry, palmEntry );
00887         }
00888     } // backupEntry
00889     return false;
00890 }
00891 
00892 bool RecordConduit::pcCopyToPalm( PCEntry *pcEntry, PilotAppCategory *backupEntry,
00893         PilotAppCategory*palmEntry )
00894 {
00895     FUNCTIONSETUP;
00896 
00897     if ( pcEntry->isEmpty() ) return false;
00898     PilotAppCategory *hhEntry = palmEntry;
00899     bool hhEntryCreated = false;
00900     if ( !hhEntry )
00901     {
00902         hhEntry = createPalmEntry( 0 );
00903         hhEntryCreated=true;
00904     }
00905     _copy( hhEntry, pcEntry );
00906 #ifdef DEBUG
00907     DEBUGKPILOT << "palmEntry->id=" << hhEntry->id() << ", pcEntry.ID=" <<
00908         pcEntry->uid() << endl;
00909 #endif
00910 
00911     if( palmSaveEntry( hhEntry, pcEntry ) )
00912     {
00913 #ifdef DEBUG
00914         DEBUGKPILOT << "Entry palmEntry->id=" <<
00915         hhEntry->id() << "saved to palm, now updating pcEntry->uid()=" << pcEntry->uid() << endl;
00916 #endif
00917         pcSaveEntry( pcEntry, backupEntry, hhEntry );
00918     }
00919     if ( hhEntryCreated ) KPILOT_DELETE( hhEntry );
00920     return true;
00921 }
00922 
00923 
00924 
00925 
00926 bool RecordConduit::palmCopyToPC( PCEntry *pcEntry, PilotAppCategory *backupEntry,
00927         PilotAppCategory *palmEntry )
00928 {
00929     FUNCTIONSETUP;
00930     if ( !palmEntry )
00931     {
00932         return false;
00933     }
00934     _copy( pcEntry, palmEntry );
00935     pcSaveEntry( pcEntry, backupEntry, palmEntry );
00936     backupSaveEntry( palmEntry );
00937     return true;
00938 }
00939 
00940 
00941 
00942 /*********************************************************************
00943                  l o w - l e v e l   f u n c t i o n s   f o r
00944                    adding / removing palm/pc records
00945  *********************************************************************/
00946 
00947 
00948 
00949 bool RecordConduit::palmSaveEntry( PilotAppCategory *palmEntry, PCEntry *pcEntry )
00950 {
00951     FUNCTIONSETUP;
00952 
00953 #ifdef DEBUG
00954     DEBUGKPILOT << "Saving to pilot " << palmEntry->id() << endl;
00955 #endif
00956 
00957     PilotRecord *pilotRec = palmEntry->pack();
00958     recordid_t pilotId = fDatabase->writeRecord(pilotRec);
00959 #ifdef DEBUG
00960     DEBUGKPILOT << "PilotRec nach writeRecord (" << pilotId <<
00961         ": ID=" << pilotRec->id() << endl;
00962 #endif
00963     fLocalDatabase->writeRecord( pilotRec );
00964     KPILOT_DELETE( pilotRec );
00965 
00966     // pilotId == 0 if using local db, so don't overwrite the valid id
00967     if ( pilotId != 0 )
00968     {
00969         palmEntry->setID( pilotId );
00970         if ( !mSyncedIds.contains( pilotId ) )
00971         {
00972             mSyncedIds.append( pilotId );
00973         }
00974     }
00975 
00976     recordid_t hhId( pcEntry->recid() );
00977     if ( hhId != pilotId )
00978     {
00979         pcEntry->setRecid( pilotId );
00980         return true;
00981     }
00982 
00983     return false;
00984 }
00985 
00986 
00987 
00988 bool RecordConduit::backupSaveEntry( PilotAppCategory *backup )
00989 {
00990     FUNCTIONSETUP;
00991     if ( !backup ) return false;
00992 
00993 
00994 #ifdef DEBUG
00995 //  showPilotAppCategory( backup );
00996 #endif
00997     PilotRecord *pilotRec = backup->pack();
00998     fLocalDatabase->writeRecord( pilotRec );
00999     KPILOT_DELETE( pilotRec );
01000     return true;
01001 }
01002 
01003 
01004 
01005 bool RecordConduit::pcSaveEntry( PCEntry *pcEntry, PilotAppCategory *,
01006     PilotAppCategory * )
01007 {
01008     FUNCTIONSETUP;
01009 
01010 #ifdef DEBUG
01011     DEBUGKPILOT << "Before _savepcEntry, pcEntry->uid()=" <<
01012         pcEntry->uid() << endl;
01013 #endif
01014     if ( pcEntry->recid() != 0 )
01015     {
01016         mEntryMap.insert( pcEntry->recid(), pcEntry->uid() );
01017     }
01018 
01019     mPCData->updateEntry( pcEntry );
01020     return true;
01021 }
01022 
01023 
01024 
01025 bool RecordConduit::pcDeleteEntry( PCEntry *pcEntry, PilotAppCategory *backupEntry,
01026     PilotAppCategory *palmEntry )
01027 {
01028     FUNCTIONSETUP;
01029 
01030     if ( palmEntry )
01031     {
01032         if ( !mSyncedIds.contains( palmEntry->id() ) )
01033         {
01034             mSyncedIds.append(palmEntry->id());
01035         }
01036         palmEntry->makeDeleted();
01037         PilotRecord *pilotRec = palmEntry->pack();
01038         pilotRec->setDeleted();
01039         mPalmIndex--;
01040         fDatabase->writeRecord( pilotRec );
01041         fLocalDatabase->writeRecord( pilotRec );
01042         mSyncedIds.append( pilotRec->id() );
01043         KPILOT_DELETE( pilotRec );
01044     }
01045     else if ( backupEntry )
01046     {
01047         if ( !mSyncedIds.contains( backupEntry->id() ) )
01048         {
01049             mSyncedIds.append( backupEntry->id() );
01050         }
01051         backupEntry->makeDeleted();
01052         PilotRecord *pilotRec = backupEntry->pack();
01053         pilotRec->setDeleted();
01054         mPalmIndex--;
01055         fLocalDatabase->writeRecord( pilotRec );
01056         mSyncedIds.append( pilotRec->id() );
01057         KPILOT_DELETE( pilotRec );
01058     }
01059     if ( !pcEntry->isEmpty() )
01060     {
01061 #ifdef DEBUG
01062         DEBUGKPILOT << fname << " removing " << pcEntry->uid() << endl;
01063 #endif
01064         mPCData->removeEntry( pcEntry );
01065     }
01066     return true;
01067 }
01068 
01069 
01070 
01071 /*********************************************************************
01072                    C O P Y   R E C O R D S
01073  *********************************************************************/
01074 
01075 
01076 
01077 
01078 
01079 /*********************************************************************
01080  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
01081  *********************************************************************/
01082 
01083 
01084 
01085 
01086 // TODO: right now entries are equal if both first/last name and organization are
01087 //  equal. This rules out two entries for the same person(e.g. real home and weekend home)
01088 //  or two persons with the same name where you don't know the organization.!!!
01089 RecordConduit::PCEntry *RecordConduit::findMatch( PilotAppCategory *palmEntry ) const
01090 {
01091     FUNCTIONSETUP;
01092     if ( !palmEntry )
01093         return 0;
01094 
01095     // TODO: also search with the pilotID
01096     // first, use the pilotID to UID map to find the appropriate record
01097     if( !isFirstSync() && ( palmEntry->id() > 0) )
01098     {
01099         QString id( mEntryMap[palmEntry->id()] );
01100 #ifdef DEBUG
01101         DEBUGKPILOT << fname << ": PilotRecord has id " << palmEntry->id() << ", mapped to " << id << endl;
01102 #endif
01103         if( !id.isEmpty() )
01104         {
01105             PCEntry *res = mPCData->findByUid( id );
01106             if ( !res && !res->isEmpty() ) return res;
01107             KPILOT_DELETE( res );
01108 #ifdef DEBUG
01109             DEBUGKPILOT << fname << ": PilotRecord has id " << palmEntry->id() <<
01110                 ", but could not be found on the PC side" << endl;
01111 #endif
01112         }
01113     }
01114 
01115     for ( PCData::Iterator iter = mPCData->begin(); !mPCData->atEnd( iter ); ++iter )
01116     {
01117         PCEntry *abEntry = *iter;
01118         recordid_t rid( abEntry->recid() );
01119         if ( rid>0 )
01120         {
01121             if ( rid == palmEntry->id() )
01122                 return abEntry;// yes, we found it
01123             // skip this PCEntry, as it has a different corresponding address on the handheld
01124             //if ( mAllIds.contains( rid ) ) continue;
01125         }
01126 
01127         if ( _equal( palmEntry, abEntry, eqFlagsAlmostAll ) )
01128         {
01129             return abEntry;
01130         }
01131         KPILOT_DELETE( abEntry );
01132     }
01133 #ifdef DEBUG
01134     DEBUGKPILOT << fname << ": Could not find any entry matching Palm record with id " << QString::number( palmEntry->id() ) << endl;
01135 #endif
01136     return 0;
01137 }
01138 
01139 #endif
01140 
01141 
01142 
01143 
01144 #include "recordConduit.moc"
01145