• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • kdepim
  • Sitemap
  • Contact Us
 

kpilot

doc-conduit.cc

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