kpilot

vcal-conduitbase.cc

Go to the documentation of this file.
00001 /* KPilot
00002 **
00003 ** Copyright (C) 2002-3 by Reinhold Kainhofer
00004 ** Copyright (C) 2001 by Dan Pilone
00005 **
00006 ** Contributions:
00007 **    Copyright (c) 2001 David Jarvie <software@astrojar.org.uk>
00008 **    Copyright (C) 2006 by Bertjan Broeksema <b.broeksema@gmail.com>
00009 **
00010 ** This file defines the vcal-conduit plugin.
00011 */
00012 
00013 /*
00014 ** This program is free software; you can redistribute it and/or modify
00015 ** it under the terms of the GNU General Public License as published by
00016 ** the Free Software Foundation; either version 2 of the License, or
00017 ** (at your option) any later version.
00018 **
00019 ** This program is distributed in the hope that it will be useful,
00020 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
00021 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00022 ** GNU General Public License for more details.
00023 **
00024 ** You should have received a copy of the GNU General Public License
00025 ** along with this program in a file called COPYING; if not, write to
00026 ** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
00027 ** MA 02110-1301, USA.
00028 */
00029 
00030 /*
00031 ** Bug reports and questions can be sent to kde-pim@kde.org
00032 */
00033 
00034 #include <options.h>
00035 
00036 #include <qtimer.h>
00037 #include <qfile.h>
00038 
00039 #include <kmessagebox.h>
00040 #include <kio/netaccess.h>
00041 
00042 #include "libkcal/calendar.h"
00043 #include "libkcal/calendarlocal.h"
00044 #include "libkcal/calendarresources.h"
00045 #include <kstandarddirs.h>
00046 
00047 #include "pilotSerialDatabase.h"
00048 #include "pilotLocalDatabase.h"
00049 #include "pilotDateEntry.h"
00050 
00051 #include "vcal-conduitbase.moc"
00052 #include "vcalconduitSettings.h"
00053 
00054 #ifndef LIBKCAL_IS_VERSION
00055 #warning "Using an old version of libkcal with timezone bug."
00056 #define LIBKCAL_IS_VERSION(a,b,c) (0)
00057 #endif
00058 
00059 #include "conduitstate.h"
00060 #include "initstate.h"
00061 
00062 
00063 /****************************************************************************
00064  *                          VCalConduitBase class                           *
00065  ****************************************************************************/
00066 
00067 VCalConduitBase::VCalConduitBase(KPilotLink *d,
00068     const char *n,
00069     const QStringList &a) :
00070     ConduitAction(d,n,a),
00071     fCalendar(0L),
00072     fP(0L)
00073 {
00074     FUNCTIONSETUP;
00075 
00076     fState = new InitState();
00077 }
00078 
00079 VCalConduitBase::~VCalConduitBase()
00080 {
00081     FUNCTIONSETUP;
00082 
00083     KPILOT_DELETE(fP);
00084     KPILOT_DELETE(fState);
00085     KPILOT_DELETE(fCalendar);
00086     KPILOT_DELETE(fDatabase);
00087     KPILOT_DELETE(fLocalDatabase);
00088 }
00089 
00090 
00091 /*
00092     There are several different scenarios for a record on the Palm and its PC
00093     counterpart. N means a new record, M flags a modified record, D a deleted
00094     and - an unmodified record. First is the Palm record, second the
00095     corresponding PC record:
00096     (-,-) unchanged, just sync if first time or full sync
00097     (N,-) no rec matching the Palm ID in the backupDB/calendar yet => add
00098           KCal::Event
00099     (M,-) record is in backupDB, unchanged in calendar => modify in calendar and
00100           in backupDB
00101     (D,-) deleted on Palm, exists in backupDB and calendar => just delete from
00102           calendar and backupDB
00103     (-,N) no or invalid pilotID set for the KCal::Event => just add to palm and
00104           backupDB
00105     (-,M) valid PilotID set => just modify on Palm
00106     (-,D) Record in backupDB, but not in calendar => delete from Palm and
00107           backupDB
00108     (N,N) Can't find out (the two records are not correlated in any way, they
00109           just have the same data!!
00110     (M,M),(M,L),(L,M) (Record exists on Palm and the Event has the ID) CONFLICT,
00111           ask the user what to do or use a config setting
00112     (L,L) already deleted on both, no need to do anything.
00113 
00114     The sync process is as follows (for a fast sync):
00115     1) HHToPCState goes through all records on Palm (just the modified one
00116        are necessary), find it in the backupDB. The following handles ([NMD],*)
00117         a) if it doesn't exist and was not deleted, add it to the calendar and
00118            the backupDB
00119         b) if it exists, is unchanged in the calendar and was not deleted,
00120            just modify in the calendar
00121         c) if it exists and was deleted, delete it from the calendar if
00122            necessary
00123     2) PCToHHState goes through all KCale::Events in the calendar (just
00124        modified, this is the modification time is later than the last sync time
00125         ). This handles (-,N),(-,M)
00126         a) if it does not have a pilotID, add it to the palm and backupDB,
00127            store the PalmID
00128         b) if it has a valid pilotID, update the Palm record and the backup
00129     3) DeletedUnsyncedHHState goes through all palm records (which don't
00130        have the deleted flag) of the palm db and if one does not exist in the
00131        Calendar, it was deleted there, so delete it from the Palm and backup,
00132        too. This handles the case of (-,D)
00133     4) DeletedUnsyncedPCState goes through all KCal::Events in the calendar and
00134        looks for a corresponding event in the palm database. If it does not
00135        exist, that means that it was deleted on the palm, so we need to also
00136        delete it from the local calendar.  This handles the case of (D,-).
00137 
00138     In addition to the fast sync, where the last sync was done with this very
00139     PC and calendar file, there are two special cases: a full and a first sync.
00140     -) a full sync goes through all records, not just the modified ones. The
00141        pilotID setting of the calendar records is used to determine if the
00142        record already exists. if yes, the record is just modified.
00143     -) a first sync completely ignores the pilotID setting of the calendar
00144        events. All records are added, so there might be duplicates. The add
00145        function for the calendar should check if a similar record already
00146        exists, but this is not done yet.
00147 
00148     -) a full sync is done if
00149        a) there is a backupdb and a calendar, but the PC id number changed
00150        b) it was explicitly requested by pressing the full sync button in KPilot
00151        c) the setting "always full sync" was selected in the configuration dlg
00152     -) a first sync is done if
00153        a) either the calendar or the backup DB does not exist.
00154        b) the calendar and the backup DB exists, but the sync is done for a
00155           different User name
00156        c) it was explicitly requested in KPilot
00157 */
00158 
00159 /* virtual */ bool VCalConduitBase::exec()
00160 {
00161     FUNCTIONSETUP;
00162 
00163     readConfig();
00164 
00165     // don't do a first sync by default in any case, only when explicitly
00166     // requested, or the backup database or the alendar are empty.
00167     setFirstSync( false );
00168 
00169     // TODO: Check Full sync and First sync
00170     bool retrieved = false;
00171     if ( !openDatabases( dbname(), &retrieved ) ) goto error;
00172     setFirstSync( retrieved );
00173 
00174     // If we are in testmode we don't need the local calendar. Else a
00175     // calendar *must* be opened, we want to sync something don't we?
00176     if (!syncMode().isTest() && !openCalendar() ) goto error;
00177 
00178     // Start processing the sync
00179     QTimer::singleShot(0, this, SLOT(slotProcess()));
00180     return true;
00181 
00182 error:
00183     emit logError( i18n( "Could not open the calendar databases." ) );
00184 
00185     KPILOT_DELETE(fCalendar);
00186     KPILOT_DELETE(fP);
00187     KPILOT_DELETE(fState);
00188     return false;
00189 }
00190 
00191 void VCalConduitBase::slotProcess() {
00192     FUNCTIONSETUP;
00193 
00194     // start the current state if necessary
00195     if( fState && !fState->started() ) {
00196         fState->startSync( this );
00197     }
00198 
00199     // Process next record if applicable
00200     if( hasNextRecord )
00201     {
00202         fState->handleRecord( this );
00203         QTimer::singleShot( 0, this, SLOT( slotProcess() ) );
00204     }
00205     // Else finish the current state if there is one
00206     else if( fState )
00207     {
00208         fState->finishSync( this );
00209         QTimer::singleShot( 0, this, SLOT( slotProcess() ) );
00210     }
00211     // No state so sync is finished
00212     else
00213     {
00214         DEBUGKPILOT << fname << ": Sync finished." << endl;
00215         delayDone();
00216     }
00217 }
00218 
00219 /* virtual */ void VCalConduitBase::readConfig()
00220 {
00221     config()->readConfig();
00222     SyncAction::ConflictResolution res = (SyncAction::ConflictResolution)
00223         (config()->conflictResolution());
00224     setConflictResolution( res );
00225 }
00226 
00227 static void listResources( KCal::CalendarResources *p )
00228 {
00229     FUNCTIONSETUP;
00230     KCal::CalendarResourceManager *manager = p->resourceManager();
00231 
00232     DEBUGKPILOT << fname << ": Resources in calendar:" << endl;
00233     KCal::CalendarResourceManager::Iterator it;
00234     for( it = manager->begin(); it != manager->end(); ++it )
00235     {
00236         DEBUGKPILOT << fname << ": " << (*it)->resourceName() << endl;
00237     }
00238 }
00239 
00240 /* virtual */ bool VCalConduitBase::openCalendar()
00241 {
00242     FUNCTIONSETUP;
00243 
00244     KConfig korgcfg( locate( "config", CSL1("korganizerrc") ) );
00245 
00246     // this part taken from adcalendarbase.cpp:
00247     korgcfg.setGroup( "Time & Date" );
00248     QString tz(korgcfg.readEntry( "TimeZoneId" ) );
00249 
00250     DEBUGKPILOT << fname << ": KOrganizer's time zone = " << tz << endl;
00251 
00252     // Need a subclass ptr. for the ResourceCalendar methods
00253     KCal::CalendarResources *rescal = 0L;
00254 
00255     DEBUGKPILOT << fname << ": Got calendar type " << config()->calendarType()
00256         << endl;
00257 
00258     switch(config()->calendarType())
00259     {
00260         case VCalConduitSettings::eCalendarLocal:
00261         {
00262             DEBUGKPILOT << fname << "Using CalendarLocal, file = "
00263                 << config()->calendarFile() << endl;
00264 
00265             if ( config()->calendarFile().isEmpty() )
00266             {
00267                 DEBUGKPILOT << fname << "Empty calendar file name." << endl;
00268 
00269                 emit logError( i18n( "You selected to sync with an iCalendar"
00270                         " file, but did not give a filename. Please select a"
00271                         " valid file name in the conduit's configuration"
00272                         " dialog" ) );
00273                 return false;
00274             }
00275 
00276             fCalendar = new KCal::CalendarLocal( tz );
00277             if ( !fCalendar )
00278             {
00279                 WARNINGKPILOT
00280                     << "Cannot initialize calendar object for file "
00281                     << config()->calendarFile() << endl;
00282                 return false;
00283             }
00284 
00285             DEBUGKPILOT << fname << "Calendar's timezone: "
00286                 << fCalendar->timeZoneId() << endl;
00287             DEBUGKPILOT << fname << "Calendar is local time: "
00288                 << fCalendar->isLocalTime() << endl;
00289 
00290             emit logMessage( fCalendar->isLocalTime() ?
00291                 i18n( "Using local time zone: %1" ).arg( tz ) :
00292                 i18n( "Using non-local time zone: %1" ).arg( tz ) );
00293 
00294             KURL kurl( config()->calendarFile() );
00295             if( !KIO::NetAccess::download( config()->calendarFile(),
00296                 fCalendarFile, 0L ) && !kurl.isLocalFile() )
00297             {
00298                 emit logError(i18n( "You chose to sync with the file \"%1\", which "
00299                     "cannot be opened. Please make sure to supply a "
00300                     "valid file name in the conduit's configuration dialog. "
00301                     "Aborting the conduit." ).arg( config()->calendarFile() ) );
00302                 KIO::NetAccess::removeTempFile( fCalendarFile );
00303                 return false;
00304             }
00305 
00306             // if there is no calendar yet, use a first sync..
00307             // the calendar is initialized, so nothing more to do...
00308             if (!dynamic_cast<KCal::CalendarLocal*>(fCalendar)->load(fCalendarFile) )
00309             {
00310                 DEBUGKPILOT << fname << "Calendar file " << fCalendarFile
00311                     << " could not be opened. Will create a new one" << endl;
00312 
00313                 // Try to create empty file. if it fails,
00314                 // no valid file name was given.
00315                 QFile fl(fCalendarFile);
00316                 if (!fl.open(IO_WriteOnly | IO_Append))
00317                 {
00318                     DEBUGKPILOT << fname << "Invalid calendar file name "
00319                         << fCalendarFile << endl;
00320 
00321                     emit logError( i18n( "You chose to sync with the file \"%1\", which "
00322                         "cannot be opened or created. Please make sure to supply a "
00323                         "valid file name in the conduit's configuration dialog. "
00324                         "Aborting the conduit." ).arg( config()->calendarFile() ) );
00325                     return false;
00326                 }
00327                 fl.close();
00328                 setFirstSync( true );
00329             }
00330             addSyncLogEntry( i18n( "Syncing with file \"%1\"" )
00331                 .arg( config()->calendarFile() ) );
00332             break;
00333         }
00334 
00335         case VCalConduitSettings::eCalendarResource:
00336             DEBUGKPILOT << "Using CalendarResource!" << endl;
00337 
00338             rescal = new KCal::CalendarResources( tz );
00339             listResources(rescal);
00340             fCalendar = rescal;
00341             if ( !fCalendar)
00342             {
00343                 WARNINGKPILOT << "Cannot initialize calendar " <<
00344                     "object for ResourceCalendar" << endl;
00345                 return false;
00346             }
00347 
00348 #if LIBKCAL_IS_VERSION(1,1,0)
00349             rescal->readConfig();
00350             rescal->load();
00351 #else
00352 #warning "Timezone bug is present."
00353 #endif
00354             addSyncLogEntry( i18n( "Syncing with standard calendar resource." ) );
00355             emit logMessage( fCalendar->isLocalTime() ?
00356                 i18n( "Using local time zone: %1" ).arg( tz ) :
00357                 i18n( "Using non-local time zone: %1" ).arg( tz ) );
00358             break;
00359         default:
00360             break;
00361     }
00362 
00363     if ( !fCalendar )
00364     {
00365         WARNINGKPILOT << "Unable to initialize calendar object."
00366             << " Please check the conduit's setup." << endl;
00367         emit logError( i18n( "Unable to initialize the calendar object. Please"
00368             " check the conduit's setup") );
00369         return false;
00370     }
00371     fP = createPrivateCalendarData( fCalendar );
00372     if ( !fP )
00373     {
00374         return false;
00375     }
00376     int rc = fP->updateIncidences();
00377     DEBUGKPILOT << fname << ": return from updateIncidences: [" << rc
00378         << "]" << endl;
00379 
00380     if ( fP->count() < 1 )
00381     {
00382         setFirstSync( true );
00383     }
00384 
00385     return true;
00386 }
00387 
00388 KCal::Incidence* VCalConduitBase::addRecord( PilotRecord *r )
00389 {
00390     FUNCTIONSETUP;
00391 
00392     recordid_t id = fLocalDatabase->writeRecord( r );
00393     DEBUGKPILOT<<fname<<": Pilot Record ID = " << r->id() << ", backup ID = "
00394         << id << endl;
00395 
00396     PilotRecordBase *de = newPilotEntry( r );
00397     KCal::Incidence*e = 0L;
00398 
00399     if ( de )
00400     {
00401         e = fP->findIncidence( r->id() );
00402         if ( !e )
00403         {
00404             // no corresponding entry found, so create, copy and insert it.
00405             e = newIncidence();
00406             incidenceFromRecord( e, de );
00407             fP->addIncidence( e );
00408             fCtrPC->created();
00409         }
00410         else
00411         {
00412             // similar entry found, so just copy, no need to insert again
00413             incidenceFromRecord( e, de );
00414             fCtrPC->updated();
00415         }
00416     }
00417     KPILOT_DELETE( de );
00418     return e;
00419 }
00420 
00421 int VCalConduitBase::resolveConflict( KCal::Incidence *e, PilotRecordBase *de ) {
00422     if ( getConflictResolution() == SyncAction::eAskUser )
00423     {
00424         // TODO: This is messed up!!!
00425         QString query = i18n( "The following item was modified "
00426             "both on the Handheld and on your PC:\nPC entry:\n\t" );
00427         query += e->summary();
00428         query += i18n( "\nHandheld entry:\n\t" );
00429         query += getTitle( de );
00430         query += i18n( "\n\nWhich entry do you want to keep? It will "
00431             "overwrite the other entry." );
00432 
00433         return KMessageBox::No == questionYesNo(
00434             query,
00435             i18n( "Conflicting Entries" ),
00436             QString::null,
00437             0 /* Never timeout */,
00438             i18n( "Handheld" ), i18n( "PC" ));
00439     }
00440     return getConflictResolution();
00441 }
00442 
00443 KCal::Incidence*VCalConduitBase::changeRecord(PilotRecord *r,PilotRecord *)
00444 {
00445     FUNCTIONSETUP;
00446 
00447     PilotRecordBase *de = newPilotEntry( r );
00448     KCal::Incidence *e = fP->findIncidence( r->id() );
00449 
00450     DEBUGKPILOT << fname << ": Pilot Record ID: [" << r->id() << "]" << endl;
00451 
00452     if ( e && de )
00453     {
00454         // TODO: check for conflict, and if there is one, ask for resolution
00455         if ( ( e->syncStatus() != KCal::Incidence::SYNCNONE )
00456             && r->isModified() )
00457         {
00458             // TODO: I have not yet found a way to complete ignore an item
00459             if (resolveConflict( e, de ) )
00460             {
00461                 // PC record takes precedence:
00462                 KPILOT_DELETE( de );
00463                 return e;
00464             }
00465         }
00466         // no conflict or conflict resolution says, Palm overwrites, so do it:
00467         incidenceFromRecord( e, de );
00468 
00469         // NOTE: This MUST be done last, since every other set* call
00470         // calls updated(), which will trigger an
00471         // setSyncStatus(SYNCMOD)!!!
00472         e->setSyncStatus(KCal::Incidence::SYNCNONE);
00473         fLocalDatabase->writeRecord( r );
00474     }
00475     else
00476     {
00477         WARNINGKPILOT
00478             << "While changing record -- not found in iCalendar" << endl;
00479         addRecord( r );
00480     }
00481 
00482     KPILOT_DELETE( de );
00483     return e;
00484 }
00485 
00486 
00487 KCal::Incidence*VCalConduitBase::deleteRecord( PilotRecord *r, PilotRecord * )
00488 {
00489     FUNCTIONSETUP;
00490 
00491     KCal::Incidence *e = fP->findIncidence(r->id());
00492     if (e)
00493     {
00494         // RemoveEvent also takes it out of the calendar.
00495         fP->removeIncidence(e);
00496         fCtrPC->deleted();
00497     }
00498     fLocalDatabase->writeRecord( r );
00499     return NULL;
00500 }
00501 
00502 
00503 void VCalConduitBase::addPalmRecord( KCal::Incidence *e )
00504 {
00505     FUNCTIONSETUP;
00506 
00507     PilotRecordBase *de = newPilotEntry( 0L );
00508     updateIncidenceOnPalm( e, de );
00509     fCtrHH->created();
00510     KPILOT_DELETE( de );
00511 }
00512 
00513 
00514 void VCalConduitBase::changePalmRecord(KCal::Incidence*e, PilotRecord*s)
00515 {
00516     PilotRecordBase *de = newPilotEntry( s );
00517     updateIncidenceOnPalm( e, de );
00518     fCtrHH->updated();
00519     KPILOT_DELETE( de );
00520 }
00521 
00522 
00523 void VCalConduitBase::deletePalmRecord( KCal::Incidence *e, PilotRecord *s )
00524 {
00525     FUNCTIONSETUP;
00526     if ( s )
00527     {
00528         DEBUGKPILOT << fname << ": deleting record " << s->id() << endl;
00529         s->setDeleted();
00530         fDatabase->writeRecord( s );
00531         fLocalDatabase->writeRecord( s );
00532         fCtrHH->deleted();
00533     }
00534     else
00535     {
00536         DEBUGKPILOT << fname << ": could not find record to delete (";
00537         DEBUGKPILOT << e->pilotId() << ")" << endl;
00538     }
00539 
00540     Q_UNUSED(e);
00541 }
00542 
00543 /* I have to use a pointer to an existing PilotDateEntry so that I can handle
00544    new records as well (and to prevent some crashes concerning the validity
00545    domain of the PilotRecord*r). In syncEvent this PilotDateEntry is created. */
00546 void VCalConduitBase::updateIncidenceOnPalm( KCal::Incidence *e,
00547     PilotRecordBase *de )
00548 {
00549     FUNCTIONSETUP;
00550     if ( !de || !e ) {
00551         DEBUGKPILOT << fname << ": NULL event given... Skipping it" << endl;
00552         return;
00553     }
00554 
00555     if ( e->syncStatus() == KCal::Incidence::SYNCDEL )
00556     {
00557         DEBUGKPILOT << fname << ": don't write deleted incidence "
00558             << e->summary() << " to the palm" << endl;
00559         return;
00560     }
00561 
00562     PilotRecord *r = recordFromIncidence( de, e );
00563 
00564     // TODO: Check for conflict!
00565     if ( r )
00566     {
00567         recordid_t id=fDatabase->writeRecord(r);
00568         r->setID(id);
00569 //      r->setAttrib(r->getAttrib() & ~dlpRecAttrDeleted);
00570         fLocalDatabase->writeRecord( r );
00571 //      fDatabase->writeRecord(r);
00572         e->setPilotId( id );
00573 
00574         // NOTE: This MUST be done last, since every other set* call
00575         // calls updated(), which will trigger an
00576         // setSyncStatus(SYNCMOD)!!!
00577         e->setSyncStatus(KCal::Incidence::SYNCNONE);
00578         KPILOT_DELETE( r );
00579     }
00580 }
00581 
00582 const QString VCalConduitBase::dbname()
00583 {
00584     return QString::null;
00585 }
00586 
00587 PilotRecord *VCalConduitBase::readRecordByIndex( int index )
00588 {
00589     FUNCTIONSETUP;
00590     return fDatabase->readRecordByIndex( index );
00591 }
00592 
00593 KCal::Incidence *VCalConduitBase::incidenceFromRecord( PilotRecord *r )
00594 {
00595     FUNCTIONSETUP;
00596     PilotRecordBase *pac = newPilotEntry( r );
00597     KCal::Incidence *i = newIncidence();
00598     incidenceFromRecord( i, pac );
00599 
00600     KPILOT_DELETE( pac );
00601     return i;
00602 }
00603 
00604 void VCalConduitBase::setState( ConduitState *s )
00605 {
00606     KPILOT_DELETE( fState );
00607     fState = s;
00608 }
00609 
00610 /* virtual */ void VCalConduitBase::postSync( )
00611 {
00612     FUNCTIONSETUP;
00613     if (fCtrPC && fP)
00614         fCtrPC->setEndCount(fP->count());
00615 }
00616 
00617 /* virtual */ void VCalConduitBase::preSync( )
00618 {
00619     FUNCTIONSETUP;
00620     if (fCtrPC && fP)
00621         fCtrPC->setStartCount(fP->count());
00622 }