kpilot

doc-conduit.cc

Go to the documentation of this file.
00001 /* KPilot
00002 **
00003 ** Copyright (C) 2002 by Reinhold Kainhofer
00004 **
00005 ** The doc conduit synchronizes text files on the PC with DOC databases on the Palm
00006 */
00007 
00008 /*
00009 ** This program is free software; you can redistribute it and/or modify
00010 ** it under the terms of the GNU General Public License as published by
00011 ** the Free Software Foundation; either version 2 of the License, or
00012 ** (at your option) any later version.
00013 **
00014 ** This program is distributed in the hope that it will be useful,
00015 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
00016 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00017 ** GNU General Public License for more details.
00018 **
00019 ** You should have received a copy of the GNU General Public License
00020 ** along with this program in a file called COPYING; if not, write to
00021 ** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
00022 ** MA 02110-1301, USA.
00023 */
00024 
00025 /*
00026 ** Bug reports and questions can be sent to kde-pim@kde.org.
00027 */
00028 
00029 
00030 // naming of the bookmark file:
00031 // PDB->TXT:    convert bookmarks to a .bm file
00032 // TXT->PDB:    If a .bmk file exists, use it, otherwise use the .bm file (from the PDB->TXT conversion)
00033 //          This way, the bookmark file is not overwritten, a manual bookmark file overrides, but the bookmarks from the handheld are still available
00034 
00035 
00036 #include "options.h"
00037 #include "doc-conduit.moc"
00038 
00039 #include <qtimer.h>
00040 #include <qdir.h>
00041 
00042 #include <kconfig.h>
00043 #include <kmdcodec.h>
00044 
00045 #include <pilotLocalDatabase.h>
00046 #include <pilotSerialDatabase.h>
00047 
00048 #include "doc-factory.h"
00049 #include "doc-conflictdialog.h"
00050 #include "DOC-converter.h"
00051 #include "pilotDOCHead.h"
00052 #include "docconduitSettings.h"
00053 
00054 
00055 // Something to allow us to check what revision
00056 // the modules are that make up a binary distribution.
00057 extern "C"
00058 {
00059 unsigned long version_conduit_doc = Pilot::PLUGIN_API;
00060 }
00061 
00062 QString dirToString(eSyncDirectionEnum dir) {
00063     switch(dir) {
00064 //      case eSyncAll: return "eSyncAll";
00065         case eSyncPDAToPC: return CSL1("eSyncPDAToPC");
00066         case eSyncPCToPDA: return CSL1("eSyncPCToPDA");
00067         case eSyncNone: return CSL1("eSyncNone");
00068         case eSyncConflict: return CSL1("eSyncConflict");
00069         case eSyncDelete: return CSL1("eSyncDelete");
00070         default: return CSL1("ERROR");
00071     }
00072 }
00073 
00074 
00075 /*********************************************************************
00076                         C O N S T R U C T O R
00077  *********************************************************************/
00078 
00079 
00080 DOCConduit::DOCConduit(KPilotLink * o,
00081     const char *n, const QStringList & a):ConduitAction(o, n, a)
00082 {
00083     FUNCTIONSETUP;
00084     fConduitName=i18n("DOC");
00085 }
00086 
00087 
00088 
00089 DOCConduit::~DOCConduit()
00090 {
00091     FUNCTIONSETUP;
00092 }
00093 
00094 
00095 bool DOCConduit::isCorrectDBTypeCreator(DBInfo dbinfo) {
00096     return dbinfo.type == dbtype() && dbinfo.creator == dbcreator();
00097 }
00098 const unsigned long DOCConduit::dbtype() {
00099     return get_long(DOCConduitFactory::dbDOCtype);
00100 }
00101 const unsigned long DOCConduit::dbcreator() {
00102     return get_long(DOCConduitFactory::dbDOCcreator);
00103 }
00104 
00105 
00106 
00107 /*********************************************************************
00108                 L O A D I N G   T H E   D A T A
00109  *********************************************************************/
00110 
00111 
00112 
00113 void DOCConduit::readConfig()
00114 {
00115     FUNCTIONSETUP;
00116     DOCConduitSettings::self()->readConfig();
00117 
00118     eConflictResolution = (enum eSyncDirectionEnum) (DOCConduitSettings::conflictResolution() );
00119     fTXTBookmarks = DOCConverter::eBmkNone;
00120     if ( DOCConduitSettings::convertBookmarks() )
00121     {
00122         if ( DOCConduitSettings::bmkFileBookmarks() )
00123             fTXTBookmarks |= DOCConverter::eBmkFile;
00124         if ( DOCConduitSettings::inlineBookmarks() )
00125             fTXTBookmarks |= DOCConverter::eBmkInline;
00126         if ( DOCConduitSettings::endtagBookmarks() )
00127             fTXTBookmarks |= DOCConverter::eBmkEndtags;
00128     }
00129 
00130     eSyncDirection = (enum eSyncDirectionEnum)(DOCConduitSettings::syncDirection() );
00131 
00132 #ifdef DEBUG
00133     DEBUGKPILOT << fname
00134         << ": Settings "
00135         << " tXTDirectory=" << DOCConduitSettings::tXTDirectory()
00136         << " pDBDirectory=" << DOCConduitSettings::pDBDirectory()
00137         << " keepPDBLocally=" << DOCConduitSettings::keepPDBsLocally()
00138         << " eConflictResolution=" << eConflictResolution
00139         << " tXTBookmarks=" << fTXTBookmarks
00140         << " pDBBookmarks=" << DOCConduitSettings::bookmarksToPC()
00141         << " compress=" << DOCConduitSettings::compress()
00142         << " eSyncDirection=" << eSyncDirection << endl;
00143 #endif
00144 }
00145 
00146 
00147 
00148 bool DOCConduit::pcTextChanged(QString txtfn)
00149 {
00150     FUNCTIONSETUP;
00151     // How do I find out if a text file has changed shince we last synced it??
00152     // Use KMD5 for now. If I realize it is too slow, then I have to go back to comparing modification times
00153     // 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.
00154     QString oldDigest=DOCConduitSettings::self()->config()->readEntry(txtfn);
00155     if (oldDigest.length()<=0)
00156     {
00157         return true;
00158     }
00159 #ifdef DEBUG
00160     DEBUGKPILOT<<"Old digest is "<<oldDigest<<endl;
00161 #endif
00162 
00163     KMD5 docmd5;
00164     QFile txtfile(txtfn);
00165     if (txtfile.open(IO_ReadOnly)){
00166         docmd5.update(txtfile);
00167         QString thisDigest(docmd5.hexDigest() /* .data() */);
00168 #ifdef DEBUG
00169         DEBUGKPILOT<<"New digest is "<<thisDigest<<endl;
00170 #endif
00171         return (thisDigest.length()<=0) || (thisDigest!=oldDigest);
00172     } else {
00173         // File does not exist. This should actually never happen. Anyways, just return true to indicate it has changed.
00174         // doSync should detect this and delete the doc from the handheld.
00175         return true;
00176     }
00177     return false;
00178 }
00179 
00180 
00181 
00182 bool DOCConduit::hhTextChanged(PilotDatabase*docdb)
00183 {
00184     FUNCTIONSETUP;
00185     if (!docdb) return false;
00186 
00187     PilotRecord *firstRec = docdb->readRecordByIndex(0);
00188     PilotDOCHead docHeader(firstRec);
00189     KPILOT_DELETE(firstRec);
00190 
00191     int storyRecs = docHeader.numRecords;
00192 
00193     // determine the index of the next modified record (does it lie
00194     // beyond the actual text records?)
00195     int modRecInd=-1;
00196     PilotRecord*modRec=docdb->readNextModifiedRec(&modRecInd);
00197 #ifdef DEBUG
00198     DEBUGKPILOT<<"Index of first changed record: "<<modRecInd<<endl;
00199 #endif
00200 
00201     KPILOT_DELETE(modRec);
00202     // if the header record was changed, find out which is the first changed
00203     // real document record:
00204     if (modRecInd==0) {
00205         modRec=docdb->readNextModifiedRec(&modRecInd);
00206 #ifdef DEBUG
00207         DEBUGKPILOT<<"Reread Index of first changed records: "<<modRecInd<<endl;
00208 #endif
00209         KPILOT_DELETE(modRec);
00210     }
00211 
00212     // The record index starts with 0, so only a negative number means
00213     // no modified record was found
00214     if (modRecInd >= 0) {
00215 #ifdef DEBUG
00216         DEBUGKPILOT<<"Handheld side has changed, condition="<<
00217             ((!DOCConduitSettings::ignoreBmkChanges()) || (modRecInd <= storyRecs))<<endl;
00218 #endif
00219         if ((!DOCConduitSettings::ignoreBmkChanges()) || (modRecInd <= storyRecs))
00220             return true;
00221     } else {
00222 #ifdef DEBUG
00223         DEBUGKPILOT<<"Handheld side has NOT changed!"<<endl;
00224 #endif
00225         return false;
00226     }
00227     return false;
00228 }
00229 
00230 
00231 
00232 /*********************************************************************
00233  *     Helper functions
00234  ********************************************************************/
00235 
00236 QString DOCConduit::constructPDBFileName(QString name) {
00237     FUNCTIONSETUP;
00238     QString fn;
00239     QDir dr(DOCConduitSettings::pDBDirectory());
00240     QFileInfo pth(dr, name);
00241     if (!name.isEmpty()) fn=pth.absFilePath()+CSL1(".pdb");
00242     return fn;
00243 }
00244 QString DOCConduit::constructTXTFileName(QString name) {
00245     FUNCTIONSETUP;
00246     QString fn;
00247     QDir dr( DOCConduitSettings::tXTDirectory() );
00248     QFileInfo pth(dr, name);
00249     if (!name.isEmpty()) fn=pth.absFilePath()+CSL1(".txt");
00250     return fn;
00251 }
00252 
00253 
00254 
00255 
00256 
00257 /*********************************************************************
00258                 S Y N C   S T R U C T U R E
00259  *********************************************************************/
00260 
00261 
00262 
00263 
00264 
00265 /* virtual */ bool DOCConduit::exec()
00266 {
00267     FUNCTIONSETUP;
00268 
00269     readConfig();
00270     dbnr=0;
00271 
00272     emit logMessage(i18n("Searching for texts and databases to synchronize"));
00273 
00274     QTimer::singleShot(0, this, SLOT(syncNextDB()));
00275     return true;
00276 }
00277 
00278 
00279 
00280 bool DOCConduit::doSync(docSyncInfo &sinfo)
00281 {
00282     FUNCTIONSETUP;
00283     bool res=false;
00284 
00285     if (sinfo.direction==eSyncDelete) {
00286         if (!sinfo.txtfilename.isEmpty()) {
00287             if (!QFile::remove(sinfo.txtfilename)) {
00288                 WARNINGKPILOT << "Unable to delete the text file " << sinfo.txtfilename << " on the PC" << endl;
00289             }
00290             QString bmkfilename = sinfo.txtfilename;
00291             if (bmkfilename.endsWith(CSL1(".txt"))){
00292                 bmkfilename.remove(bmkfilename.length()-4, 4);
00293             }
00294             bmkfilename+=CSL1(PDBBMK_SUFFIX);
00295             if (!QFile::remove(bmkfilename)) {
00296 #ifdef DEBUG
00297                 DEBUGKPILOT<<"Could not remove bookmarks file "<<bmkfilename<<" for database "<<sinfo.handheldDB<<endl;
00298 #endif
00299             }
00300         }
00301         if (!sinfo.pdbfilename.isEmpty() && DOCConduitSettings::keepPDBsLocally() ) {
00302             PilotLocalDatabase*database=new PilotLocalDatabase(DOCConduitSettings::pDBDirectory(),
00303                 QString::fromLatin1(sinfo.dbinfo.name), false);
00304             if (database) {
00305                 if ( database->deleteDatabase() !=0 ) {
00306                     WARNINGKPILOT << "Unable to delete database " << sinfo.dbinfo.name << " on the PC" << endl;
00307                 }
00308                 KPILOT_DELETE(database);
00309             }
00310         }
00311         if (!DOCConduitSettings::localSync()) {
00312             PilotDatabase *database=deviceLink()->database( sinfo.dbinfo.name );
00313             if ( database->deleteDatabase() !=0 ) {
00314                     WARNINGKPILOT << "Unable to delete database " << sinfo.dbinfo.name << " from the handheld" << endl;
00315             }
00316             KPILOT_DELETE(database);
00317         }
00318         return true;
00319     }
00320     // preSyncAction should initialize the custom databases/files for the
00321     // specific action chosen for this db and return a pointer to a docDBInfo
00322     // instance which points either to a local database or a database on the handheld.
00323     PilotDatabase *database = preSyncAction(sinfo);
00324 
00325     if (database && ( !database->isOpen() ) ) {
00326 #ifdef DEBUG
00327         DEBUGKPILOT<<"Database "<<sinfo.dbinfo.name<<" does not yet exist. Creating it:"<<endl;
00328 #endif
00329         if (!database->createDatabase(dbcreator(), dbtype()) ) {
00330 #ifdef DEBUG
00331             DEBUGKPILOT<<"Failed"<<endl;
00332             emit logMessage(i18n("Database created."));
00333 #endif
00334         }
00335     }
00336 
00337     if (database && database->isOpen()) {
00338         DOCConverter docconverter;
00339         connect(&docconverter, SIGNAL(logError(const QString &)), SIGNAL(logError(const QString &)));
00340         connect(&docconverter, SIGNAL(logMessage(const QString &)), SIGNAL(logMessage(const QString &)));
00341 
00342         docconverter.setTXTpath( DOCConduitSettings::tXTDirectory(), sinfo.txtfilename );
00343         docconverter.setPDB(database);
00344         docconverter.setCompress(DOCConduitSettings::compress());
00345 
00346         switch (sinfo.direction) {
00347             case eSyncPDAToPC:
00348                 docconverter.setBookmarkTypes(DOCConduitSettings::bookmarksToPC());
00349                 res = docconverter.convertPDBtoTXT();
00350                 break;
00351             case eSyncPCToPDA:
00352                 docconverter.setBookmarkTypes(fTXTBookmarks);
00353                 res = docconverter.convertTXTtoPDB();
00354                 break;
00355             default:
00356                 break;
00357         }
00358 
00359         // Now calculate the md5 checksum of the PC text and write it to the config file
00360         if (res)
00361         {
00362             KMD5 docmd5;
00363             QFile txtfile(docconverter.txtFilename());
00364             if (txtfile.open(IO_ReadOnly)) {
00365                 docmd5.update(txtfile);
00366                 QString thisDigest(docmd5.hexDigest() /* .data() */);
00367                 DOCConduitSettings::self()->config()->writeEntry(docconverter.txtFilename(), thisDigest);
00368                 DOCConduitSettings::self()->config()->sync();
00369 #ifdef DEBUG
00370                 DEBUGKPILOT<<"MD5 Checksum of the text "<<sinfo.txtfilename<<" is "<<thisDigest<<endl;
00371 #endif
00372             } else {
00373 #ifdef DEBUG
00374                 DEBUGKPILOT<<"couldn't open file "<<docconverter.txtFilename()<<" for reading!!!"<<endl;
00375 #endif
00376             }
00377         }
00378 
00379         if (!postSyncAction(database, sinfo, res))
00380             emit logError(i18n("Unable to install the locally created PalmDOC %1 to the handheld.")
00381                 .arg(QString::fromLatin1(sinfo.dbinfo.name)));
00382         if (!res)
00383             emit logError(i18n("Conversion of PalmDOC \"%1\" failed.")
00384                 .arg(QString::fromLatin1(sinfo.dbinfo.name)));
00385 //      disconnect(&docconverter, SIGNAL(logError(const QString &)), SIGNAL(logError(const QString &)));
00386 //      disconnect(&docconverter, SIGNAL(logMessage(const QString &)), SIGNAL(logMessage(const QString &)));
00387 //      KPILOT_DELETE(database);
00388     }
00389     else
00390     {
00391         emit logError(i18n("Unable to open or create the database %1.")
00392             .arg(QString::fromLatin1(sinfo.dbinfo.name)));
00393     }
00394     return res;
00395 }
00396 
00397 
00400 void DOCConduit::syncNextDB() {
00401     FUNCTIONSETUP;
00402     DBInfo dbinfo;
00403 
00404     if (eSyncDirection==eSyncPCToPDA || fHandle->findDatabase(NULL, &dbinfo, dbnr, dbtype(), dbcreator() /*, cardno */ ) < 0)
00405     {
00406         // no more databases available, so check for PC->Palm sync
00407         QTimer::singleShot(0, this, SLOT(syncNextTXT()));
00408         return;
00409     }
00410     dbnr=dbinfo.index+1;
00411 #ifdef DEBUG
00412     DEBUGKPILOT<<"Next Palm database to sync: "<<dbinfo.name<<", Index="<<dbinfo.index<<endl;
00413 #endif
00414 
00415     // if creator and/or type don't match, go to next db
00416     if (!isCorrectDBTypeCreator(dbinfo) ||
00417         fDBNames.contains(QString::fromLatin1(dbinfo.name)))
00418     {
00419         QTimer::singleShot(0, this, SLOT(syncNextDB()));
00420         return;
00421     }
00422 
00423     QString txtfilename=constructTXTFileName(QString::fromLatin1(dbinfo.name));
00424     QString pdbfilename=constructPDBFileName(QString::fromLatin1(dbinfo.name));
00425 
00426     docSyncInfo syncInfo(QString::fromLatin1(dbinfo.name),
00427         txtfilename, pdbfilename, eSyncNone);
00428     syncInfo.dbinfo=dbinfo;
00429     needsSync(syncInfo);
00430     fSyncInfoList.append(syncInfo);
00431     fDBNames.append(QString::fromLatin1(dbinfo.name));
00432 
00433     QTimer::singleShot(0, this, SLOT(syncNextDB()));
00434     return;
00435 }
00436 
00437 
00438 
00439 void DOCConduit::syncNextTXT()
00440 {
00441     FUNCTIONSETUP;
00442 
00443     if (eSyncDirection==eSyncPDAToPC  )
00444     {
00445         // We don't sync from PC to PDB, so start the conflict resolution and then the actual sync process
00446         docnames.clear();
00447         QTimer::singleShot(0, this, SLOT(checkPDBFiles()));
00448         return;
00449     }
00450 
00451     // if docnames isn't initialized, get a list of all *.txt files in DOCConduitSettings::tXTDirectory()
00452     if (docnames.isEmpty()/* || dociterator==docnames.end() */) {
00453         docnames=QDir( DOCConduitSettings::tXTDirectory(), CSL1("*.txt")).entryList() ;
00454         dociterator=docnames.begin();
00455     }
00456     if (dociterator==docnames.end()) {
00457         // no more databases available, so start the conflict resolution and then the actual sync proces
00458         docnames.clear();
00459         QTimer::singleShot(0, this, SLOT(checkPDBFiles()));
00460         return;
00461     }
00462 
00463     QString fn=(*dociterator);
00464 
00465     QDir dr( DOCConduitSettings::tXTDirectory() );
00466     QFileInfo fl(dr, fn );
00467     QString txtfilename=fl.absFilePath();
00468     QString pdbfilename;
00469     ++dociterator;
00470 
00471     DBInfo dbinfo;
00472     // Include all "extensions" except the last. This allows full stops inside the database name (e.g. abbreviations)
00473     // first fill everything with 0, so we won't have a buffer overflow.
00474     memset(&dbinfo.name[0], 0, 33);
00475     strncpy(&dbinfo.name[0], fl.baseName(TRUE).latin1(), 30);
00476 
00477     bool alreadySynced=fDBNames.contains(fl.baseName(TRUE));
00478     if (!alreadySynced) {
00479         docSyncInfo syncInfo(QString::fromLatin1(dbinfo.name),
00480             txtfilename, pdbfilename, eSyncNone);
00481         syncInfo.dbinfo=dbinfo;
00482         needsSync(syncInfo);
00483         fSyncInfoList.append(syncInfo);
00484         fDBNames.append(QString::fromLatin1(dbinfo.name));
00485     } else {
00486 #ifdef DEBUG
00487         DEBUGKPILOT<<txtfilename<<" has already been synced, skipping it."<<endl;
00488 #endif
00489     }
00490 
00491     QTimer::singleShot(0, this, SLOT(syncNextTXT()));
00492     return;
00493 }
00494 
00495 
00496 
00499 void DOCConduit::checkPDBFiles() {
00500     FUNCTIONSETUP;
00501 
00502     if ( DOCConduitSettings::localSync() || !DOCConduitSettings::keepPDBsLocally() || eSyncDirection==eSyncPCToPDA )
00503     {
00504         // no more databases available, so check for PC->Palm sync
00505         QTimer::singleShot(0, this, SLOT(checkDeletedDocs()));
00506         return;
00507     }
00508 
00509     // Walk through all files in the pdb directory and check if it has already been synced.
00510     // if docnames isn't initialized, get a list of all *.pdb files in DOCConduitSettings::pDBDirectory()
00511     if (docnames.isEmpty()/* || dociterator==docnames.end() */) {
00512         docnames=QDir(DOCConduitSettings::pDBDirectory(), CSL1("*.pdb")).entryList() ;
00513         dociterator=docnames.begin();
00514     }
00515     if (dociterator==docnames.end()) {
00516         // no more databases available, so start the conflict resolution and then the actual sync proces
00517         docnames.clear();
00518         QTimer::singleShot(0, this, SLOT(checkDeletedDocs()));
00519         return;
00520     }
00521 
00522     QString fn=(*dociterator);
00523 
00524     QDir dr(DOCConduitSettings::pDBDirectory());
00525     QFileInfo fl(dr, fn );
00526     QString pdbfilename=fl.absFilePath();
00527     ++dociterator;
00528 
00529     //  Get the doc title and check if it has already been synced (in the synced docs list of in fDBNames to be synced)
00530     // 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.
00531     QString dbname=fl.baseName(TRUE).left(30);
00532     if (!fDBNames.contains(dbname) && !fDBListSynced.contains(dbname)) {
00533         if (fHandle->installFiles(pdbfilename, false)) {
00534             DBInfo dbinfo;
00535             // Include all "extensions" except the last. This allows full stops inside the database name (e.g. abbreviations)
00536             // first fill everything with 0, so we won't have a buffer overflow.
00537             memset(&dbinfo.name[0], 0, 33);
00538             strncpy(&dbinfo.name[0], dbname.latin1(), 30);
00539 
00540             docSyncInfo syncInfo(dbname, constructTXTFileName(dbname), pdbfilename, eSyncNone);
00541             syncInfo.dbinfo=dbinfo;
00542             needsSync(syncInfo);
00543             fSyncInfoList.append(syncInfo);
00544             fDBNames.append(dbname);
00545         } else {
00546 #ifdef DEBUG
00547             DEBUGKPILOT<<"Could not install database "<<dbname<<" ("<<pdbfilename<<") to the handheld"<<endl;
00548 #endif
00549         }
00550     }
00551 
00552     QTimer::singleShot(0, this, SLOT(checkPDBFiles()));
00553 }
00554 
00555 
00556 
00557 void DOCConduit::checkDeletedDocs()
00558 {
00559     FUNCTIONSETUP;
00560 
00561     for (QStringList::Iterator it=fDBListSynced.begin(); it!=fDBListSynced.end(); ++it ) {
00562         if (!fDBNames.contains(*it)) {
00563             // We need to delete this doc:
00564             QString dbname(*it);
00565             QString txtfilename=constructTXTFileName(dbname);
00566             QString pdbfilename=constructPDBFileName(dbname);
00567             docSyncInfo syncInfo(dbname, txtfilename, pdbfilename, eSyncDelete);
00568 
00569             DBInfo dbinfo;
00570             memset(&dbinfo.name[0], 0, 33);
00571             strncpy(&dbinfo.name[0], dbname.latin1(), 30);
00572             syncInfo.dbinfo=dbinfo;
00573 
00574             fSyncInfoList.append(syncInfo);
00575         }
00576     }
00577     QTimer::singleShot(0, this, SLOT(resolve()));
00578     return;
00579 }
00580 
00581 
00582 
00583 void DOCConduit::resolve() {
00584     FUNCTIONSETUP;
00585 
00586     for (fSyncInfoListIterator=fSyncInfoList.begin(); fSyncInfoListIterator!=fSyncInfoList.end(); ++fSyncInfoListIterator) {
00587         // Walk through each database and apply the conflictResolution option.
00588         // the remaining conflicts will be resolved in the resolution dialog
00589         if ((*fSyncInfoListIterator).direction==eSyncConflict){
00590 #ifdef DEBUG
00591             DEBUGKPILOT<<"We have a conflict for "<<(*fSyncInfoListIterator).handheldDB<<", default="<<eConflictResolution<<endl;
00592 #endif
00593             switch (eConflictResolution)
00594             {
00595                 case eSyncPDAToPC:
00596 #ifdef DEBUG
00597                     DEBUGKPILOT<<"PDA overrides for database "<<(*fSyncInfoListIterator).handheldDB<<endl;
00598 #endif
00599                     (*fSyncInfoListIterator).direction = eSyncPDAToPC;
00600                     break;
00601                 case eSyncPCToPDA:
00602 #ifdef DEBUG
00603                     DEBUGKPILOT<<"PC overrides for database "<<(*fSyncInfoListIterator).handheldDB<<endl;
00604 #endif
00605                     (*fSyncInfoListIterator).direction = eSyncPCToPDA;
00606                     break;
00607                 case eSyncNone:
00608 #ifdef DEBUG
00609                     DEBUGKPILOT<<"No sync for database "<<(*fSyncInfoListIterator).handheldDB<<endl;
00610 #endif
00611                     (*fSyncInfoListIterator).direction = eSyncNone;
00612                     break;
00613                 case eSyncDelete:
00614                 case eSyncConflict:
00615                 default:
00616 #ifdef DEBUG
00617                     DEBUGKPILOT<<"Conflict remains due to default resolution setting for database "<<(*fSyncInfoListIterator).handheldDB<<endl;
00618 #endif
00619                     break;
00620             }
00621         }
00622     }
00623 
00624     // Show the conflict resolution dialog and ask for the action for each database
00625     ResolutionDialog*dlg=new ResolutionDialog( 0,  i18n("Conflict Resolution"), &fSyncInfoList , fHandle);
00626     bool show=DOCConduitSettings::alwaysShowResolutionDialog() || (dlg && dlg->hasConflicts);
00627     if (show) {
00628         if (!dlg || !dlg->exec() ) {
00629             KPILOT_DELETE(dlg)
00630             emit logMessage(i18n("Sync aborted by user."));
00631             QTimer::singleShot(0, this, SLOT(cleanup()));
00632             return;
00633         }
00634     }
00635     KPILOT_DELETE(dlg)
00636 
00637 
00638     // 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
00639     fDBNames.clear();
00640     fSyncInfoListIterator=fSyncInfoList.begin();
00641     QTimer::singleShot(0,this, SLOT(syncDatabases()));
00642     return;
00643 }
00644 
00645 
00646 
00647 void DOCConduit::syncDatabases() {
00648     FUNCTIONSETUP;
00649     if (fSyncInfoListIterator==fSyncInfoList.end()) {
00650         // We're done, so clean up
00651         QTimer::singleShot(0, this, SLOT(cleanup()));
00652         return;
00653     }
00654 
00655     docSyncInfo sinfo=(*fSyncInfoListIterator);
00656     ++fSyncInfoListIterator;
00657 
00658     switch (sinfo.direction) {
00659         case eSyncConflict:
00660 #ifdef DEBUG
00661             DEBUGKPILOT<<"Entry "<<sinfo.handheldDB<<"( txtfilename: "<<sinfo.txtfilename<<
00662                 ", pdbfilename: "<<sinfo.pdbfilename<<") had sync direction eSyncConflict!!!"<<endl;
00663 #endif
00664             break;
00665         case eSyncDelete:
00666         case eSyncPDAToPC:
00667         case eSyncPCToPDA:
00668             emit logMessage(i18n("Synchronizing text \"%1\"").arg(sinfo.handheldDB));
00669             if (!doSync(sinfo)) {
00670                 // The sync could not be done, so inform the user (the error message should probably issued inside doSync)
00671 #ifdef DEBUG
00672                 DEBUGKPILOT<<"There was some error syncing the text \""<<sinfo.handheldDB<<"\" with the file "<<sinfo.txtfilename<<endl;
00673 #endif
00674             }
00675             break;
00676         case eSyncNone:
00677 //      case eSyncAll:
00678             break;
00679     }
00680     if (sinfo.direction != eSyncDelete) fDBNames.append(sinfo.handheldDB);
00681 
00682     QTimer::singleShot(0,this, SLOT(syncDatabases()));
00683     return;
00684 }
00685 
00686 
00687 PilotDatabase*DOCConduit::openDOCDatabase(const QString &dbname) {
00688     if (DOCConduitSettings::localSync())
00689     {
00690         return new PilotLocalDatabase(DOCConduitSettings::pDBDirectory(), dbname, false);
00691     }
00692     else
00693     {
00694         return deviceLink()->database( dbname );
00695     }
00696 }
00697 
00698 
00699 bool DOCConduit::needsSync(docSyncInfo &sinfo)
00700 {
00701     FUNCTIONSETUP;
00702     sinfo.direction = eSyncNone;
00703 
00704     PilotDatabase*docdb=openDOCDatabase(QString::fromLatin1(sinfo.dbinfo.name));
00705     if (!fDBListSynced.contains(sinfo.handheldDB)) {
00706         // the database wasn't included on last sync, so it has to be new.
00707 #ifdef DEBUG
00708         DEBUGKPILOT<<"Database "<<sinfo.dbinfo.name<<" wasn't included in the previous sync!"<<endl;
00709 #endif
00710 
00711         /* Resolution Table:
00712             PC  HH  |  normal      PC->HH      HH->PC
00713             -----------------------------------------
00714             N   -   |     P          P           D
00715             -   N   |     H          D           H
00716             N   N   |     C          P           H
00717         */
00718 
00719         if (QFile::exists(sinfo.txtfilename)) sinfo.fPCStatus=eStatNew;
00720         else sinfo.fPCStatus=eStatDoesntExist;
00721         if (docdb && docdb->isOpen()) sinfo.fPalmStatus=eStatNew;
00722         else sinfo.fPalmStatus=eStatDoesntExist;
00723         KPILOT_DELETE(docdb);
00724 
00725         switch (eSyncDirection) {
00726             case eSyncPDAToPC:
00727                 if (sinfo.fPalmStatus==eStatDoesntExist)
00728                     sinfo.direction=eSyncDelete;
00729                 else sinfo.direction=eSyncPDAToPC;
00730                 break;
00731             case eSyncPCToPDA:
00732                 if (sinfo.fPCStatus==eStatDoesntExist)
00733                     sinfo.direction=eSyncDelete;
00734                 else sinfo.direction=eSyncPCToPDA;
00735                 break;
00736             case eSyncNone: // means actually both directions!
00737                 if (sinfo.fPCStatus==eStatNew) {
00738                     if (sinfo.fPalmStatus==eStatNew) sinfo.direction=eSyncConflict;
00739                     else sinfo.direction=eSyncPCToPDA;
00740                 } else {
00741                     if (sinfo.fPalmStatus==eStatNew) sinfo.direction=eSyncPDAToPC;
00742                     else {
00743                         sinfo.direction=eSyncNone;
00744 #ifdef DEBUG
00745                         DEBUGKPILOT<<"I'm supposed to find a sync direction, but the "<<
00746                         " text "<<sinfo.dbinfo.name<<" doesn't exist on either "<<
00747                         " the handheld or the PC"<<endl;
00748 #endif
00749                     }
00750                 }
00751                 break;
00752             default:
00753                 break;
00754         }
00755         return true;
00756     }
00757 
00758     // Text was included in the last sync
00759     if (!QFile::exists(sinfo.txtfilename)) sinfo.fPCStatus=eStatDeleted;
00760     else if(pcTextChanged(sinfo.txtfilename)) {
00761         sinfo.fPCStatus=eStatChanged;
00762 #ifdef DEBUG
00763         DEBUGKPILOT<<"PC side has changed!"<<endl;
00764 #endif
00765         // TODO: Check for changed bookmarks on the PC side
00766 #ifdef DEBUG
00767     } else {
00768         DEBUGKPILOT<<"PC side has NOT changed!"<<endl;
00769 #endif
00770     }
00771 
00772     if (!docdb || !docdb->isOpen()) sinfo.fPalmStatus=eStatDeleted;
00773     else if (hhTextChanged(docdb)) {
00774 #ifdef DEBUG
00775         DEBUGKPILOT<<"Handheld side has changed!"<<endl;
00776 #endif
00777         sinfo.fPalmStatus=eStatChanged;
00778 #ifdef DEBUG
00779     } else {
00780         DEBUGKPILOT<<"Handheld side has NOT changed!"<<endl;
00781 #endif
00782     }
00783     KPILOT_DELETE(docdb);
00784 
00785 
00786     // Now that we know the status of both sides, determine what to do.
00787         /* Resolution Table:
00788             PC  HH  |  normal      PC->HH      HH->PC
00789             -----------------------------------------
00790             -   -   |     -          -           -
00791             C   -   |     P          P           H
00792             -   C   |     H          P           H
00793             C   C   |     C          P           H
00794             D   -   |     D          D           H
00795             -   D   |     D          P           D
00796             D   D   |     D          D           D
00797             -----------------------------------------
00798             C   D   |     C          P           D
00799             D   C   |     C          D           H
00800         */
00801 
00802 
00803     if (sinfo.fPCStatus == eStatNone && sinfo.fPalmStatus==eStatNone) {
00804 #ifdef DEBUG
00805         DEBUGKPILOT<<"Nothing has changed, not need for a sync."<<endl;
00806 #endif
00807         sinfo.direction=eSyncNone;
00808         return false;
00809     }
00810 
00811     // In all other cases, if only one direction (PC->HH or HH->PC)
00812     // should be done, check if the DB was deleted or if we are supposed
00813     // to sync that direction
00814 
00815     if (eSyncDirection==eSyncPCToPDA) {
00816         if (sinfo.fPCStatus==eStatDeleted) sinfo.direction=eSyncDelete;
00817         else sinfo.direction=eSyncPCToPDA;
00818         return true;
00819     }
00820     if (eSyncDirection==eSyncPDAToPC) {
00821         if (sinfo.fPalmStatus==eStatDeleted) sinfo.direction=eSyncDelete;
00822         else sinfo.direction=eSyncPDAToPC;
00823         return true;
00824     }
00825 
00826 
00827     // ---------------------------------------------------------------
00828     // Finally, do the normal case, where both directions are possible
00829     // ---------------------------------------------------------------
00830 
00831 
00832     // if either is deleted, and the other is not changed, delete
00833     if ( ((sinfo.fPCStatus==eStatDeleted) && (sinfo.fPalmStatus!=eStatChanged)) ||
00834          ((sinfo.fPalmStatus==eStatDeleted) && (sinfo.fPCStatus!=eStatChanged)) )
00835     {
00836 #ifdef DEBUG
00837         DEBUGKPILOT<<"DB was deleted on one side and not changed on "
00838             "the other -> Delete it."<<endl;
00839 #endif
00840         sinfo.direction=eSyncDelete;
00841         return true;
00842     }
00843 
00844     // eStatDeleted (and both not changed) have already been treated, for all
00845     // other values in combination with eStatNone, just copy the texts.
00846     if (sinfo.fPCStatus==eStatNone) {
00847 #ifdef DEBUG
00848         DEBUGKPILOT<<"PC side has changed!"<<endl;
00849 #endif
00850         sinfo.direction=eSyncPDAToPC;
00851         return true;
00852     }
00853 
00854     if (sinfo.fPalmStatus==eStatNone) {
00855         sinfo.direction=eSyncPCToPDA;
00856         return true;
00857     }
00858 
00859     // All other cases
00860     //    (deleted,changed), (changed, deleted), (changed,changed)
00861     // create a conflict:
00862     sinfo.direction=eSyncConflict;
00863     return true;
00864 }
00865 
00866 
00867 
00868 PilotDatabase *DOCConduit::preSyncAction(docSyncInfo &sinfo) const
00869 {
00870     FUNCTIONSETUP;
00871 
00872     {
00873         // make sure the dir for the local texts really exists!
00874         QDir dir( DOCConduitSettings::tXTDirectory() );
00875         if (!dir.exists())
00876         {
00877             dir.mkdir(dir.absPath());
00878         }
00879     }
00880 
00881     DBInfo dbinfo=sinfo.dbinfo;
00882     switch (sinfo.direction)
00883     {
00884         case eSyncPDAToPC:
00885             if (DOCConduitSettings::keepPDBsLocally())
00886             {
00887                 // make sure the dir for the local db really exists!
00888                 QDir dir(DOCConduitSettings::pDBDirectory());
00889 
00890                 if (!dir.exists())
00891                 {
00892                     dir.mkdir(dir.absPath());
00893                 }
00894 #ifdef DEBUG
00895                 DEBUGKPILOT<<"Need to fetch database "<<dbinfo.name<<
00896                     " to the directory "<<dir.absPath()<<endl;
00897 #endif
00898                 dbinfo.flags &= ~dlpDBFlagOpen;
00899 
00900                 if (!fHandle->retrieveDatabase(sinfo.pdbfilename, &dbinfo) )
00901                 {
00902                     WARNINGKPILOT << "Unable to retrieve database " << dbinfo.name <<
00903                         " from the handheld into " << sinfo.pdbfilename << "." << endl;
00904                     return 0L;
00905                 }
00906             }
00907             break;
00908         case eSyncPCToPDA:
00909             if (DOCConduitSettings::keepPDBsLocally())
00910             {
00911                 // make sure the dir for the local db really exists!
00912                 QDir dir(DOCConduitSettings::pDBDirectory());
00913                 if (!dir.exists())
00914                 {
00915                     dir.mkdir(dir.absPath());
00916                 }
00917             }
00918             break;
00919         default:
00920             break;
00921     }
00922     if (DOCConduitSettings::keepPDBsLocally())
00923     {
00924         return new PilotLocalDatabase(DOCConduitSettings::pDBDirectory(),
00925             QString::fromLatin1(dbinfo.name), false);
00926     }
00927     else
00928     {
00929         return deviceLink()->database(QString::fromLatin1(dbinfo.name));
00930     }
00931 }
00932 
00933 
00934 // res gives us information whether the sync worked and the db might need to be
00935 // transferred to the handheld or not (and we just need to clean up the mess)
00936 bool DOCConduit::postSyncAction(PilotDatabase * database,
00937                                 docSyncInfo &sinfo, bool res)
00938 {
00939     FUNCTIONSETUP;
00940     bool rs = true;
00941 
00942     switch (sinfo.direction)
00943     {
00944     case eSyncPDAToPC:
00945         // also reset the sync flags on the handheld
00946 #ifdef DEBUG
00947         DEBUGKPILOT<<"Resetting sync flags for database "
00948             <<sinfo.dbinfo.name<<endl;
00949 #endif
00950         if (DOCConduitSettings::keepPDBsLocally() && !DOCConduitSettings::localSync())
00951         {
00952             PilotDatabase*db=deviceLink()->database(
00953                 QString::fromLatin1(sinfo.dbinfo.name));
00954 #ifdef DEBUG
00955             DEBUGKPILOT<<"Middle 1 Resetting sync flags for database "
00956                 <<sinfo.dbinfo.name<<endl;
00957 #endif
00958             if (db)
00959             {
00960                 db->resetSyncFlags();
00961                 KPILOT_DELETE(db);
00962             }
00963         }
00964 #ifdef DEBUG
00965         DEBUGKPILOT<<"End Resetting sync flags for database "
00966             <<sinfo.dbinfo.name<<endl;
00967 #endif
00968         break;
00969     case eSyncPCToPDA:
00970         if (DOCConduitSettings::keepPDBsLocally() && !DOCConduitSettings::localSync() && res)
00971         {
00972             // Copy the database to the palm
00973             PilotLocalDatabase*localdb=dynamic_cast<PilotLocalDatabase*>(database);
00974             if (localdb)
00975             {
00976 #ifdef DEBUG
00977                 DEBUGKPILOT<<"Installing file "<<localdb->dbPathName()<<" ("
00978                     <<sinfo.handheldDB<<") to the handheld"<<endl;
00979 #endif
00980                 QString dbpathname=localdb->dbPathName();
00981                 // This deletes localdb as well, which is just a cast from database
00982                 KPILOT_DELETE(database);
00983                 if (!fHandle->installFiles(dbpathname, false))
00984                 {
00985                     rs = false;
00986 #ifdef DEBUG
00987                     DEBUGKPILOT<<"Could not install the database "<<dbpathname<<" ("
00988                         <<sinfo.handheldDB<<")"<<endl;
00989 #endif
00990                 }
00991             }
00992         }
00993     default:
00994         break;
00995     }
00996 
00997 #ifdef DEBUG
00998     DEBUGKPILOT<<"Vor KPILOT_DELETE(database)"<<endl;
00999 #endif
01000 
01001     KPILOT_DELETE(database);
01002 #ifdef DEBUG
01003     DEBUGKPILOT<<"End postSyncAction"<<endl;
01004 #endif
01005     return rs;
01006 }
01007 
01008 
01009 
01010 void DOCConduit::cleanup()
01011 {
01012     FUNCTIONSETUP;
01013     DOCConduitSettings::setConvertedDOCfiles( fDBNames );
01014     DOCConduitSettings::self()->writeConfig();
01015 
01016     emit syncDone(this);
01017 }
01018