/* 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 #include #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 TQTimer(this); connect(fTimer,TQT_SIGNAL(timeout()),this,TQT_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 } TQString 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"); } } TQString 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( TQMap &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(TQString name, KPilotDeviceLink * o, const char *n, const TQStringList & 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. */ TQTimer::singleShot(0, this, TQT_SLOT(slotPalmRecToPC())); return true; } void RecordConduit::slotPalmRecToPC() { FUNCTIONSETUP; PilotRecord *palmRec = 0L, *backupRec = 0L; if ( getSyncDirection() == SyncAction::eCopyPCToHH ) { mPCIter = mPCData->begin(); TQTimer::singleShot(0, this, TQT_SLOT(slotPCRecToPalm())); return; } if ( isFullSync() ) palmRec = fDatabase->readRecordByIndex( mPalmIndex++ ); else palmRec = dynamic_cast (fDatabase)->readNextModifiedRec(); if ( !palmRec ) { mPCIter = mPCData->begin(); TQTimer::singleShot( 0, this, TQT_SLOT( slotPCRecToPalm() ) ); return; } // already synced, so skip: if ( mSyncedIds.contains( palmRec->id() ) ) { KPILOT_DELETE( palmRec ); TQTimer::singleShot( 0, this, TQT_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 ); TQTimer::singleShot(0, this, TQT_SLOT(slotPalmRecToPC())); } void RecordConduit::slotPCRecToPalm() { FUNCTIONSETUP; if ( ( getSyncDirection()==SyncAction::eCopyHHToPC ) || mPCData->atEnd( mPCIter ) ) { mPalmIndex = 0; TQTimer::singleShot( 0, this, TQT_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 ); TQTimer::singleShot( 0, this, TQT_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 ); TQTimer::singleShot( 0, this, TQT_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 ); TQTimer::singleShot( 0, this, TQT_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: TQTimer::singleShot( 0, this, TQT_SLOT( slotPCRecToPalm() ) ); } void RecordConduit::slotDeletedRecord() { FUNCTIONSETUP; PilotRecord *backupRec = fLocalDatabase->readRecordByIndex( mPalmIndex++ ); if( !backupRec || isFirstSync() ) { KPILOT_DELETE(backupRec); TQTimer::singleShot( 0, this, TQT_SLOT( slotDeleteUnsyncedPCRecords() ) ); return; } // already synced, so skip this record: if ( mSyncedIds.contains( backupRec->id() ) ) { KPILOT_DELETE( backupRec ); TQTimer::singleShot( 0, this, TQT_SLOT( slotDeletedRecord() ) ); return; } TQString 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 ); TQTimer::singleShot( 0, this, TQT_SLOT( slotDeletedRecord() ) ); } void RecordConduit::slotDeleteUnsyncedPCRecords() { FUNCTIONSETUP; if ( getSyncDirection() == SyncAction::eCopyHHToPC ) { TQStringList uids; RecordIDList::iterator it; TQString 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 TQStringList alluids( mPCData->uids() ); TQStringList::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 ); } } } TQTimer::singleShot(0, this, TQT_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); } } } TQTimer::singleShot( 0, this, TQT_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 TQStringList RecordConduit::categories() const { TQStringList cats; for ( unsigned int j = 0; j < Pilot::CATEGORY_COUNT; j++ ) { TQString 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 TQString & str1, const TQString & 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, TQString::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, TQString::null is returned */ TQString RecordConduit::getCatForHH( const TQStringList cats, const TQString curr ) const { FUNCTIONSETUP; if ( cats.size() < 1 ) return TQString::null; if ( cats.contains( curr ) ) return curr; for ( TQStringList::ConstIterator it = cats.begin(); it != cats.end(); ++it) { for ( unsigned int j = 0; j < Pilot::CATEGORY_COUNT; j++ ) { TQString catnm( category( j ) ); if ( !(*it).isEmpty() && ( (*it)==catnm ) ) { return catnm; } } } // If we have a free label, return the first possible cat TQString lastCat( category( Pilot::CATEGORY_COUNT-1 ) ); return ( lastCat.isEmpty() ) ? ( cats.first() ) : ( TQString::null ); } void RecordConduit::setCategory(PCEntry * pcEntry, TQString 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) ) { TQString 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 " << TQString::number( palmEntry->id() ) << endl; #endif return 0; } #endif #include "recordConduit.moc"