summaryrefslogtreecommitdiffstats
path: root/kpilot/lib/recordConduit.cc
diff options
context:
space:
mode:
Diffstat (limited to 'kpilot/lib/recordConduit.cc')
-rw-r--r--kpilot/lib/recordConduit.cc1145
1 files changed, 1145 insertions, 0 deletions
diff --git a/kpilot/lib/recordConduit.cc b/kpilot/lib/recordConduit.cc
new file mode 100644
index 000000000..f11b3b573
--- /dev/null
+++ b/kpilot/lib/recordConduit.cc
@@ -0,0 +1,1145 @@
+/* KPilot
+**
+** Copyright (C) 2004 by Reinhold Kainhofer
+** Based on the addressbook conduit:
+** Copyright (C) 2000,2001 by Dan Pilone
+** Copyright (C) 2000 Gregory Stern
+** Copyright (C) 2002-2003 by Reinhold Kainhofer
+**
+** This conduit is the base class for all record-based conduits.
+** all the sync logic is included in this class, and all child classes
+** just have to implement some specific copying and conflict resolution
+** methods.
+*/
+
+/*
+** This program is free software; you can redistribute it and/or modify
+** it under the terms of the GNU General Public License as published by
+** the Free Software Foundation; either version 2 of the License, or
+** (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU General Public License for more details.
+**
+** You should have received a copy of the GNU General Public License
+** along with this program in a file called COPYING; if not, write to
+** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+** MA 02110-1301, USA.
+*/
+
+/*
+** Bug reports and questions can be sent to kde-pim@kde.org.
+*/
+
+
+
+#include "options.h"
+
+#include <qtimer.h>
+#include <qfile.h>
+
+#include "pilotAppCategory.h"
+#include "pilotSerialDatabase.h"
+#include "pilotLocalDatabase.h"
+#include "recordConduit.h"
+
+
+// Something to allow us to check what revision
+// the modules are that make up a binary distribution.
+//
+//
+extern "C"
+{
+long version_record_conduit = Pilot::PLUGIN_API;
+}
+
+
+/* virtual */ bool RecordConduitBase::exec()
+{
+ FUNCTIONSETUP;
+ fState = Initialize;
+
+ setFirstSync(false);
+
+ bool retrieved = false;
+ if (!openDatabases( fDBName, &retrieved))
+ {
+ emit logError(i18n("Unable to open the %1 database on the handheld.").arg( fDBName ) );
+ return false;
+ }
+ if (retrieved) setFirstSync(true);
+
+ if (isFirstSync()) fIDList=fDatabase->idList();
+ else fIDList=fDatabase->modifiedIDList();
+ fIDListIterator = fIDList.begin();
+
+ fTimer = new QTimer(this);
+ connect(fTimer,SIGNAL(timeout()),this,SLOT(process()));
+ fTimer->start(0,false); // Fire as often as possible to prompt processing
+ return true;
+}
+
+/* virtual */ void RecordConduitBase::process()
+{
+ FUNCTIONSETUP;
+ SyncProgress p = Error;
+
+#ifdef DEBUG
+ DEBUGKPILOT << fname << ": From state " << name(fState) << endl;
+#endif
+
+ switch(fState)
+ {
+ case Initialize :
+ p = loadPC();
+ break;
+ case PalmToPC :
+ p = palmRecToPC();
+ break;
+ case PCToPalm :
+ p = pcRecToPalm();
+ break;
+ case Cleanup :
+ p = cleanup();
+ break;
+ }
+
+#ifdef DEBUG
+ DEBUGKPILOT << fname << ": Step returned " << name(p) << endl;
+#endif
+
+ switch(p)
+ {
+ case Error :
+ fTimer->stop();
+ delayDone();
+ return;
+ case NotDone :
+ // Return so we get called again.
+ return;
+ case Done :
+ // Get on with it.
+ break;
+ }
+
+#ifdef DEBUG
+ DEBUGKPILOT << fname << ": Step is done, moving to next state." << endl;
+#endif
+
+ // Here the previous call was done.
+ switch(fState)
+ {
+ case Initialize :
+ switch (syncMode().mode())
+ {
+ case SyncMode::eRestore :
+ case SyncMode::eCopyPCToHH : /* These two don't copy Palm records to the PC */
+ fState = PCToPalm;
+ break;
+ default :
+ fState = PalmToPC;
+ }
+ break;
+ case PalmToPC :
+ switch (syncMode().mode())
+ {
+ case SyncMode::eBackup :
+ case SyncMode::eCopyHHToPC : /* These modes don't copy PC records back */
+ fState = Cleanup;
+ break;
+ default :
+ fState = PCToPalm;
+ }
+ break;
+ case PCToPalm :
+ fState = Cleanup;
+ break;
+ case Cleanup :
+ fTimer->stop();
+ delayDone();
+ // No change in state, timer stopped and we're done.
+ break;
+ }
+
+#ifdef DEBUG
+ DEBUGKPILOT << fname << ": Next state is " << name(fState) << endl;
+#endif
+
+}
+
+
+QString RecordConduitBase::name(RecordConduitBase::SyncProgress s)
+{
+ switch(s)
+ {
+ case RecordConduitBase::NotDone:
+ return CSL1("NotDone");
+ case RecordConduitBase::Done:
+ return CSL1("Done");
+ case RecordConduitBase::Error:
+ return CSL1("Error");
+ }
+}
+
+
+QString RecordConduitBase::name(RecordConduitBase::States s)
+{
+ switch(s)
+ {
+ case RecordConduitBase::Initialize:
+ return CSL1("Initialize");
+ case RecordConduitBase::PalmToPC:
+ return CSL1("Handheld-to-PC");
+ case RecordConduitBase::PCToPalm:
+ return CSL1("PC-to-Handheld");
+ case RecordConduitBase::Cleanup:
+ return CSL1("Cleanup");
+ }
+}
+
+
+#if 0
+/** make that entry on the pc archived (i.e. deleted on the handheld,
+ * while present on the pc, but not synced to the handheld */
+bool RecordConduit::PCData::makeArchived( RecordConduit::PCEntry *pcEntry )
+{
+ if ( pcEntry ) {
+ pcEntry->makeArchived();
+ setChanged( true );
+ return true;
+ } else return false;
+}
+
+
+/* Builds the map which links record ids to uid's of PCEntry. This is the slow implementation,
+ * that should always work. subclasses should reimplement it to speed things up.
+*/
+bool RecordConduit::PCData::mapContactsToPilot( QMap<recordid_t,QString> &idContactMap )
+{
+ FUNCTIONSETUP;
+
+ idContactMap.clear();
+
+ Iterator it = begin();
+ PCEntry *ent;
+ while ( !atEnd( it ) ) {
+ ent = *it;
+ recordid_t id( ent->recid() );
+ if ( id != 0 ) {
+ idContactMap.insert( id, ent->uid() );
+ }
+ ++it;
+ }
+#ifdef DEBUG
+ DEBUGKPILOT << fname << ": Loaded " << idContactMap.size() <<
+ " Entries on the pc and mapped them to records on the handheld. " << endl;
+#endif
+ return true;
+}
+
+
+
+/*********************************************************************
+ C O N S T R U C T O R
+ *********************************************************************/
+
+
+
+bool RecordConduit::mArchiveDeleted = false;
+
+RecordConduit::RecordConduit(QString name, KPilotDeviceLink * o, const char *n, const QStringList & a):
+ ConduitAction(o, n, a),
+ mPCData(0), mPalmIndex(0),
+ mEntryMap(), mSyncedIds(), mAllIds()
+{
+ FUNCTIONSETUP;
+ fConduitName = name;
+}
+
+
+
+RecordConduit::~RecordConduit()
+{
+ if ( mPCData ) KPILOT_DELETE(mPCData);
+}
+
+
+
+
+
+
+/*********************************************************************
+ S Y N C S T R U C T U R E
+ *********************************************************************/
+
+
+
+/* virtual */ bool RecordConduit::exec()
+{
+ FUNCTIONSETUP;
+
+ if ( !_prepare() ) return false;
+
+ fFirstSync = false;
+ // Database names probably in latin1.
+ if( !openDatabases( dbName(), &fFirstSync ) )
+ {
+ emit logError(i18n("Unable to open the %1 database on the handheld.").arg( dbName() ) );
+ return false;
+ }
+ _getAppInfo();
+ if( !mPCData->loadData() )
+ {
+ emit logError( i18n("Unable to open %1.").arg( mPCData->description() ) );
+ return false;
+ }
+ // get the addresseMap which maps Pilot unique record(address) id's to
+ // a Abbrowser Addressee; allows for easy lookup and comparisons
+ if ( mPCData->isEmpty() )
+ fFirstSync = true;
+ else
+ mPCData->mapContactsToPilot( mEntryMap );
+ fFirstSync = fFirstSync || ( mPCData->isEmpty() );
+
+ // perform syncing from palm to abbrowser
+ // iterate through all records in palm pilot
+ mPalmIndex = 0;
+
+#ifdef DEBUG
+ DEBUGKPILOT << fname << ": fullsync=" << isFullSync() << ", firstSync=" << isFirstSync() << endl;
+ DEBUGKPILOT << fname << ": "
+ << "syncDirection=" << getSyncDirection() << ", "
+// << "archive = " << AbbrowserSettings::archiveDeleted()
+ << endl;
+ DEBUGKPILOT << fname << ": conflictRes="<< getConflictResolution() << endl;
+// DEBUGKPILOT << fname << ": PilotStreetHome=" << AbbrowserSettings::pilotStreet() << ", PilotFaxHOme" << AbbrowserSettings::pilotFax() << endl;
+#endif
+
+ if ( !isFirstSync() )
+ mAllIds=fDatabase->idList();
+
+ /* Note:
+ if eCopyPCToHH or eCopyHHToPC, first sync everything, then lookup
+ those entries on the receiving side that are not yet syncced and delete
+ them. Use slotDeleteUnsyncedPCRecords and slotDeleteUnsyncedHHRecords
+ for this, and no longer purge the whole addressbook before the sync to
+ prevent data loss in case of connection loss. */
+
+ QTimer::singleShot(0, this, SLOT(slotPalmRecToPC()));
+
+ return true;
+}
+
+
+
+void RecordConduit::slotPalmRecToPC()
+{
+ FUNCTIONSETUP;
+ PilotRecord *palmRec = 0L, *backupRec = 0L;
+
+ if ( getSyncDirection() == SyncAction::eCopyPCToHH )
+ {
+ mPCIter = mPCData->begin();
+ QTimer::singleShot(0, this, SLOT(slotPCRecToPalm()));
+ return;
+ }
+
+ if ( isFullSync() )
+ palmRec = fDatabase->readRecordByIndex( mPalmIndex++ );
+ else
+ palmRec = dynamic_cast <PilotSerialDatabase * >(fDatabase)->readNextModifiedRec();
+
+ if ( !palmRec )
+ {
+ mPCIter = mPCData->begin();
+ QTimer::singleShot( 0, this, SLOT( slotPCRecToPalm() ) );
+ return;
+ }
+
+ // already synced, so skip:
+ if ( mSyncedIds.contains( palmRec->id() ) )
+ {
+ KPILOT_DELETE( palmRec );
+ QTimer::singleShot( 0, this, SLOT( slotPalmRecToPC() ) );
+ return;
+ }
+
+ backupRec = fLocalDatabase->readRecordById( palmRec->id() );
+ PilotRecord *compareRec = backupRec ? backupRec : palmRec;
+ PilotAppCategory *compareEntry = createPalmEntry( compareRec );
+ PCEntry *pcEntry = findMatch( compareEntry );
+ KPILOT_DELETE( compareEntry );
+
+ PilotAppCategory *backupEntry=0L;
+ if ( backupRec )
+ backupEntry = createPalmEntry( backupRec );
+ PilotAppCategory *palmEntry=0L;
+ if ( palmRec )
+ palmEntry = createPalmEntry( palmRec );
+
+ syncEntry( pcEntry, backupEntry, palmEntry );
+
+ mSyncedIds.append( palmRec->id() );
+
+ KPILOT_DELETE( pcEntry );
+ KPILOT_DELETE( palmEntry );
+ KPILOT_DELETE( backupEntry );
+ KPILOT_DELETE( palmRec );
+ KPILOT_DELETE( backupRec );
+
+ QTimer::singleShot(0, this, SLOT(slotPalmRecToPC()));
+}
+
+
+
+void RecordConduit::slotPCRecToPalm()
+{
+ FUNCTIONSETUP;
+
+ if ( ( getSyncDirection()==SyncAction::eCopyHHToPC ) ||
+ mPCData->atEnd( mPCIter ) )
+ {
+ mPalmIndex = 0;
+ QTimer::singleShot( 0, this, SLOT( slotDeletedRecord() ) );
+ return;
+ }
+
+ PilotRecord *backupRec=0L;
+ PCEntry *pcEntry = *mPCIter;
+ ++mPCIter;
+
+ // If marked as archived, don't sync!
+ if ( isArchived( pcEntry ) )
+ {
+#ifdef DEBUG
+ DEBUGKPILOT << fname << ": address with id " << pcEntry->uid() <<
+ " marked archived, so don't sync." << endl;
+#endif
+ KPILOT_DELETE( pcEntry );
+ QTimer::singleShot( 0, this, SLOT( slotPCRecToPalm() ) );
+ return;
+ }
+
+ recordid_t recID( pcEntry->recid() );
+ if ( recID == 0 )
+ {
+ // it's a new item(no record ID and not inserted by the Palm -> PC sync), so add it
+ syncEntry( pcEntry, 0L, 0L );
+ KPILOT_DELETE( pcEntry );
+ QTimer::singleShot( 0, this, SLOT( slotPCRecToPalm() ) );
+ return;
+ }
+
+ // look into the list of already synced record ids to see if the PCEntry hasn't already been synced
+ if ( mSyncedIds.contains( recID ) )
+ {
+#ifdef DEBUG
+ DEBUGKPILOT << ": address with id " << recID << " already synced." << endl;
+#endif
+ KPILOT_DELETE( pcEntry );
+ QTimer::singleShot( 0, this, SLOT( slotPCRecToPalm() ) );
+ return;
+ }
+
+
+ backupRec = fLocalDatabase->readRecordById( recID );
+ // only update if no backup record or the backup record is not equal to the PCEntry
+
+ PilotAppCategory*backupEntry=0L;
+ if ( backupRec )
+ backupEntry = createPalmEntry( backupRec );
+ if( !backupRec || isFirstSync() || !_equal( backupEntry, pcEntry ) )
+ {
+ PilotRecord *palmRec = fDatabase->readRecordById( recID );
+ PilotAppCategory *palmEntry=0L;
+ if (palmRec)
+ palmEntry = createPalmEntry( palmRec );
+ syncEntry( pcEntry, backupEntry, palmEntry );
+ // update the id just in case it changed
+ if ( palmRec )
+ recID = palmRec->id();
+ KPILOT_DELETE( palmRec );
+ KPILOT_DELETE( palmEntry );
+ }
+
+ KPILOT_DELETE( pcEntry );
+ KPILOT_DELETE( backupEntry );
+ KPILOT_DELETE( backupRec );
+ mSyncedIds.append( recID );
+
+ // done with the sync process, go on with the next one:
+ QTimer::singleShot( 0, this, SLOT( slotPCRecToPalm() ) );
+}
+
+
+
+void RecordConduit::slotDeletedRecord()
+{
+ FUNCTIONSETUP;
+
+ PilotRecord *backupRec = fLocalDatabase->readRecordByIndex( mPalmIndex++ );
+ if( !backupRec || isFirstSync() )
+ {
+ KPILOT_DELETE(backupRec);
+ QTimer::singleShot( 0, this, SLOT( slotDeleteUnsyncedPCRecords() ) );
+ return;
+ }
+
+ // already synced, so skip this record:
+ if ( mSyncedIds.contains( backupRec->id() ) )
+ {
+ KPILOT_DELETE( backupRec );
+ QTimer::singleShot( 0, this, SLOT( slotDeletedRecord() ) );
+ return;
+ }
+
+ QString uid = mEntryMap[ backupRec->id() ];
+ PCEntry *pcEntry = mPCData->findByUid( uid );
+ PilotRecord *palmRec = fDatabase->readRecordById( backupRec->id() );
+ PilotAppCategory *backupEntry = 0L;
+ if (backupRec)
+ backupEntry = createPalmEntry( backupRec );
+ PilotAppCategory*palmEntry=0L;
+ if (palmRec)
+ palmEntry = createPalmEntry( palmRec );
+
+ mSyncedIds.append( backupRec->id() );
+ syncEntry( pcEntry, backupEntry, palmEntry );
+
+ KPILOT_DELETE( pcEntry );
+ KPILOT_DELETE( palmEntry );
+ KPILOT_DELETE( backupEntry );
+ KPILOT_DELETE( palmRec );
+ KPILOT_DELETE( backupRec );
+ QTimer::singleShot( 0, this, SLOT( slotDeletedRecord() ) );
+}
+
+
+
+void RecordConduit::slotDeleteUnsyncedPCRecords()
+{
+ FUNCTIONSETUP;
+ if ( getSyncDirection() == SyncAction::eCopyHHToPC )
+ {
+ QStringList uids;
+ RecordIDList::iterator it;
+ QString uid;
+ for ( it = mSyncedIds.begin(); it != mSyncedIds.end(); ++it)
+ {
+ uid = mEntryMap[ *it ];
+ if ( !uid.isEmpty() ) uids.append( uid );
+ }
+ // TODO: Does this speed up anything?
+ // qHeapSort( uids );
+ const QStringList alluids( mPCData->uids() );
+ QStringList::ConstIterator uidit;
+ for ( uidit = alluids.constBegin(); uidit != alluids.constEnd(); ++uidit )
+ {
+ if ( !uids.contains( *uidit ) )
+ {
+#ifdef DEBUG
+ DEBUGKPILOT << "Deleting PCEntry with uid " << (*uidit) << " from PC (is not on HH, and syncing with HH->PC direction)" << endl;
+#endif
+ mPCData->removeEntry( *uidit );
+ }
+ }
+ }
+ QTimer::singleShot(0, this, SLOT(slotDeleteUnsyncedHHRecords()));
+}
+
+
+
+void RecordConduit::slotDeleteUnsyncedHHRecords()
+{
+ FUNCTIONSETUP;
+ if ( getSyncDirection() == SyncAction::eCopyPCToHH )
+ {
+ RecordIDList ids = fDatabase->idList();
+ RecordIDList::iterator it;
+ for ( it = ids.begin(); it != ids.end(); ++it )
+ {
+ if ( !mSyncedIds.contains(*it) )
+ {
+#ifdef DEBUG
+ DEBUGKPILOT << "Deleting record with ID " << *it << " from handheld (is not on PC, and syncing with PC->HH direction)" << endl;
+#endif
+ fDatabase->deleteRecord(*it);
+ fLocalDatabase->deleteRecord(*it);
+ }
+ }
+ }
+ QTimer::singleShot( 0, this, SLOT( slotCleanup() ) );
+}
+
+
+void RecordConduit::slotCleanup()
+{
+ FUNCTIONSETUP;
+
+ // Set the appInfoBlock, just in case the category labels changed
+ _setAppInfo();
+ doPostSync();
+ if(fDatabase)
+ {
+ fDatabase->resetSyncFlags();
+ fDatabase->cleanup();
+ }
+ if(fLocalDatabase)
+ {
+ fLocalDatabase->resetSyncFlags();
+ fLocalDatabase->cleanup();
+ }
+ KPILOT_DELETE( fDatabase );
+ KPILOT_DELETE( fLocalDatabase );
+ // TODO: do something if saving fails!
+ mPCData->saveData();
+ mPCData->cleanup();
+ emit syncDone(this);
+}
+
+
+/** Return the list of category names on the handheld
+ */
+const QStringList RecordConduit::categories() const
+{
+ QStringList cats;
+ for ( unsigned int j = 0; j < Pilot::CATEGORY_COUNT; j++ ) {
+ QString catName( category( j ) );
+ if ( !catName.isEmpty() ) cats << catName;
+ }
+ return cats;
+}
+int RecordConduit::findFlags() const
+{
+ return eqFlagsAlmostAll;
+}
+
+
+bool RecordConduit::isDeleted( const PilotAppCategory *palmEntry )
+{
+ if ( !palmEntry )
+ return true;
+ if ( palmEntry->isDeleted() && !palmEntry->isArchived() )
+ return true;
+ if ( palmEntry->isArchived() )
+ return !archiveDeleted();
+ return false;
+}
+bool RecordConduit::isArchived( const PilotAppCategory *palmEntry )
+{
+ if ( palmEntry && palmEntry->isArchived() )
+ return archiveDeleted();
+ else
+ return false;
+}
+
+
+
+
+/*********************************************************************
+ L O A D I N G T H E D A T A
+ *********************************************************************/
+
+
+
+bool RecordConduit::_prepare()
+{
+ FUNCTIONSETUP;
+
+ readConfig();
+ mSyncedIds.clear();
+ mPCData = initializePCData();
+
+ return mPCData && doPrepare();
+}
+
+
+void RecordConduit::_getAppInfo()
+{
+ FUNCTIONSETUP;
+ // get the address application header information
+ unsigned char *buffer = new unsigned char[Pilot::MAX_APPINFO_SIZE];
+ int appLen=fDatabase->readAppBlock(buffer, Pilot::MAX_APPINFO_SIZE);
+
+ doUnpackAppInfo( buffer, appLen );
+ delete[] buffer;
+ buffer = 0;
+}
+
+void RecordConduit::_setAppInfo()
+{
+ FUNCTIONSETUP;
+ // get the address application header information
+ int appLen = 0;
+ unsigned char *buffer = doPackAppInfo( &appLen );
+ if ( buffer )
+ { if (fDatabase)
+ fDatabase->writeAppBlock( buffer, appLen );
+ if (fLocalDatabase)
+ fLocalDatabase->writeAppBlock( buffer, appLen );
+ delete[] buffer;
+ }
+}
+
+
+int RecordConduit::compareStr( const QString & str1, const QString & str2 )
+{
+// FUNCTIONSETUP;
+ if ( str1.isEmpty() && str2.isEmpty() )
+ return 0;
+ else
+ return str1.compare( str2 );
+}
+
+
+/**
+ * _getCat returns the id of the category from the given categories list.
+ * If the address has no categories on the PC, QString::null is returned.
+ * If the current category exists in the list of cats, it is returned
+ * Otherwise the first cat in the list that exists on the HH is returned
+ * If none of the categories exists on the palm, QString::null is returned
+ */
+QString RecordConduit::getCatForHH( const QStringList cats, const QString curr ) const
+{
+ FUNCTIONSETUP;
+ if ( cats.size() < 1 )
+ return QString::null;
+ if ( cats.contains( curr ) )
+ return curr;
+ for ( QStringList::ConstIterator it = cats.begin(); it != cats.end(); ++it)
+ {
+ for ( unsigned int j = 0; j < Pilot::CATEGORY_COUNT; j++ )
+ {
+ QString catnm( category( j ) );
+ if ( !(*it).isEmpty() && ( (*it)==catnm ) )
+ {
+ return catnm;
+ }
+ }
+ }
+ // If we have a free label, return the first possible cat
+ QString lastCat( category( Pilot::CATEGORY_COUNT-1 ) );
+ return ( lastCat.isEmpty() ) ? ( cats.first() ) : ( QString::null );
+}
+
+void RecordConduit::setCategory(PCEntry * pcEntry, QString cat)
+{
+ if ( !cat.isEmpty() && cat!=category( 0 ) )
+ pcEntry->insertCategory(cat);
+}
+
+
+
+
+
+
+/*********************************************************************
+ G E N E R A L S Y N C F U N C T I O N
+ These functions modify the Handheld and the addressbook
+ *********************************************************************/
+
+
+
+bool RecordConduit::syncEntry( PCEntry *pcEntry, PilotAppCategory*backupEntry,
+ PilotAppCategory*palmEntry)
+{
+ FUNCTIONSETUP;
+
+ if ( getSyncDirection() == SyncAction::eCopyPCToHH )
+ {
+ if ( pcEntry->isEmpty() )
+ {
+ return pcDeleteEntry( pcEntry, backupEntry, palmEntry );
+ }
+ else
+ {
+ return pcCopyToPalm( pcEntry, backupEntry, palmEntry );
+ }
+ }
+
+ if ( getSyncDirection() == SyncAction::eCopyHHToPC )
+ {
+ if (!palmEntry)
+ return pcDeleteEntry(pcEntry, backupEntry, palmEntry);
+ else
+ return palmCopyToPC(pcEntry, backupEntry, palmEntry);
+ }
+
+ if ( !backupEntry || isFirstSync() )
+ {
+ /*
+ Resolution matrix (0..does not exist, E..exists, D..deleted flag set, A..archived):
+ HH PC | Resolution
+ ------------------------------------------------------------
+ 0 A | -
+ 0 E | PC -> HH, reset ID if not set correctly
+ D 0 | delete (error, should never occur!!!)
+ D E | CR (ERROR)
+ E/A 0 | HH -> PC
+ E/A E/A| merge/CR
+ */
+ if ( !palmEntry && isArchived( pcEntry ) )
+ {
+ return true;
+ }
+ else if ( !palmEntry && !pcEntry->isEmpty() )
+ {
+ // PC->HH
+ bool res = pcCopyToPalm( pcEntry, 0L, 0L );
+ return res;
+ }
+ else if ( !palmEntry && pcEntry->isEmpty() )
+ {
+ // everything's empty -> ERROR
+ return false;
+ }
+ else if ( ( isDeleted( palmEntry ) || isArchived( palmEntry ) ) && pcEntry->isEmpty())
+ {
+ if ( isArchived( palmEntry ) )
+ return palmCopyToPC( pcEntry, 0L, palmEntry );
+ else
+ // this happens if you add a record on the handheld and delete it again before you do the next sync
+ return pcDeleteEntry( pcEntry, 0L, palmEntry );
+ }
+ else if ( ( isDeleted(palmEntry) || isArchived( palmEntry ) ) && !pcEntry->isEmpty() )
+ {
+ // CR (ERROR)
+ return smartMergeEntry( pcEntry, 0L, palmEntry );
+ }
+ else if ( pcEntry->isEmpty() )
+ {
+ // HH->PC
+ return palmCopyToPC( pcEntry, 0L, palmEntry );
+ }
+ else
+ {
+ // Conflict Resolution
+ return smartMergeEntry( pcEntry, 0L, palmEntry );
+ }
+ } // !backupEntry
+ else
+ {
+ /*
+ Resolution matrix:
+ 1) if HH.(empty| (deleted &! archived) ) -> { if (PC==B) -> delete, else -> CR }
+ if HH.archived -> {if (PC==B) -> copyToPC, else -> CR }
+ if PC.empty -> { if (HH==B) -> delete, else -> CR }
+ if PC.archived -> {if (HH==B) -> delete on HH, else CR }
+ 2) if PC==HH -> { update B, update ID of PC if needed }
+ 3) if PC==B -> { HH!=PC, thus HH modified, so copy HH->PC }
+ if HH==B -> { PC!=HH, thus PC modified, so copy PC->HH }
+ 4) else: all three PCEntrys are different -> CR
+ */
+
+ if ( !palmEntry || isDeleted(palmEntry) )
+ {
+ if ( _equal( backupEntry, pcEntry ) || pcEntry->isEmpty() )
+ {
+ return pcDeleteEntry( pcEntry, backupEntry, 0L );
+ }
+ else
+ {
+ return smartMergeEntry( pcEntry, backupEntry, 0L );
+ }
+ }
+ else if ( pcEntry->isEmpty() )
+ {
+ if (*palmEntry == *backupEntry)
+ {
+ return pcDeleteEntry( pcEntry, backupEntry, palmEntry );
+ }
+ else
+ {
+ return smartMergeEntry( pcEntry, backupEntry, palmEntry );
+ }
+ }
+ else if ( _equal( palmEntry, pcEntry ) )
+ {
+ // update Backup, update ID of PC if neededd
+ return backupSaveEntry( palmEntry );
+ }
+ else if ( _equal( backupEntry, pcEntry ) )
+ {
+#ifdef DEBUG
+ DEBUGKPILOT << "Flags: " << palmEntry->getAttrib() << ", isDeleted=" <<
+ isDeleted( palmEntry ) << ", isArchived=" << isArchived( palmEntry )
+ << endl;
+#endif
+ if ( isDeleted( palmEntry ) )
+ {
+ return pcDeleteEntry( pcEntry, backupEntry, palmEntry );
+ }
+ else
+ {
+ return palmCopyToPC( pcEntry, backupEntry, palmEntry );
+ }
+ }
+ else if ( *palmEntry == *backupEntry )
+ {
+ return pcCopyToPalm( pcEntry, backupEntry, palmEntry );
+ }
+ else
+ {
+ // CR, since all are different
+ return smartMergeEntry( pcEntry, backupEntry, palmEntry );
+ }
+ } // backupEntry
+ return false;
+}
+
+bool RecordConduit::pcCopyToPalm( PCEntry *pcEntry, PilotAppCategory *backupEntry,
+ PilotAppCategory*palmEntry )
+{
+ FUNCTIONSETUP;
+
+ if ( pcEntry->isEmpty() ) return false;
+ PilotAppCategory *hhEntry = palmEntry;
+ bool hhEntryCreated = false;
+ if ( !hhEntry )
+ {
+ hhEntry = createPalmEntry( 0 );
+ hhEntryCreated=true;
+ }
+ _copy( hhEntry, pcEntry );
+#ifdef DEBUG
+ DEBUGKPILOT << "palmEntry->id=" << hhEntry->id() << ", pcEntry.ID=" <<
+ pcEntry->uid() << endl;
+#endif
+
+ if( palmSaveEntry( hhEntry, pcEntry ) )
+ {
+#ifdef DEBUG
+ DEBUGKPILOT << "Entry palmEntry->id=" <<
+ hhEntry->id() << "saved to palm, now updating pcEntry->uid()=" << pcEntry->uid() << endl;
+#endif
+ pcSaveEntry( pcEntry, backupEntry, hhEntry );
+ }
+ if ( hhEntryCreated ) KPILOT_DELETE( hhEntry );
+ return true;
+}
+
+
+
+
+bool RecordConduit::palmCopyToPC( PCEntry *pcEntry, PilotAppCategory *backupEntry,
+ PilotAppCategory *palmEntry )
+{
+ FUNCTIONSETUP;
+ if ( !palmEntry )
+ {
+ return false;
+ }
+ _copy( pcEntry, palmEntry );
+ pcSaveEntry( pcEntry, backupEntry, palmEntry );
+ backupSaveEntry( palmEntry );
+ return true;
+}
+
+
+
+/*********************************************************************
+ l o w - l e v e l f u n c t i o n s f o r
+ adding / removing palm/pc records
+ *********************************************************************/
+
+
+
+bool RecordConduit::palmSaveEntry( PilotAppCategory *palmEntry, PCEntry *pcEntry )
+{
+ FUNCTIONSETUP;
+
+#ifdef DEBUG
+ DEBUGKPILOT << "Saving to pilot " << palmEntry->id() << endl;
+#endif
+
+ PilotRecord *pilotRec = palmEntry->pack();
+ recordid_t pilotId = fDatabase->writeRecord(pilotRec);
+#ifdef DEBUG
+ DEBUGKPILOT << "PilotRec nach writeRecord (" << pilotId <<
+ ": ID=" << pilotRec->id() << endl;
+#endif
+ fLocalDatabase->writeRecord( pilotRec );
+ KPILOT_DELETE( pilotRec );
+
+ // pilotId == 0 if using local db, so don't overwrite the valid id
+ if ( pilotId != 0 )
+ {
+ palmEntry->setID( pilotId );
+ if ( !mSyncedIds.contains( pilotId ) )
+ {
+ mSyncedIds.append( pilotId );
+ }
+ }
+
+ recordid_t hhId( pcEntry->recid() );
+ if ( hhId != pilotId )
+ {
+ pcEntry->setRecid( pilotId );
+ return true;
+ }
+
+ return false;
+}
+
+
+
+bool RecordConduit::backupSaveEntry( PilotAppCategory *backup )
+{
+ FUNCTIONSETUP;
+ if ( !backup ) return false;
+
+
+#ifdef DEBUG
+// showPilotAppCategory( backup );
+#endif
+ PilotRecord *pilotRec = backup->pack();
+ fLocalDatabase->writeRecord( pilotRec );
+ KPILOT_DELETE( pilotRec );
+ return true;
+}
+
+
+
+bool RecordConduit::pcSaveEntry( PCEntry *pcEntry, PilotAppCategory *,
+ PilotAppCategory * )
+{
+ FUNCTIONSETUP;
+
+#ifdef DEBUG
+ DEBUGKPILOT << "Before _savepcEntry, pcEntry->uid()=" <<
+ pcEntry->uid() << endl;
+#endif
+ if ( pcEntry->recid() != 0 )
+ {
+ mEntryMap.insert( pcEntry->recid(), pcEntry->uid() );
+ }
+
+ mPCData->updateEntry( pcEntry );
+ return true;
+}
+
+
+
+bool RecordConduit::pcDeleteEntry( PCEntry *pcEntry, PilotAppCategory *backupEntry,
+ PilotAppCategory *palmEntry )
+{
+ FUNCTIONSETUP;
+
+ if ( palmEntry )
+ {
+ if ( !mSyncedIds.contains( palmEntry->id() ) )
+ {
+ mSyncedIds.append(palmEntry->id());
+ }
+ palmEntry->makeDeleted();
+ PilotRecord *pilotRec = palmEntry->pack();
+ pilotRec->setDeleted();
+ mPalmIndex--;
+ fDatabase->writeRecord( pilotRec );
+ fLocalDatabase->writeRecord( pilotRec );
+ mSyncedIds.append( pilotRec->id() );
+ KPILOT_DELETE( pilotRec );
+ }
+ else if ( backupEntry )
+ {
+ if ( !mSyncedIds.contains( backupEntry->id() ) )
+ {
+ mSyncedIds.append( backupEntry->id() );
+ }
+ backupEntry->makeDeleted();
+ PilotRecord *pilotRec = backupEntry->pack();
+ pilotRec->setDeleted();
+ mPalmIndex--;
+ fLocalDatabase->writeRecord( pilotRec );
+ mSyncedIds.append( pilotRec->id() );
+ KPILOT_DELETE( pilotRec );
+ }
+ if ( !pcEntry->isEmpty() )
+ {
+#ifdef DEBUG
+ DEBUGKPILOT << fname << " removing " << pcEntry->uid() << endl;
+#endif
+ mPCData->removeEntry( pcEntry );
+ }
+ return true;
+}
+
+
+
+/*********************************************************************
+ C O P Y R E C O R D S
+ *********************************************************************/
+
+
+
+
+
+/*********************************************************************
+ 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
+ *********************************************************************/
+
+
+
+
+// TODO: right now entries are equal if both first/last name and organization are
+// equal. This rules out two entries for the same person(e.g. real home and weekend home)
+// or two persons with the same name where you don't know the organization.!!!
+RecordConduit::PCEntry *RecordConduit::findMatch( PilotAppCategory *palmEntry ) const
+{
+ FUNCTIONSETUP;
+ if ( !palmEntry )
+ return 0;
+
+ // TODO: also search with the pilotID
+ // first, use the pilotID to UID map to find the appropriate record
+ if( !isFirstSync() && ( palmEntry->id() > 0) )
+ {
+ QString id( mEntryMap[palmEntry->id()] );
+#ifdef DEBUG
+ DEBUGKPILOT << fname << ": PilotRecord has id " << palmEntry->id() << ", mapped to " << id << endl;
+#endif
+ if( !id.isEmpty() )
+ {
+ PCEntry *res = mPCData->findByUid( id );
+ if ( !res && !res->isEmpty() ) return res;
+ KPILOT_DELETE( res );
+#ifdef DEBUG
+ DEBUGKPILOT << fname << ": PilotRecord has id " << palmEntry->id() <<
+ ", but could not be found on the PC side" << endl;
+#endif
+ }
+ }
+
+ for ( PCData::Iterator iter = mPCData->begin(); !mPCData->atEnd( iter ); ++iter )
+ {
+ PCEntry *abEntry = *iter;
+ recordid_t rid( abEntry->recid() );
+ if ( rid>0 )
+ {
+ if ( rid == palmEntry->id() )
+ return abEntry;// yes, we found it
+ // skip this PCEntry, as it has a different corresponding address on the handheld
+ //if ( mAllIds.contains( rid ) ) continue;
+ }
+
+ if ( _equal( palmEntry, abEntry, eqFlagsAlmostAll ) )
+ {
+ return abEntry;
+ }
+ KPILOT_DELETE( abEntry );
+ }
+#ifdef DEBUG
+ DEBUGKPILOT << fname << ": Could not find any entry matching Palm record with id " << QString::number( palmEntry->id() ) << endl;
+#endif
+ return 0;
+}
+
+#endif
+
+
+
+
+#include "recordConduit.moc"
+