You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1146 lines
27 KiB
1146 lines
27 KiB
/* 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 <tqtimer.h>
|
|
#include <tqfile.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 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<recordid_t,TQString> &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 <PilotSerialDatabase * >(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() 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() is returned
|
|
*/
|
|
TQString RecordConduit::getCatForHH( const TQStringList cats, const TQString curr ) const
|
|
{
|
|
FUNCTIONSETUP;
|
|
if ( cats.size() < 1 )
|
|
return TQString();
|
|
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() );
|
|
}
|
|
|
|
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"
|
|
|