/* 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 #include #include #include #include #include #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 "<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: "<readNextModifiedRec(&modRecInd); #ifdef DEBUG DEBUGKPILOT<<"Reread Index of first changed records: "<= 0) { #ifdef DEBUG DEBUGKPILOT<<"Handheld side has changed, condition="<< ((!DOCConduitSettings::ignoreBmkChanges()) || (modRecInd <= storyRecs))<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 "<createDatabase(dbcreator(), dbtype()) ) { #ifdef DEBUG DEBUGKPILOT<<"Failed"<isOpen()) { DOCConverter docconverter; connect(&docconverter, TQT_SIGNAL(logError(const TQString &)), TQT_SIGNAL(logError(const TQString &))); connect(&docconverter, TQT_SIGNAL(logMessage(const TQString &)), TQT_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(*TQT_TQIODEVICE(&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 "<findDatabase(NULL, &dbinfo, dbnr, dbtype(), dbcreator() /*, cardno */ ) < 0) { // no more databases available, so check for PC->Palm sync TQTimer::singleShot(0, this, TQT_SLOT(syncNextTXT())); return; } dbnr=dbinfo.index+1; #ifdef DEBUG DEBUGKPILOT<<"Next Palm database to sync: "<Palm sync TQTimer::singleShot(0, this, TQT_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, TQT_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 "<hasConflicts); if (show) { if (!dlg || !dlg->exec() ) { KPILOT_DELETE(dlg) emit logMessage(i18n("Sync aborted by user.")); TQTimer::singleShot(0, this, TQT_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, TQT_SLOT(syncDatabases())); return; } void DOCConduit::syncDatabases() { FUNCTIONSETUP; if (fSyncInfoListIterator==fSyncInfoList.end()) { // We're done, so clean up TQTimer::singleShot(0, this, TQT_SLOT(cleanup())); return; } docSyncInfo sinfo=(*fSyncInfoListIterator); ++fSyncInfoListIterator; switch (sinfo.direction) { case eSyncConflict: #ifdef DEBUG DEBUGKPILOT<<"Entry "<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 "<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 "<isOpen()) sinfo.fPalmStatus=eStatDeleted; else if (hhTextChanged(docdb)) { #ifdef DEBUG DEBUGKPILOT<<"Handheld side has changed!"<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."<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."<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 " <database( TQString::fromLatin1(sinfo.dbinfo.name)); #ifdef DEBUG DEBUGKPILOT<<"Middle 1 Resetting sync flags for database " <resetSyncFlags(); KPILOT_DELETE(db); } } #ifdef DEBUG DEBUGKPILOT<<"End Resetting sync flags for database " <(database); if (localdb) { #ifdef DEBUG DEBUGKPILOT<<"Installing file "<dbPathName()<<" (" <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 "<writeConfig(); emit syncDone(this); }