/* KPilot
**
** Copyright (C) 2002 by Reinhold Kainhofer
**
** The doc conduit synchronizes text files on the PC with DOC databases on the Palm
*/

/*
** 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.
*/


// naming of the bookmark file:
// PDB->TXT:	convert bookmarks to a .bm file
// TXT->PDB:	If a .bmk file exists, use it, otherwise use the .bm file (from the PDB->TXT conversion)
//			This way, the bookmark file is not overwritten, a manual bookmark file overrides, but the bookmarks from the handheld are still available


#include "options.h"
#include "doc-conduit.moc"

#include <tqtimer.h>
#include <tqdir.h>

#include <tdeconfig.h>
#include <kmdcodec.h>

#include <pilotLocalDatabase.h>
#include <pilotSerialDatabase.h>

#include "doc-factory.h"
#include "doc-conflictdialog.h"
#include "DOC-converter.h"
#include "pilotDOCHead.h"
#include "docconduitSettings.h"


// Something to allow us to check what revision
// the modules are that make up a binary distribution.
extern "C"
{
unsigned long version_conduit_doc = Pilot::PLUGIN_API;
}

TQString dirToString(eSyncDirectionEnum dir) {
	switch(dir) {
//		case eSyncAll: return "eSyncAll";
		case eSyncPDAToPC: return CSL1("eSyncPDAToPC");
		case eSyncPCToPDA: return CSL1("eSyncPCToPDA");
		case eSyncNone: return CSL1("eSyncNone");
		case eSyncConflict: return CSL1("eSyncConflict");
		case eSyncDelete: return CSL1("eSyncDelete");
		default: return CSL1("ERROR");
	}
}


/*********************************************************************
                        C O N S T R U C T O R
 *********************************************************************/


DOCConduit::DOCConduit(KPilotLink * o,
	const char *n, const TQStringList & a):ConduitAction(o, n, a)
{
	FUNCTIONSETUP;
	fConduitName=i18n("DOC");
}



DOCConduit::~DOCConduit()
{
	FUNCTIONSETUP;
}


bool DOCConduit::isCorrectDBTypeCreator(DBInfo dbinfo) {
	return dbinfo.type == dbtype() && dbinfo.creator == dbcreator();
}
const unsigned long DOCConduit::dbtype() {
	return get_long(DOCConduitFactory::dbDOCtype);
}
const unsigned long DOCConduit::dbcreator() {
	return get_long(DOCConduitFactory::dbDOCcreator);
}



/*********************************************************************
                L O A D I N G   T H E   D A T A
 *********************************************************************/



void DOCConduit::readConfig()
{
	FUNCTIONSETUP;
	DOCConduitSettings::self()->readConfig();

	eConflictResolution = (enum eSyncDirectionEnum) (DOCConduitSettings::conflictResolution() );
	fTXTBookmarks = DOCConverter::eBmkNone;
	if ( DOCConduitSettings::convertBookmarks() )
	{
		if ( DOCConduitSettings::bmkFileBookmarks() )
			fTXTBookmarks |= DOCConverter::eBmkFile;
		if ( DOCConduitSettings::inlineBookmarks() )
			fTXTBookmarks |= DOCConverter::eBmkInline;
		if ( DOCConduitSettings::endtagBookmarks() )
			fTXTBookmarks |= DOCConverter::eBmkEndtags;
	}

	eSyncDirection = (enum eSyncDirectionEnum)(DOCConduitSettings::syncDirection() );

#ifdef DEBUG
	DEBUGKPILOT << fname
		<< ": Settings "
		<< " tXTDirectory=" << DOCConduitSettings::tXTDirectory()
		<< " pDBDirectory=" << DOCConduitSettings::pDBDirectory()
		<< " keepPDBLocally=" << DOCConduitSettings::keepPDBsLocally()
		<< " eConflictResolution=" << eConflictResolution
		<< " tXTBookmarks=" << fTXTBookmarks
		<< " pDBBookmarks=" << DOCConduitSettings::bookmarksToPC()
		<< " compress=" << DOCConduitSettings::compress()
		<< " eSyncDirection=" << eSyncDirection << endl;
#endif
}



bool DOCConduit::pcTextChanged(TQString txtfn)
{
	FUNCTIONSETUP;
	// How do I find out if a text file has changed shince we last synced it??
	// Use KMD5 for now. If I realize it is too slow, then I have to go back to comparing modification times
	// if there is no config setting yet, assume the file has been changed. the md5 sum will be written to the config file after the sync.
	TQString oldDigest=DOCConduitSettings::self()->config()->readEntry(txtfn);
	if (oldDigest.length()<=0)
	{
		return true;
	}
#ifdef DEBUG
	DEBUGKPILOT<<"Old digest is "<<oldDigest<<endl;
#endif

	KMD5 docmd5;
	TQFile txtfile(txtfn);
	if (txtfile.open(IO_ReadOnly)){
		docmd5.update(txtfile);
		TQString thisDigest(docmd5.hexDigest() /* .data() */);
#ifdef DEBUG
		DEBUGKPILOT<<"New digest is "<<thisDigest<<endl;
#endif
		return (thisDigest.length()<=0) || (thisDigest!=oldDigest);
	} else {
		// File does not exist. This should actually never happen. Anyways, just return true to indicate it has changed.
		// doSync should detect this and delete the doc from the handheld.
		return true;
	}
	return false;
}



bool DOCConduit::hhTextChanged(PilotDatabase*docdb)
{
	FUNCTIONSETUP;
	if (!docdb) return false;

	PilotRecord *firstRec = docdb->readRecordByIndex(0);
	PilotDOCHead docHeader(firstRec);
	KPILOT_DELETE(firstRec);

	int storyRecs = docHeader.numRecords;

	// determine the index of the next modified record (does it lie
	// beyond the actual text records?)
	int modRecInd=-1;
	PilotRecord*modRec=docdb->readNextModifiedRec(&modRecInd);
#ifdef DEBUG
	DEBUGKPILOT<<"Index of first changed record: "<<modRecInd<<endl;
#endif

	KPILOT_DELETE(modRec);
	// if the header record was changed, find out which is the first changed
	// real document record:
	if (modRecInd==0) {
		modRec=docdb->readNextModifiedRec(&modRecInd);
#ifdef DEBUG
		DEBUGKPILOT<<"Reread Index of first changed records: "<<modRecInd<<endl;
#endif
		KPILOT_DELETE(modRec);
	}

	// The record index starts with 0, so only a negative number means
	// no modified record was found
	if (modRecInd >= 0) {
#ifdef DEBUG
		DEBUGKPILOT<<"Handheld side has changed, condition="<<
			((!DOCConduitSettings::ignoreBmkChanges()) || (modRecInd <= storyRecs))<<endl;
#endif
		if ((!DOCConduitSettings::ignoreBmkChanges()) || (modRecInd <= storyRecs))
			return true;
	} else {
#ifdef DEBUG
		DEBUGKPILOT<<"Handheld side has NOT changed!"<<endl;
#endif
		return false;
	}
	return false;
}



/*********************************************************************
 *     Helper functions
 ********************************************************************/

TQString DOCConduit::constructPDBFileName(TQString name) {
	FUNCTIONSETUP;
	TQString fn;
	TQDir dr(DOCConduitSettings::pDBDirectory());
	TQFileInfo pth(dr, name);
	if (!name.isEmpty()) fn=pth.absFilePath()+CSL1(".pdb");
	return fn;
}
TQString DOCConduit::constructTXTFileName(TQString name) {
	FUNCTIONSETUP;
	TQString fn;
	TQDir dr( DOCConduitSettings::tXTDirectory() );
	TQFileInfo pth(dr, name);
	if (!name.isEmpty()) fn=pth.absFilePath()+CSL1(".txt");
	return fn;
}





/*********************************************************************
                S Y N C   S T R U C T U R E
 *********************************************************************/





/* virtual */ bool DOCConduit::exec()
{
	FUNCTIONSETUP;

	readConfig();
	dbnr=0;

	emit logMessage(i18n("Searching for texts and databases to synchronize"));

	TQTimer::singleShot(0, this, TQ_SLOT(syncNextDB()));
	return true;
}



bool DOCConduit::doSync(docSyncInfo &sinfo)
{
	FUNCTIONSETUP;
	bool res=false;

	if (sinfo.direction==eSyncDelete) {
		if (!sinfo.txtfilename.isEmpty()) {
			if (!TQFile::remove(sinfo.txtfilename)) {
				WARNINGKPILOT << "Unable to delete the text file " << sinfo.txtfilename << " on the PC" << endl;
			}
			TQString bmkfilename = sinfo.txtfilename;
			if (bmkfilename.endsWith(CSL1(".txt"))){
				bmkfilename.remove(bmkfilename.length()-4, 4);
			}
			bmkfilename+=CSL1(PDBBMK_SUFFIX);
			if (!TQFile::remove(bmkfilename)) {
#ifdef DEBUG
				DEBUGKPILOT<<"Could not remove bookmarks file "<<bmkfilename<<" for database "<<sinfo.handheldDB<<endl;
#endif
			}
		}
		if (!sinfo.pdbfilename.isEmpty() && DOCConduitSettings::keepPDBsLocally() ) {
			PilotLocalDatabase*database=new PilotLocalDatabase(DOCConduitSettings::pDBDirectory(),
				TQString::fromLatin1(sinfo.dbinfo.name), false);
			if (database) {
				if ( database->deleteDatabase() !=0 ) {
					WARNINGKPILOT << "Unable to delete database " << sinfo.dbinfo.name << " on the PC" << endl;
				}
				KPILOT_DELETE(database);
			}
		}
		if (!DOCConduitSettings::localSync()) {
			PilotDatabase *database=deviceLink()->database( sinfo.dbinfo.name );
			if ( database->deleteDatabase() !=0 ) {
					WARNINGKPILOT << "Unable to delete database " << sinfo.dbinfo.name << " from the handheld" << endl;
			}
			KPILOT_DELETE(database);
		}
		return true;
	}
	// preSyncAction should initialize the custom databases/files for the
	// specific action chosen for this db and return a pointer to a docDBInfo
	// instance which points either to a local database or a database on the handheld.
	PilotDatabase *database = preSyncAction(sinfo);

	if (database && ( !database->isOpen() ) ) {
#ifdef DEBUG
		DEBUGKPILOT<<"Database "<<sinfo.dbinfo.name<<" does not yet exist. Creating it:"<<endl;
#endif
		if (!database->createDatabase(dbcreator(), dbtype()) ) {
#ifdef DEBUG
			DEBUGKPILOT<<"Failed"<<endl;
			emit logMessage(i18n("Database created."));
#endif
		}
	}

	if (database && database->isOpen()) {
		DOCConverter docconverter;
		connect(&docconverter, TQ_SIGNAL(logError(const TQString &)), TQ_SIGNAL(logError(const TQString &)));
		connect(&docconverter, TQ_SIGNAL(logMessage(const TQString &)), TQ_SIGNAL(logMessage(const TQString &)));

		docconverter.setTXTpath( DOCConduitSettings::tXTDirectory(), sinfo.txtfilename );
		docconverter.setPDB(database);
		docconverter.setCompress(DOCConduitSettings::compress());

		switch (sinfo.direction) {
			case eSyncPDAToPC:
				docconverter.setBookmarkTypes(DOCConduitSettings::bookmarksToPC());
				res = docconverter.convertPDBtoTXT();
				break;
			case eSyncPCToPDA:
				docconverter.setBookmarkTypes(fTXTBookmarks);
				res = docconverter.convertTXTtoPDB();
				break;
			default:
				break;
		}

		// Now calculate the md5 checksum of the PC text and write it to the config file
		if (res)
		{
			KMD5 docmd5;
			TQFile txtfile(docconverter.txtFilename());
			if (txtfile.open(IO_ReadOnly)) {
				docmd5.update(txtfile);
				TQString thisDigest(docmd5.hexDigest() /* .data() */);
				DOCConduitSettings::self()->config()->writeEntry(docconverter.txtFilename(), thisDigest);
				DOCConduitSettings::self()->config()->sync();
#ifdef DEBUG
				DEBUGKPILOT<<"MD5 Checksum of the text "<<sinfo.txtfilename<<" is "<<thisDigest<<endl;
#endif
			} else {
#ifdef DEBUG
				DEBUGKPILOT<<"couldn't open file "<<docconverter.txtFilename()<<" for reading!!!"<<endl;
#endif
			}
		}

		if (!postSyncAction(database, sinfo, res))
			emit logError(i18n("Unable to install the locally created PalmDOC %1 to the handheld.")
				.arg(TQString::fromLatin1(sinfo.dbinfo.name)));
		if (!res)
			emit logError(i18n("Conversion of PalmDOC \"%1\" failed.")
				.arg(TQString::fromLatin1(sinfo.dbinfo.name)));
//		disconnect(&docconverter, TQ_SIGNAL(logError(const TQString &)), TQ_SIGNAL(logError(const TQString &)));
//		disconnect(&docconverter, TQ_SIGNAL(logMessage(const TQString &)), TQ_SIGNAL(logMessage(const TQString &)));
//		KPILOT_DELETE(database);
	}
	else
	{
		emit logError(i18n("Unable to open or create the database %1.")
			.arg(TQString::fromLatin1(sinfo.dbinfo.name)));
	}
	return res;
}


/** syncNextDB walks through all PalmDoc databases on the handheld and decides if they are supposed to be synced to the PC.
 * syncNextDB and syncNextTXT fist build the list of all PalmDoc texts, and then the method syncDatabases does the actual sync. */
void DOCConduit::syncNextDB() {
	FUNCTIONSETUP;
	DBInfo dbinfo;

	if (eSyncDirection==eSyncPCToPDA || fHandle->findDatabase(NULL, &dbinfo, dbnr, dbtype(), dbcreator() /*, cardno */ ) < 0)
	{
		// no more databases available, so check for PC->Palm sync
		TQTimer::singleShot(0, this, TQ_SLOT(syncNextTXT()));
		return;
	}
	dbnr=dbinfo.index+1;
#ifdef DEBUG
	DEBUGKPILOT<<"Next Palm database to sync: "<<dbinfo.name<<", Index="<<dbinfo.index<<endl;
#endif

	// if creator and/or type don't match, go to next db
	if (!isCorrectDBTypeCreator(dbinfo) ||
		fDBNames.contains(TQString::fromLatin1(dbinfo.name)))
	{
		TQTimer::singleShot(0, this, TQ_SLOT(syncNextDB()));
		return;
	}

	TQString txtfilename=constructTXTFileName(TQString::fromLatin1(dbinfo.name));
	TQString pdbfilename=constructPDBFileName(TQString::fromLatin1(dbinfo.name));

	docSyncInfo syncInfo(TQString::fromLatin1(dbinfo.name),
		txtfilename, pdbfilename, eSyncNone);
	syncInfo.dbinfo=dbinfo;
	needsSync(syncInfo);
	fSyncInfoList.append(syncInfo);
	fDBNames.append(TQString::fromLatin1(dbinfo.name));

	TQTimer::singleShot(0, this, TQ_SLOT(syncNextDB()));
	return;
}



void DOCConduit::syncNextTXT()
{
	FUNCTIONSETUP;

	if (eSyncDirection==eSyncPDAToPC  )
	{
		// We don't sync from PC to PDB, so start the conflict resolution and then the actual sync process
		docnames.clear();
		TQTimer::singleShot(0, this, TQ_SLOT(checkPDBFiles()));
		return;
	}

	// if docnames isn't initialized, get a list of all *.txt files in DOCConduitSettings::tXTDirectory()
	if (docnames.isEmpty()/* || dociterator==docnames.end() */) {
		docnames=TQDir( DOCConduitSettings::tXTDirectory(), CSL1("*.txt")).entryList() ;
		dociterator=docnames.begin();
	}
	if (dociterator==docnames.end()) {
		// no more databases available, so start the conflict resolution and then the actual sync proces
		docnames.clear();
		TQTimer::singleShot(0, this, TQ_SLOT(checkPDBFiles()));
		return;
	}

	TQString fn=(*dociterator);

	TQDir dr( DOCConduitSettings::tXTDirectory() );
	TQFileInfo fl(dr, fn );
	TQString txtfilename=fl.absFilePath();
	TQString pdbfilename;
	++dociterator;

	DBInfo dbinfo;
	// Include all "extensions" except the last. This allows full stops inside the database name (e.g. abbreviations)
	// first fill everything with 0, so we won't have a buffer overflow.
	memset(&dbinfo.name[0], 0, 33);
	strncpy(&dbinfo.name[0], fl.baseName(true).latin1(), 30);

	bool alreadySynced=fDBNames.contains(fl.baseName(true));
	if (!alreadySynced) {
		docSyncInfo syncInfo(TQString::fromLatin1(dbinfo.name),
			txtfilename, pdbfilename, eSyncNone);
		syncInfo.dbinfo=dbinfo;
		needsSync(syncInfo);
		fSyncInfoList.append(syncInfo);
		fDBNames.append(TQString::fromLatin1(dbinfo.name));
	} else {
#ifdef DEBUG
		DEBUGKPILOT<<txtfilename<<" has already been synced, skipping it."<<endl;
#endif
	}

	TQTimer::singleShot(0, this, TQ_SLOT(syncNextTXT()));
	return;
}



/** This slot will only be used if DOCConduitSettings::keepPDBsLocally() to check if new doc databases have been copied to the pdb directory.
 *  If so, install it to the handheld and sync it to the PC */
void DOCConduit::checkPDBFiles() {
	FUNCTIONSETUP;

	if ( DOCConduitSettings::localSync() || !DOCConduitSettings::keepPDBsLocally() || eSyncDirection==eSyncPCToPDA )
	{
		// no more databases available, so check for PC->Palm sync
		TQTimer::singleShot(0, this, TQ_SLOT(checkDeletedDocs()));
		return;
	}

	// Walk through all files in the pdb directory and check if it has already been synced.
	// if docnames isn't initialized, get a list of all *.pdb files in DOCConduitSettings::pDBDirectory()
	if (docnames.isEmpty()/* || dociterator==docnames.end() */) {
		docnames=TQDir(DOCConduitSettings::pDBDirectory(), CSL1("*.pdb")).entryList() ;
		dociterator=docnames.begin();
	}
	if (dociterator==docnames.end()) {
		// no more databases available, so start the conflict resolution and then the actual sync proces
		docnames.clear();
		TQTimer::singleShot(0, this, TQ_SLOT(checkDeletedDocs()));
		return;
	}

	TQString fn=(*dociterator);

	TQDir dr(DOCConduitSettings::pDBDirectory());
	TQFileInfo fl(dr, fn );
	TQString pdbfilename=fl.absFilePath();
	++dociterator;

	//  Get the doc title and check if it has already been synced (in the synced docs list of in fDBNames to be synced)
	// If the doc title doesn't appear in either list, install it to the Handheld, and add it to the list of dbs to be synced.
	TQString dbname=fl.baseName(true).left(30);
	if (!fDBNames.contains(dbname) && !fDBListSynced.contains(dbname)) {
		if (fHandle->installFiles(pdbfilename, false)) {
			DBInfo dbinfo;
			// Include all "extensions" except the last. This allows full stops inside the database name (e.g. abbreviations)
			// first fill everything with 0, so we won't have a buffer overflow.
			memset(&dbinfo.name[0], 0, 33);
			strncpy(&dbinfo.name[0], dbname.latin1(), 30);

			docSyncInfo syncInfo(dbname, constructTXTFileName(dbname), pdbfilename, eSyncNone);
			syncInfo.dbinfo=dbinfo;
			needsSync(syncInfo);
			fSyncInfoList.append(syncInfo);
			fDBNames.append(dbname);
		} else {
#ifdef DEBUG
			DEBUGKPILOT<<"Could not install database "<<dbname<<" ("<<pdbfilename<<") to the handheld"<<endl;
#endif
		}
	}

	TQTimer::singleShot(0, this, TQ_SLOT(checkPDBFiles()));
}



void DOCConduit::checkDeletedDocs()
{
	FUNCTIONSETUP;

	for (TQStringList::Iterator it=fDBListSynced.begin(); it!=fDBListSynced.end(); ++it ) {
		if (!fDBNames.contains(*it)) {
			// We need to delete this doc:
			TQString dbname(*it);
			TQString txtfilename=constructTXTFileName(dbname);
			TQString pdbfilename=constructPDBFileName(dbname);
			docSyncInfo syncInfo(dbname, txtfilename, pdbfilename, eSyncDelete);

			DBInfo dbinfo;
			memset(&dbinfo.name[0], 0, 33);
			strncpy(&dbinfo.name[0], dbname.latin1(), 30);
			syncInfo.dbinfo=dbinfo;

			fSyncInfoList.append(syncInfo);
		}
	}
	TQTimer::singleShot(0, this, TQ_SLOT(resolve()));
	return;
}



void DOCConduit::resolve() {
	FUNCTIONSETUP;

	for (fSyncInfoListIterator=fSyncInfoList.begin(); fSyncInfoListIterator!=fSyncInfoList.end(); ++fSyncInfoListIterator) {
		// Walk through each database and apply the conflictResolution option.
		// the remaining conflicts will be resolved in the resolution dialog
		if ((*fSyncInfoListIterator).direction==eSyncConflict){
#ifdef DEBUG
			DEBUGKPILOT<<"We have a conflict for "<<(*fSyncInfoListIterator).handheldDB<<", default="<<eConflictResolution<<endl;
#endif
			switch (eConflictResolution)
			{
				case eSyncPDAToPC:
#ifdef DEBUG
					DEBUGKPILOT<<"PDA overrides for database "<<(*fSyncInfoListIterator).handheldDB<<endl;
#endif
					(*fSyncInfoListIterator).direction = eSyncPDAToPC;
					break;
				case eSyncPCToPDA:
#ifdef DEBUG
					DEBUGKPILOT<<"PC overrides for database "<<(*fSyncInfoListIterator).handheldDB<<endl;
#endif
					(*fSyncInfoListIterator).direction = eSyncPCToPDA;
					break;
				case eSyncNone:
#ifdef DEBUG
					DEBUGKPILOT<<"No sync for database "<<(*fSyncInfoListIterator).handheldDB<<endl;
#endif
					(*fSyncInfoListIterator).direction = eSyncNone;
					break;
				case eSyncDelete:
				case eSyncConflict:
				default:
#ifdef DEBUG
					DEBUGKPILOT<<"Conflict remains due to default resolution setting for database "<<(*fSyncInfoListIterator).handheldDB<<endl;
#endif
					break;
			}
		}
	}

	// Show the conflict resolution dialog and ask for the action for each database
	ResolutionDialog*dlg=new ResolutionDialog( 0,  i18n("Conflict Resolution"), &fSyncInfoList , fHandle);
	bool show=DOCConduitSettings::alwaysShowResolutionDialog() || (dlg && dlg->hasConflicts);
	if (show) {
		if (!dlg || !dlg->exec() ) {
			KPILOT_DELETE(dlg)
			emit logMessage(i18n("Sync aborted by user."));
			TQTimer::singleShot(0, this, TQ_SLOT(cleanup()));
			return;
		}
	}
	KPILOT_DELETE(dlg)


	// fDBNames will be filled with the names of the databases that are actually synced (not deleted), so I can write the list to the config file
	fDBNames.clear();
	fSyncInfoListIterator=fSyncInfoList.begin();
	TQTimer::singleShot(0,this, TQ_SLOT(syncDatabases()));
	return;
}



void DOCConduit::syncDatabases() {
	FUNCTIONSETUP;
	if (fSyncInfoListIterator==fSyncInfoList.end()) {
		// We're done, so clean up
		TQTimer::singleShot(0, this, TQ_SLOT(cleanup()));
		return;
	}

	docSyncInfo sinfo=(*fSyncInfoListIterator);
	++fSyncInfoListIterator;

	switch (sinfo.direction) {
		case eSyncConflict:
#ifdef DEBUG
			DEBUGKPILOT<<"Entry "<<sinfo.handheldDB<<"( txtfilename: "<<sinfo.txtfilename<<
				", pdbfilename: "<<sinfo.pdbfilename<<") had sync direction eSyncConflict!!!"<<endl;
#endif
			break;
		case eSyncDelete:
		case eSyncPDAToPC:
		case eSyncPCToPDA:
			emit logMessage(i18n("Synchronizing text \"%1\"").arg(sinfo.handheldDB));
			if (!doSync(sinfo)) {
				// The sync could not be done, so inform the user (the error message should probably issued inside doSync)
#ifdef DEBUG
				DEBUGKPILOT<<"There was some error syncing the text \""<<sinfo.handheldDB<<"\" with the file "<<sinfo.txtfilename<<endl;
#endif
			}
			break;
		case eSyncNone:
//		case eSyncAll:
			break;
	}
	if (sinfo.direction != eSyncDelete) fDBNames.append(sinfo.handheldDB);

	TQTimer::singleShot(0,this, TQ_SLOT(syncDatabases()));
	return;
}


PilotDatabase*DOCConduit::openDOCDatabase(const TQString &dbname) {
	if (DOCConduitSettings::localSync())
	{
		return new PilotLocalDatabase(DOCConduitSettings::pDBDirectory(), dbname, false);
	}
	else
	{
		return deviceLink()->database( dbname );
	}
}


bool DOCConduit::needsSync(docSyncInfo &sinfo)
{
	FUNCTIONSETUP;
	sinfo.direction = eSyncNone;

	PilotDatabase*docdb=openDOCDatabase(TQString::fromLatin1(sinfo.dbinfo.name));
	if (!fDBListSynced.contains(sinfo.handheldDB)) {
		// the database wasn't included on last sync, so it has to be new.
#ifdef DEBUG
		DEBUGKPILOT<<"Database "<<sinfo.dbinfo.name<<" wasn't included in the previous sync!"<<endl;
#endif

		/* Resolution Table:
			PC  HH  |  normal      PC->HH      HH->PC
			-----------------------------------------
			N   -   |     P          P           D
			-   N   |     H          D           H
			N   N   |     C          P           H
		*/

		if (TQFile::exists(sinfo.txtfilename)) sinfo.fPCStatus=eStatNew;
		else sinfo.fPCStatus=eStatDoesntExist;
		if (docdb && docdb->isOpen()) sinfo.fPalmStatus=eStatNew;
		else sinfo.fPalmStatus=eStatDoesntExist;
		KPILOT_DELETE(docdb);

		switch (eSyncDirection) {
			case eSyncPDAToPC:
				if (sinfo.fPalmStatus==eStatDoesntExist)
					sinfo.direction=eSyncDelete;
				else sinfo.direction=eSyncPDAToPC;
				break;
			case eSyncPCToPDA:
				if (sinfo.fPCStatus==eStatDoesntExist)
					sinfo.direction=eSyncDelete;
				else sinfo.direction=eSyncPCToPDA;
				break;
			case eSyncNone: // means actually both directions!
				if (sinfo.fPCStatus==eStatNew) {
					if (sinfo.fPalmStatus==eStatNew) sinfo.direction=eSyncConflict;
					else sinfo.direction=eSyncPCToPDA;
				} else {
					if (sinfo.fPalmStatus==eStatNew) sinfo.direction=eSyncPDAToPC;
					else {
						sinfo.direction=eSyncNone;
#ifdef DEBUG
						DEBUGKPILOT<<"I'm supposed to find a sync direction, but the "<<
						" text "<<sinfo.dbinfo.name<<" doesn't exist on either "<<
						" the handheld or the PC"<<endl;
#endif
					}
				}
				break;
			default:
				break;
		}
		return true;
	}

	// Text was included in the last sync
	if (!TQFile::exists(sinfo.txtfilename)) sinfo.fPCStatus=eStatDeleted;
	else if(pcTextChanged(sinfo.txtfilename)) {
		sinfo.fPCStatus=eStatChanged;
#ifdef DEBUG
		DEBUGKPILOT<<"PC side has changed!"<<endl;
#endif
		// TODO: Check for changed bookmarks on the PC side
#ifdef DEBUG
	} else {
		DEBUGKPILOT<<"PC side has NOT changed!"<<endl;
#endif
	}

	if (!docdb || !docdb->isOpen()) sinfo.fPalmStatus=eStatDeleted;
	else if (hhTextChanged(docdb)) {
#ifdef DEBUG
		DEBUGKPILOT<<"Handheld side has changed!"<<endl;
#endif
		sinfo.fPalmStatus=eStatChanged;
#ifdef DEBUG
	} else {
		DEBUGKPILOT<<"Handheld side has NOT changed!"<<endl;
#endif
	}
	KPILOT_DELETE(docdb);


	// Now that we know the status of both sides, determine what to do.
		/* Resolution Table:
			PC  HH  |  normal      PC->HH      HH->PC
			-----------------------------------------
			-   -   |     -          -           -
			C   -   |     P          P           H
			-   C   |     H          P           H
			C   C   |     C          P           H
			D   -   |     D          D           H
			-   D   |     D          P           D
			D   D   |     D          D           D
			-----------------------------------------
			C   D   |     C          P           D
			D   C   |     C          D           H
		*/


	if (sinfo.fPCStatus == eStatNone && sinfo.fPalmStatus==eStatNone) {
#ifdef DEBUG
		DEBUGKPILOT<<"Nothing has changed, not need for a sync."<<endl;
#endif
		sinfo.direction=eSyncNone;
		return false;
	}

	// In all other cases, if only one direction (PC->HH or HH->PC)
	// should be done, check if the DB was deleted or if we are supposed
	// to sync that direction

	if (eSyncDirection==eSyncPCToPDA) {
		if (sinfo.fPCStatus==eStatDeleted) sinfo.direction=eSyncDelete;
		else sinfo.direction=eSyncPCToPDA;
		return true;
	}
	if (eSyncDirection==eSyncPDAToPC) {
		if (sinfo.fPalmStatus==eStatDeleted) sinfo.direction=eSyncDelete;
		else sinfo.direction=eSyncPDAToPC;
		return true;
	}


	// ---------------------------------------------------------------
	// Finally, do the normal case, where both directions are possible
	// ---------------------------------------------------------------


	// if either is deleted, and the other is not changed, delete
	if ( ((sinfo.fPCStatus==eStatDeleted) && (sinfo.fPalmStatus!=eStatChanged)) ||
	     ((sinfo.fPalmStatus==eStatDeleted) && (sinfo.fPCStatus!=eStatChanged)) )
	{
#ifdef DEBUG
		DEBUGKPILOT<<"DB was deleted on one side and not changed on "
			"the other -> Delete it."<<endl;
#endif
		sinfo.direction=eSyncDelete;
		return true;
	}

	// eStatDeleted (and both not changed) have already been treated, for all
	// other values in combination with eStatNone, just copy the texts.
	if (sinfo.fPCStatus==eStatNone) {
#ifdef DEBUG
		DEBUGKPILOT<<"PC side has changed!"<<endl;
#endif
		sinfo.direction=eSyncPDAToPC;
		return true;
	}

	if (sinfo.fPalmStatus==eStatNone) {
		sinfo.direction=eSyncPCToPDA;
		return true;
	}

	// All other cases
	//    (deleted,changed), (changed, deleted), (changed,changed)
	// create a conflict:
	sinfo.direction=eSyncConflict;
	return true;
}



PilotDatabase *DOCConduit::preSyncAction(docSyncInfo &sinfo) const
{
	FUNCTIONSETUP;

	{
		// make sure the dir for the local texts really exists!
		TQDir dir( DOCConduitSettings::tXTDirectory() );
		if (!dir.exists())
		{
			dir.mkdir(dir.absPath());
		}
	}

	DBInfo dbinfo=sinfo.dbinfo;
	switch (sinfo.direction)
	{
		case eSyncPDAToPC:
			if (DOCConduitSettings::keepPDBsLocally())
			{
				// make sure the dir for the local db really exists!
				TQDir dir(DOCConduitSettings::pDBDirectory());

				if (!dir.exists())
				{
					dir.mkdir(dir.absPath());
				}
#ifdef DEBUG
				DEBUGKPILOT<<"Need to fetch database "<<dbinfo.name<<
					" to the directory "<<dir.absPath()<<endl;
#endif
				dbinfo.flags &= ~dlpDBFlagOpen;

				if (!fHandle->retrieveDatabase(sinfo.pdbfilename, &dbinfo) )
				{
					WARNINGKPILOT << "Unable to retrieve database " << dbinfo.name <<
						" from the handheld into " << sinfo.pdbfilename << "." << endl;
					return 0L;
				}
			}
			break;
		case eSyncPCToPDA:
			if (DOCConduitSettings::keepPDBsLocally())
			{
				// make sure the dir for the local db really exists!
				TQDir dir(DOCConduitSettings::pDBDirectory());
				if (!dir.exists())
				{
					dir.mkdir(dir.absPath());
				}
			}
			break;
		default:
			break;
	}
	if (DOCConduitSettings::keepPDBsLocally())
	{
		return new PilotLocalDatabase(DOCConduitSettings::pDBDirectory(),
			TQString::fromLatin1(dbinfo.name), false);
	}
	else
	{
		return deviceLink()->database(TQString::fromLatin1(dbinfo.name));
	}
}


// res gives us information whether the sync worked and the db might need to be
// transferred to the handheld or not (and we just need to clean up the mess)
bool DOCConduit::postSyncAction(PilotDatabase * database,
                                docSyncInfo &sinfo, bool res)
{
	FUNCTIONSETUP;
	bool rs = true;

	switch (sinfo.direction)
	{
	case eSyncPDAToPC:
		// also reset the sync flags on the handheld
#ifdef DEBUG
		DEBUGKPILOT<<"Resetting sync flags for database "
			<<sinfo.dbinfo.name<<endl;
#endif
		if (DOCConduitSettings::keepPDBsLocally() && !DOCConduitSettings::localSync())
		{
			PilotDatabase*db=deviceLink()->database(
				TQString::fromLatin1(sinfo.dbinfo.name));
#ifdef DEBUG
			DEBUGKPILOT<<"Middle 1 Resetting sync flags for database "
				<<sinfo.dbinfo.name<<endl;
#endif
			if (db)
			{
				db->resetSyncFlags();
				KPILOT_DELETE(db);
			}
		}
#ifdef DEBUG
		DEBUGKPILOT<<"End Resetting sync flags for database "
			<<sinfo.dbinfo.name<<endl;
#endif
		break;
	case eSyncPCToPDA:
		if (DOCConduitSettings::keepPDBsLocally() && !DOCConduitSettings::localSync() && res)
		{
			// Copy the database to the palm
			PilotLocalDatabase*localdb=dynamic_cast<PilotLocalDatabase*>(database);
			if (localdb)
			{
#ifdef DEBUG
				DEBUGKPILOT<<"Installing file "<<localdb->dbPathName()<<" ("
					<<sinfo.handheldDB<<") to the handheld"<<endl;
#endif
				TQString dbpathname=localdb->dbPathName();
				// This deletes localdb as well, which is just a cast from database
				KPILOT_DELETE(database);
				if (!fHandle->installFiles(dbpathname, false))
				{
					rs = false;
#ifdef DEBUG
					DEBUGKPILOT<<"Could not install the database "<<dbpathname<<" ("
						<<sinfo.handheldDB<<")"<<endl;
#endif
				}
			}
		}
	default:
		break;
	}

#ifdef DEBUG
	DEBUGKPILOT<<"Vor KPILOT_DELETE(database)"<<endl;
#endif

	KPILOT_DELETE(database);
#ifdef DEBUG
	DEBUGKPILOT<<"End postSyncAction"<<endl;
#endif
	return rs;
}



void DOCConduit::cleanup()
{
	FUNCTIONSETUP;
	DOCConduitSettings::setConvertedDOCfiles( fDBNames );
	DOCConduitSettings::self()->writeConfig();

	emit syncDone(this);
}

