diff options
Diffstat (limited to 'kresources/kolab/kcal')
-rw-r--r-- | kresources/kolab/kcal/event.cpp | 7 | ||||
-rw-r--r-- | kresources/kolab/kcal/incidence.cpp | 248 | ||||
-rw-r--r-- | kresources/kolab/kcal/incidence.h | 9 | ||||
-rw-r--r-- | kresources/kolab/kcal/kolab.desktop | 1 | ||||
-rw-r--r-- | kresources/kolab/kcal/resourcekolab.cpp | 437 | ||||
-rw-r--r-- | kresources/kolab/kcal/resourcekolab.h | 64 | ||||
-rw-r--r-- | kresources/kolab/kcal/task.cpp | 171 | ||||
-rw-r--r-- | kresources/kolab/kcal/task.h | 16 |
8 files changed, 762 insertions, 191 deletions
diff --git a/kresources/kolab/kcal/event.cpp b/kresources/kolab/kcal/event.cpp index 0f25eb73d..e1d58a13c 100644 --- a/kresources/kolab/kcal/event.cpp +++ b/kresources/kolab/kcal/event.cpp @@ -190,7 +190,9 @@ void Event::setFields( const KCal::Event* event ) { Incidence::setFields( event ); - if ( event->hasEndDate() ) { + // note: if hasEndDate() is false and hasDuration() is true + // dtEnd() returns start+duration + if ( event->hasEndDate() || event->hasDuration() ) { if ( event->doesFloat() ) { // This is a floating event. Don't timezone move this one mFloatingStatus = AllDay; @@ -199,8 +201,9 @@ void Event::setFields( const KCal::Event* event ) mFloatingStatus = HasTime; setEndDate( localToUTC( event->dtEnd() ) ); } - } else + } else { mHasEndDate = false; + } setTransparency( event->transparency() ); } diff --git a/kresources/kolab/kcal/incidence.cpp b/kresources/kolab/kcal/incidence.cpp index 74f41fd8d..de076eb98 100644 --- a/kresources/kolab/kcal/incidence.cpp +++ b/kresources/kolab/kcal/incidence.cpp @@ -39,6 +39,8 @@ #include <libkcal/journal.h> #include <korganizer/version.h> +#include <libemailfunctions/email.h> + #include <kdebug.h> #include <kmdcodec.h> #include <kurl.h> @@ -50,7 +52,6 @@ using namespace Kolab; Incidence::Incidence( KCal::ResourceKolab *res, const TQString &subResource, Q_UINT32 sernum, const TQString& tz ) : KolabBase( tz ), mFloatingStatus( Unset ), mHasAlarm( false ), - mRevision( 0 ), mResource( res ), mSubResource( subResource ), mSernum( sernum ) @@ -163,16 +164,6 @@ TQString Incidence::internalUID() const return mInternalUID; } -void Incidence::setRevision( int revision ) -{ - mRevision = revision; -} - -int Incidence::revision() const -{ - return mRevision; -} - bool Incidence::loadAttendeeAttribute( TQDomElement& element, Attendee& attendee ) { @@ -183,8 +174,16 @@ bool Incidence::loadAttendeeAttribute( TQDomElement& element, TQDomElement e = n.toElement(); TQString tagName = e.tagName(); - if ( tagName == "display-name" ) - attendee.displayName = e.text(); + if ( tagName == "display-name" ) { + // Quote the text in case it contains commas or other quotable chars. + TQString tusername = KPIM::quoteNameIfNecessary( e.text() ); + + TQString tname, temail; + // ignore the return value because it will always be false since + // tusername does not contain "@domain". + KPIM::getNameAndMail( tusername, tname, temail ); + attendee.displayName = tname; + } else if ( tagName == "smtp-address" ) attendee.smtpAddress = e.text(); else if ( tagName == "status" ) @@ -249,6 +248,69 @@ void Incidence::saveAttachments( TQDomElement& element ) const } } +void Incidence::saveAlarms( TQDomElement& element ) const +{ + if ( mAlarms.isEmpty() ) return; + + TQDomElement list = element.ownerDocument().createElement( "advanced-alarms" ); + element.appendChild( list ); + for ( KCal::Alarm::List::ConstIterator it = mAlarms.constBegin(); it != mAlarms.constEnd(); ++it ) { + KCal::Alarm* a = *it; + TQDomElement e = list.ownerDocument().createElement( "alarm" ); + list.appendChild( e ); + + writeString( e, "enabled", a->enabled() ? "1" : "0" ); + if ( a->hasStartOffset() ) { + writeString( e, "start-offset", TQString::number( a->startOffset().asSeconds()/60 ) ); + } + if ( a->hasEndOffset() ) { + writeString( e, "end-offset", TQString::number( a->endOffset().asSeconds()/60 ) ); + } + if ( a->repeatCount() ) { + writeString( e, "repeat-count", TQString::number( a->repeatCount() ) ); + writeString( e, "repeat-interval", TQString::number( a->snoozeTime() ) ); + } + + switch ( a->type() ) { + case KCal::Alarm::Invalid: + break; + case KCal::Alarm::Display: + e.setAttribute( "type", "display" ); + writeString( e, "text", a->text() ); + break; + case KCal::Alarm::Procedure: + e.setAttribute( "type", "procedure" ); + writeString( e, "program", a->programFile() ); + writeString( e, "arguments", a->programArguments() ); + break; + case KCal::Alarm::Email: + { + e.setAttribute( "type", "email" ); + TQDomElement addresses = e.ownerDocument().createElement( "addresses" ); + e.appendChild( addresses ); + for ( TQValueList<KCal::Person>::ConstIterator it = a->mailAddresses().constBegin(); it != a->mailAddresses().constEnd(); ++it ) { + writeString( addresses, "address", (*it).fullName() ); + } + writeString( e, "subject", a->mailSubject() ); + writeString( e, "mail-text", a->mailText() ); + TQDomElement attachments = e.ownerDocument().createElement( "attachments" ); + e.appendChild( attachments ); + for ( TQStringList::ConstIterator it = a->mailAttachments().constBegin(); it != a->mailAttachments().constEnd(); ++it ) { + writeString( attachments, "attachment", *it ); + } + break; + } + case KCal::Alarm::Audio: + e.setAttribute( "type", "audio" ); + writeString( e, "file", a->audioFile() ); + break; + default: + kdWarning() << "Unhandled alarm type:" << a->type() << endl; + break; + } + } +} + void Incidence::saveRecurrence( TQDomElement& element ) const { TQDomElement e = element.ownerDocument().createElement( "recurrence" ); @@ -289,8 +351,14 @@ void Incidence::loadRecurrence( const TQDomElement& element ) TQDomElement e = n.toElement(); TQString tagName = e.tagName(); - if ( tagName == "interval" ) - mRecurrence.interval = e.text().toInt(); + if ( tagName == "interval" ) { + //kolab/issue4229, sometimes the interval value can be empty + if ( e.text().isEmpty() || e.text().toInt() <= 0 ) { + mRecurrence.interval = 1; + } else { + mRecurrence.interval = e.text().toInt(); + } + } else if ( tagName == "day" ) // can be present multiple times mRecurrence.days.append( e.text() ); else if ( tagName == "daynumber" ) @@ -309,6 +377,118 @@ void Incidence::loadRecurrence( const TQDomElement& element ) } } +static void loadAddressesHelper( const TQDomElement& element, KCal::Alarm* a ) +{ + for ( TQDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling() ) { + if ( n.isComment() ) + continue; + if ( n.isElement() ) { + TQDomElement e = n.toElement(); + TQString tagName = e.tagName(); + + if ( tagName == "address" ) { + a->addMailAddress( KCal::Person( e.text() ) ); + } else { + kdWarning() << "Unhandled tag" << tagName << endl; + } + } + } +} + +static void loadAttachmentsHelper( const TQDomElement& element, KCal::Alarm* a ) +{ + for ( TQDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling() ) { + if ( n.isComment() ) + continue; + if ( n.isElement() ) { + TQDomElement e = n.toElement(); + TQString tagName = e.tagName(); + + if ( tagName == "attachment" ) { + a->addMailAttachment( e.text() ); + } else { + kdWarning() << "Unhandled tag" << tagName << endl; + } + } + } +} + +static void loadAlarmHelper( const TQDomElement& element, KCal::Alarm* a ) +{ + for ( TQDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling() ) { + if ( n.isComment() ) + continue; + if ( n.isElement() ) { + TQDomElement e = n.toElement(); + TQString tagName = e.tagName(); + + if ( tagName == "start-offset" ) { + a->setStartOffset( e.text().toInt()*60 ); + } else if ( tagName == "end-offset" ) { + a->setEndOffset( e.text().toInt()*60 ); + } else if ( tagName == "repeat-count" ) { + a->setRepeatCount( e.text().toInt() ); + } else if ( tagName == "repeat-interval" ) { + a->setSnoozeTime( e.text().toInt() ); + } else if ( tagName == "text" ) { + a->setText( e.text() ); + } else if ( tagName == "program" ) { + a->setProgramFile( e.text() ); + } else if ( tagName == "arguments" ) { + a->setProgramArguments( e.text() ); + } else if ( tagName == "addresses" ) { + loadAddressesHelper( e, a ); + } else if ( tagName == "subject" ) { + a->setMailSubject( e.text() ); + } else if ( tagName == "mail-text" ) { + a->setMailText( e.text() ); + } else if ( tagName == "attachments" ) { + loadAttachmentsHelper( e, a ); + } else if ( tagName == "file" ) { + a->setAudioFile( e.text() ); + } else if ( tagName == "enabled" ) { + a->setEnabled( e.text().toInt() != 0 ); + } else { + kdWarning() << "Unhandled tag" << tagName << endl; + } + } + } +} + +void Incidence::loadAlarms( const TQDomElement& element ) +{ + for ( TQDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling() ) { + if ( n.isComment() ) + continue; + if ( n.isElement() ) { + TQDomElement e = n.toElement(); + TQString tagName = e.tagName(); + + if ( tagName == "alarm" ) { + KCal::Alarm *a = new KCal::Alarm( 0 ); + a->setEnabled( true ); // default to enabled, unless some XML attribute says otherwise. + TQString type = e.attribute( "type" ); + if ( type == "display" ) { + a->setType( KCal::Alarm::Display ); + } else if ( type == "procedure" ) { + a->setType( KCal::Alarm::Procedure ); + } else if ( type == "email" ) { + a->setType( KCal::Alarm::Email ); + } else if ( type == "audio" ) { + a->setType( KCal::Alarm::Audio ); + } else { + kdWarning() << "Unhandled alarm type:" << type << endl; + } + + loadAlarmHelper( e, a ); + mAlarms << a; + } else { + kdWarning() << "Unhandled tag" << tagName << endl; + } + } + } +} + bool Incidence::loadAttribute( TQDomElement& element ) { TQString tagName = element.tagName(); @@ -340,20 +520,17 @@ bool Incidence::loadAttribute( TQDomElement& element ) } else if ( tagName == "alarm" ) // Alarms should be minutes before. Libkcal uses event time + alarm time setAlarm( - element.text().toInt() ); + else if ( tagName == "advanced-alarms" ) + loadAlarms( element ); else if ( tagName == "x-kde-internaluid" ) setInternalUID( element.text() ); - else if ( tagName == "revision" ) { - bool ok; - int revision = element.text().toInt( &ok ); - if ( ok ) - setRevision( revision ); - } else if ( tagName == "x-custom" ) + else if ( tagName == "x-custom" ) loadCustomAttributes( element ); else { bool ok = KolabBase::loadAttribute( element ); if ( !ok ) { // Unhandled tag - save for later storage - kdDebug() << "Saving unhandled tag " << element.tagName() << endl; + //kdDebug() << "Saving unhandled tag " << element.tagName() << endl; Custom c; c.key = TQCString( "X-KDE-KolabUnhandled-" ) + element.tagName().latin1(); c.value = element.text(); @@ -385,8 +562,8 @@ bool Incidence::saveAttributes( TQDomElement& element ) const int alarmTime = qRound( -alarm() ); writeString( element, "alarm", TQString::number( alarmTime ) ); } + saveAlarms( element ); writeString( element, "x-kde-internaluid", internalUID() ); - writeString( element, "revision", TQString::number( revision() ) ); saveCustomAttributes( element ); return true; } @@ -424,13 +601,15 @@ static KCal::Attendee::PartStat attendeeStringToStatus( const TQString& s ) return KCal::Attendee::NeedsAction; if ( s == "tentative" ) return KCal::Attendee::Tentative; + if ( s == "accepted" ) + return KCal::Attendee::Accepted; if ( s == "declined" ) return KCal::Attendee::Declined; if ( s == "delegated" ) return KCal::Attendee::Delegated; // Default: - return KCal::Attendee::Accepted; + return KCal::Attendee::None; } static TQString attendeeStatusToString( KCal::Attendee::PartStat status ) @@ -649,6 +828,14 @@ void Incidence::setFields( const KCal::Incidence* incidence ) mAttachments.push_back( a ); } + mAlarms.clear(); + + // Alarms + const KCal::Alarm::List alarms = incidence->alarms(); + for ( KCal::Alarm::List::ConstIterator it = alarms.begin(); it != alarms.end(); ++it ) { + mAlarms.push_back( *it ); + } + if ( incidence->doesRecur() ) { setRecurrence( incidence->recurrence() ); mRecurrence.exclusions = incidence->recurrence()->exDates(); @@ -711,10 +898,17 @@ void Incidence::saveTo( KCal::Incidence* incidence ) incidence->setSummary( summary() ); incidence->setLocation( location() ); - if ( mHasAlarm ) { + if ( mHasAlarm && mAlarms.isEmpty() ) { KCal::Alarm* alarm = incidence->newAlarm(); alarm->setStartOffset( qRound( mAlarm * 60.0 ) ); alarm->setEnabled( true ); + alarm->setType( KCal::Alarm::Display ); + } else if ( !mAlarms.isEmpty() ) { + for ( KCal::Alarm::List::ConstIterator it = mAlarms.constBegin(); it != mAlarms.constEnd(); ++it ) { + KCal::Alarm *alarm = *it; + alarm->setParent( incidence ); + incidence->addAlarm( alarm ); + } } if ( organizer().displayName.isEmpty() ) @@ -803,7 +997,7 @@ void Incidence::saveTo( KCal::Incidence* incidence ) if ( hasPilotSyncStatus() ) incidence->setSyncStatus( pilotSyncStatus() ); - for( TQValueList<Custom>::ConstIterator it = mCustomList.begin(); it != mCustomList.end(); ++it ) { + for( TQValueList<Custom>::ConstIterator it = mCustomList.constBegin(); it != mCustomList.constEnd(); ++it ) { incidence->setNonKDECustomProperty( (*it).key, (*it).value ); } @@ -836,7 +1030,7 @@ void Incidence::loadAttachments() TQString Incidence::productID() const { - return TQString( "KOrganizer " ) + korgVersion + ", Kolab resource"; + return TQString( "KOrganizer %1, Kolab resource" ).arg( korgVersion ); } // Unhandled KCal::Incidence fields: diff --git a/kresources/kolab/kcal/incidence.h b/kresources/kolab/kcal/incidence.h index 32b112aaf..582d34c38 100644 --- a/kresources/kolab/kcal/incidence.h +++ b/kresources/kolab/kcal/incidence.h @@ -41,6 +41,7 @@ class TQDomElement; namespace KCal { class Incidence; class Recurrence; + class Alarm; class Attachment; class ResourceKolab; } @@ -115,9 +116,6 @@ public: void setInternalUID( const TQString& iuid ); TQString internalUID() const; - virtual void setRevision( int ); - virtual int revision() const; - // Load the attributes of this class virtual bool loadAttribute( TQDomElement& ); @@ -136,6 +134,9 @@ protected: void saveAttendees( TQDomElement& element ) const; void saveAttachments( TQDomElement& element ) const; + void loadAlarms( const TQDomElement& element ); + void saveAlarms( TQDomElement& element ) const; + void loadRecurrence( const TQDomElement& element ); void saveRecurrence( TQDomElement& element ) const; void saveCustomAttributes( TQDomElement& element ) const; @@ -154,9 +155,9 @@ protected: bool mHasAlarm; Recurrence mRecurrence; TQValueList<Attendee> mAttendees; + TQValueList<KCal::Alarm*> mAlarms; TQValueList<KCal::Attachment*> mAttachments; TQString mInternalUID; - int mRevision; struct Custom { TQCString key; diff --git a/kresources/kolab/kcal/kolab.desktop b/kresources/kolab/kcal/kolab.desktop index 579e6406d..c0a7daa56 100644 --- a/kresources/kolab/kcal/kolab.desktop +++ b/kresources/kolab/kcal/kolab.desktop @@ -21,7 +21,6 @@ Name[hu]=IMAP-kiszolgálón tárolt naptár a KMailen keresztül Name[is]=Dagatal á IMAP þjóni gegnum KMail Name[it]=Calendario su server IMAP via KMail Name[ja]=KMail 経由 IMAP サーバのカレンダー -Name[ka]=კალენდარი IMAP სერვერზე KMail-ის საშუალებით Name[kk]=KMail арқылы IMAP серверіндегі күнтізбе Name[km]=ប្រតិទិនលើម៉ាស៊ីនបម្រើ IMAP តាមរយៈ KMail Name[lt]=Kalendorius IMAP serveryje per KMail diff --git a/kresources/kolab/kcal/resourcekolab.cpp b/kresources/kolab/kcal/resourcekolab.cpp index b61b12110..1f5f486ff 100644 --- a/kresources/kolab/kcal/resourcekolab.cpp +++ b/kresources/kolab/kcal/resourcekolab.cpp @@ -41,7 +41,6 @@ #include <kio/uiserver_stub.h> #include <kapplication.h> #include <dcopclient.h> -#include <libkcal/icalformat.h> #include <libkdepim/kincidencechooser.h> #include <kabc/locknull.h> #include <kmainwindow.h> @@ -72,8 +71,11 @@ static const char* incidenceInlineMimeType = "text/calendar"; ResourceKolab::ResourceKolab( const KConfig *config ) : ResourceCalendar( config ), ResourceKolabBase( "ResourceKolab-libkcal" ), mCalendar( TQString::fromLatin1("UTC") ), mOpen( false ),mResourceChangedTimer( 0, - "mResourceChangedTimer" ) + "mResourceChangedTimer" ), mBatchAddingInProgress( false ) { + if ( !config ) { + setResourceName( i18n( "Kolab Server" ) ); + } setType( "imap" ); connect( &mResourceChangedTimer, TQT_SIGNAL( timeout() ), this, TQT_SLOT( slotEmitResourceChanged() ) ); @@ -132,7 +134,7 @@ bool ResourceKolab::doOpen() && openResource( config, kmailJournalContentsType, mJournalSubResources ); } -static void closeResource( KConfig& config, ResourceMap& map ) +static void writeResourceConfig( KConfig& config, ResourceMap& map ) { ResourceMap::ConstIterator it; for ( it = map.begin(); it != map.end(); ++it ) { @@ -148,10 +150,7 @@ void ResourceKolab::doClose() return; mOpen = false; - KConfig config( configFile() ); - closeResource( config, mEventSubResources ); - closeResource( config, mTodoSubResources ); - closeResource( config, mJournalSubResources ); + writeConfig(); } bool ResourceKolab::loadSubResource( const TQString& subResource, @@ -217,11 +216,20 @@ bool ResourceKolab::loadSubResource( const TQString& subResource, bool ResourceKolab::doLoad() { if (!mUidMap.isEmpty() ) { + emit resourceLoaded( this ); return true; } mUidMap.clear(); - return loadAllEvents() & loadAllTodos() & loadAllJournals(); + bool result = loadAllEvents() & loadAllTodos() & loadAllJournals(); + if ( result ) { + emit resourceLoaded( this ); + } else { + // FIXME: anyone know if the resource correctly calls loadError() + // if it has one? + } + + return result; } bool ResourceKolab::doLoadAll( ResourceMap& map, const char* mimetype ) @@ -297,19 +305,54 @@ bool ResourceKolab::doSave() && kmailTriggerSync( kmailJournalContentsType ); */ } -void ResourceKolab::incidenceUpdatedSilent( KCal::IncidenceBase* incidencebase) +void ResourceKolab::incidenceUpdatedSilent( KCal::IncidenceBase* incidencebase ) { - const TQString uid = incidencebase->uid(); + const TQString uid = incidencebase->uid(); //kdDebug() << k_funcinfo << uid << endl; if ( mUidsPendingUpdate.contains( uid ) || mUidsPendingAdding.contains( uid ) ) { /* We are currently processing this event ( removing and readding or * adding it ). If so, ignore this update. Keep the last of these around * and process once we hear back from KMail on this event. */ - mPendingUpdates.replace( uid, incidencebase ); + mPendingUpdates.remove( uid ); + mPendingUpdates.insert( uid, incidencebase ); return; } + { // start optimization + /** + KOrganizer and libkcal like calling two Incidence::updated() + for only one user change. That's because after a change, + IncidenceChanger calls incidence->setRevision( rev++ ); + which also calls Incidence::updated(). + + Lets ignore the first updated() and only send to kmail + the second. This makes things faster. + */ + + //IncidenceBase doesn't have revision(), downcast needed. + Incidence *i = dynamic_cast<Incidence*>( incidencebase ); + + if ( i ) { + bool ignoreThisUpdate = false; + + if ( !mLastKnownRevisions.contains( uid ) ) { + mLastKnownRevisions[uid] = i->revision(); + } + + // update the last known revision + if ( mLastKnownRevisions[uid] < i->revision() ) { + mLastKnownRevisions[uid] = i->revision(); + } else { + ignoreThisUpdate = true; + } + + if ( ignoreThisUpdate ) { + return; + } + } + } // end optimization + TQString subResource; Q_UINT32 sernum = 0; if ( mUidMap.contains( uid ) ) { @@ -317,78 +360,85 @@ void ResourceKolab::incidenceUpdatedSilent( KCal::IncidenceBase* incidencebase) sernum = mUidMap[ uid ].serialNumber(); mUidsPendingUpdate.append( uid ); } - sendKMailUpdate( incidencebase, subResource, sernum ); + sendKMailUpdate( incidencebase, subResource, sernum ); } void ResourceKolab::incidenceUpdated( KCal::IncidenceBase* incidencebase ) { - if ( incidencebase->isReadOnly() ) return; + if ( incidencebase->isReadOnly() ) { + return; + } + incidencebase->setSyncStatusSilent( KCal::Event::SYNCMOD ); incidencebase->setLastModified( TQDateTime::currentDateTime() ); + // we should probably update the revision number here, // or internally in the Event itself when certain things change. // need to verify with ical documentation. incidenceUpdatedSilent( incidencebase ); - } void ResourceKolab::resolveConflict( KCal::Incidence* inc, const TQString& subresource, Q_UINT32 sernum ) { - if ( ! inc ) - return; - if ( ! mResolveConflict ) { - // we should do no conflict resolution - delete inc; - return; - } - const TQString origUid = inc->uid(); - Incidence* local = mCalendar.incidence( origUid ); - Incidence* localIncidence = 0; - Incidence* addedIncidence = 0; - Incidence* result = 0; - if ( local ) { - if (*local == *inc) { - // real duplicate, remove the second one - result = local; - } else { - KIncidenceChooser* ch = new KIncidenceChooser(); - ch->setIncidence( local ,inc ); - if ( KIncidenceChooser::chooseMode == KIncidenceChooser::ask ) { - connect ( this, TQT_SIGNAL( useGlobalMode() ), ch, TQT_SLOT ( useGlobalMode() ) ); - if ( ch->exec() ) - if ( KIncidenceChooser::chooseMode != KIncidenceChooser::ask ) - emit useGlobalMode() ; - } - result = ch->getIncidence(); - delete ch; - } - } else { - // nothing there locally, just take the new one. Can't Happen (TM) - result = inc; - } - if ( result == local ) { - delete inc; - localIncidence = local; - } else if ( result == inc ) { - addedIncidence = inc; - } else if ( result == 0 ) { // take both - addedIncidence = inc; - addedIncidence->setSummary( i18n("Copy of: %1").arg( addedIncidence->summary() ) ); - addedIncidence->setUid( CalFormat::createUniqueId() ); - localIncidence = local; - } - bool silent = mSilent; - mSilent = false; - if ( !localIncidence ) { - deleteIncidence( local ); // remove local from kmail - } - mUidsPendingDeletion.append( origUid ); - if ( addedIncidence ) { - sendKMailUpdate( addedIncidence, subresource, sernum ); - } else { - kmailDeleteIncidence( subresource, sernum );// remove new from kmail - } - mSilent = silent; + if ( !inc ) { + return; + } + + if ( !mResolveConflict ) { + // we should do no conflict resolution + delete inc; + return; + } + const TQString origUid = inc->uid(); + Incidence* local = mCalendar.incidence( origUid ); + Incidence* localIncidence = 0; + Incidence* addedIncidence = 0; + Incidence* result = 0; + if ( local ) { + if ( *local == *inc ) { + // real duplicate, remove the second one + result = local; + } else { + KIncidenceChooser* ch = new KIncidenceChooser(); + ch->setIncidence( local ,inc ); + if ( KIncidenceChooser::chooseMode == KIncidenceChooser::ask ) { + connect ( this, TQT_SIGNAL( useGlobalMode() ), ch, TQT_SLOT ( useGlobalMode() ) ); + if ( ch->exec() ) { + if ( KIncidenceChooser::chooseMode != KIncidenceChooser::ask ) { + emit useGlobalMode() ; + } + } + } + result = ch->getIncidence(); + delete ch; + } + } else { + // nothing there locally, just take the new one. Can't Happen (TM) + result = inc; + } + if ( result == local ) { + delete inc; + localIncidence = local; + } else if ( result == inc ) { + addedIncidence = inc; + } else if ( result == 0 ) { // take both + addedIncidence = inc; + addedIncidence->setSummary( i18n("Copy of: %1").arg( addedIncidence->summary() ) ); + addedIncidence->setUid( CalFormat::createUniqueId() ); + localIncidence = local; + } + const bool silent = mSilent; + mSilent = false; + if ( !localIncidence ) { + deleteIncidence( local ); // remove local from kmail + } + mUidsPendingDeletion.append( origUid ); + if ( addedIncidence ) { + sendKMailUpdate( addedIncidence, subresource, sernum ); + } else { + kmailDeleteIncidence( subresource, sernum );// remove new from kmail + } + mSilent = silent; } void ResourceKolab::addIncidence( const char* mimetype, const TQString& data, const TQString& subResource, Q_UINT32 sernum ) @@ -457,15 +507,24 @@ bool ResourceKolab::sendKMailUpdate( KCal::IncidenceBase* incidencebase, const T TQStringList attURLs, attMimeTypes, attNames; TQValueList<KTempFile*> tmpFiles; for ( KCal::Attachment::List::ConstIterator it = atts.constBegin(); it != atts.constEnd(); ++it ) { - KTempFile* tempFile = new KTempFile; - TQCString decoded = KCodecs::base64Decode( TQCString( (*it)->data() ) ); - tempFile->file()->writeBlock( decoded.data(), decoded.length() ); - tempFile->close(); - KURL url; - url.setPath( tempFile->name() ); - attURLs.append( url.url() ); - attMimeTypes.append( (*it)->mimeType() ); - attNames.append( (*it)->label() ); + if ( (*it)->isUri() ) { + continue; + } + KTempFile *tempFile = new KTempFile; + if ( tempFile->status() == 0 ) { // open ok + const TQByteArray decoded = (*it)->decodedData() ; + + tempFile->file()->writeBlock( decoded.data(), decoded.count() ); + KURL url; + url.setPath( tempFile->name() ); + attURLs.append( url.url() ); + attMimeTypes.append( (*it)->mimeType() ); + attNames.append( (*it)->label() ); + tempFile->close(); + tmpFiles.append( tempFile ); + } else { + kdWarning(5006) << "Cannot open temporary file for attachment"; + } } TQStringList deletedAtts; if ( kmailListAttachments( deletedAtts, subresource, sernum ) ) { @@ -474,8 +533,9 @@ bool ResourceKolab::sendKMailUpdate( KCal::IncidenceBase* incidencebase, const T } } CustomHeaderMap customHeaders; - if ( incidence->schedulingID() != incidence->uid() ) + if ( incidence->schedulingID() != incidence->uid() ) { customHeaders.insert( "X-Kolab-SchedulingID", incidence->schedulingID() ); + } TQString subject = incidencebase->uid(); if ( !isXMLStorageFormat ) subject.prepend( "iCal " ); // conform to the old style @@ -499,34 +559,49 @@ bool ResourceKolab::addIncidence( KCal::Incidence* incidence, const TQString& _s Q_UINT32 sernum ) { Q_ASSERT( incidence ); - if ( !incidence ) return false; + if ( !incidence ) { + return false; + } + + kdDebug() << "Resourcekolab, adding incidence " + << incidence->summary() + << "; subresource = " << _subresource + << "; sernum = " << sernum + << "; mBatchAddingInProgress = " << mBatchAddingInProgress + << endl; + TQString uid = incidence->uid(); TQString subResource = _subresource; Kolab::ResourceMap *map = &mEventSubResources; // don't use a ref here! const TQString& type = incidence->type(); - if ( type == "Event" ) + if ( type == "Event" ) { map = &mEventSubResources; - else if ( type == "Todo" ) + } else if ( type == "Todo" ) { map = &mTodoSubResources; - else if ( type == "Journal" ) + } else if ( type == "Journal" ) { map = &mJournalSubResources; - else + } else { kdWarning() << "unknown type " << type << endl; + } if ( !mSilent ) { /* We got this one from the user, tell KMail. */ // Find out if this event was previously stored in KMail bool newIncidence = _subresource.isEmpty(); if ( newIncidence ) { + ResourceType type = Incidences; // Add a description of the incidence TQString text = "<b><font size=\"+1\">"; - if ( incidence->type() == "Event" ) + if ( incidence->type() == "Event" ) { + type = Events; text += i18n( "Choose the folder where you want to store this event" ); - else if ( incidence->type() == "Todo" ) + } else if ( incidence->type() == "Todo" ) { + type = Tasks; text += i18n( "Choose the folder where you want to store this task" ); - else + } else { text += i18n( "Choose the folder where you want to store this incidence" ); + } text += "<font></b><br>"; if ( !incidence->summary().isEmpty() ) text += i18n( "<b>Summary:</b> %1" ).arg( incidence->summary() ) + "<br>"; @@ -541,24 +616,51 @@ bool ResourceKolab::addIncidence( KCal::Incidence* incidence, const TQString& _s text += "<br>"; if ( incidence->type() == "Event" ) { Event* event = static_cast<Event*>( incidence ); - if ( event->hasEndDate() ) - if ( !event->doesFloat() ) + if ( event->hasEndDate() ) { + if ( !event->doesFloat() ) { text += i18n( "<b>End:</b> %1, %2" ) .arg( event->dtEndDateStr(), event->dtEndTimeStr() ); - else + } else { text += i18n( "<b>End:</b> %1" ).arg( event->dtEndDateStr() ); + } + } text += "<br>"; } - subResource = findWritableResource( *map, text ); + + // Lets not warn the user 100 times that there's no writable resource + // and not ask 100 times which resource to use + if ( !mBatchAddingInProgress || !mLastUsedResources.contains( type ) ) { + subResource = findWritableResource( type, *map, text ); + mLastUsedResources[type] = subResource; + } else { + subResource = mLastUsedResources[type]; + } + + if ( subResource.isEmpty() ) { + switch( mErrorCode ) { + case NoWritableFound: + setException( new ErrorFormat( ErrorFormat::NoWritableFound ) ); + break; + case UserCancel: + setException( new ErrorFormat( ErrorFormat::UserCancel ) ); + break; + case NoError: + break; + } + } } - if ( subResource.isEmpty() ) + if ( subResource.isEmpty() ) { + endAddingIncidences(); // cleanup + kdDebug(5650) << "ResourceKolab: subResource is empty" << endl; return false; + } mNewIncidencesMap.insert( uid, subResource ); if ( !sendKMailUpdate( incidence, subResource, sernum ) ) { kdError(5650) << "Communication problem in ResourceKolab::addIncidence()\n"; + endAddingIncidences(); // cleanup return false; } else { // KMail is doing it's best to add the event now, put a sticker on it, @@ -568,13 +670,14 @@ bool ResourceKolab::addIncidence( KCal::Incidence* incidence, const TQString& _s /* Add to the cache immediately if this is a new event coming from * KOrganizer. It relies on the incidence being in the calendar when * addIncidence returns. */ - if ( newIncidence ) { + if ( newIncidence || sernum == 0 ) { mCalendar.addIncidence( incidence ); - incidence->registerObserver( this ); + incidence->registerObserver( this ); } } } else { /* KMail told us */ - bool ourOwnUpdate = mUidsPendingUpdate.contains( uid ); + const bool ourOwnUpdate = mUidsPendingUpdate.contains( uid ); + kdDebug( 5650 ) << "addIncidence: ourOwnUpdate " << ourOwnUpdate << endl; /* Check if we updated this one, which means kmail deleted and added it. * We know the new state, so lets just not do much at all. The old incidence * in the calendar remains valid, but the serial number changed, so we need to @@ -592,6 +695,7 @@ bool ResourceKolab::addIncidence( KCal::Incidence* incidence, const TQString& _s if ( mUidMap.contains( uid ) ) { if ( mUidMap[ uid ].resource() == subResource ) { if ( (*map)[ subResource ].writable() ) { + kdDebug( 5650 ) << "lets resolve the conflict " << endl; resolveConflict( incidence, subResource, sernum ); } else { kdWarning( 5650 ) << "Duplicate event in a read-only folder detected! " @@ -601,8 +705,13 @@ bool ResourceKolab::addIncidence( KCal::Incidence* incidence, const TQString& _s } else { // duplicate uid in a different folder, do the internal-uid tango incidence->setSchedulingID( uid ); - incidence->setUid(CalFormat::createUniqueId( ) ); + + incidence->setUid( CalFormat::createUniqueId( ) ); uid = incidence->uid(); + + /* Will be needed when kmail triggers a delete, so we don't delete the inocent + * incidence that's sharing the uid with this one */ + mOriginalUID2fakeUID[qMakePair( incidence->schedulingID(), subResource )] = uid; } } /* Add to the cache if the add didn't come from KOrganizer, in which case @@ -636,13 +745,18 @@ bool ResourceKolab::addIncidence( KCal::Incidence* incidence, const TQString& _s return true; } +bool ResourceKolab::addEvent( KCal::Event *event ) +{ + return addEvent( event, TQString() ); +} -bool ResourceKolab::addEvent( KCal::Event* event ) +bool ResourceKolab::addEvent( KCal::Event *event, const TQString &subResource ) { - if ( mUidMap.contains( event->uid() ) ) + if ( mUidMap.contains( event->uid() ) ) { return true; //noop - else - return addIncidence( event, TQString::null, 0 ); + } else { + return addIncidence( event, subResource, 0 ); + } } void ResourceKolab::addEvent( const TQString& xml, const TQString& subresource, @@ -650,14 +764,16 @@ void ResourceKolab::addEvent( const TQString& xml, const TQString& subresource, { KCal::Event* event = Kolab::Event::xmlToEvent( xml, mCalendar.timeZoneId(), this, subresource, sernum ); Q_ASSERT( event ); - if( event ) { + if ( event ) { addIncidence( event, subresource, sernum ); } } bool ResourceKolab::deleteIncidence( KCal::Incidence* incidence ) { - if ( incidence->isReadOnly() ) return false; + if ( incidence->isReadOnly() ) { + return false; + } const TQString uid = incidence->uid(); if( !mUidMap.contains( uid ) ) return false; // Odd @@ -709,12 +825,18 @@ KCal::Event::List ResourceKolab::rawEvents( const TQDate& start, return mCalendar.rawEvents( start, end, inclusive ); } -bool ResourceKolab::addTodo( KCal::Todo* todo ) +bool ResourceKolab::addTodo( KCal::Todo *todo ) { - if ( mUidMap.contains( todo->uid() ) ) + return addTodo( todo, TQString() ); +} + +bool ResourceKolab::addTodo( KCal::Todo *todo, const TQString &subResource ) +{ + if ( mUidMap.contains( todo->uid() ) ) { return true; //noop - else - return addIncidence( todo, TQString::null, 0 ); + } else { + return addIncidence( todo, subResource, 0 ); + } } void ResourceKolab::addTodo( const TQString& xml, const TQString& subresource, @@ -722,8 +844,9 @@ void ResourceKolab::addTodo( const TQString& xml, const TQString& subresource, { KCal::Todo* todo = Kolab::Task::xmlToTask( xml, mCalendar.timeZoneId(), this, subresource, sernum ); Q_ASSERT( todo ); - if( todo ) - addIncidence( todo, subresource, sernum ); + if ( todo ) { + addIncidence( todo, subresource, sernum ); + } } bool ResourceKolab::deleteTodo( KCal::Todo* todo ) @@ -746,12 +869,17 @@ KCal::Todo::List ResourceKolab::rawTodosForDate( const TQDate& date ) return mCalendar.rawTodosForDate( date ); } -bool ResourceKolab::addJournal( KCal::Journal* journal ) +bool ResourceKolab::addJournal( KCal::Journal *journal ) +{ + return addJournal( journal, TQString() ); +} + +bool ResourceKolab::addJournal( KCal::Journal *journal, const TQString &subResource ) { if ( mUidMap.contains( journal->uid() ) ) return true; //noop else - return addIncidence( journal, TQString::null, 0 ); + return addIncidence( journal, subResource, 0 ); } void ResourceKolab::addJournal( const TQString& xml, const TQString& subresource, @@ -839,27 +967,33 @@ bool ResourceKolab::fromKMailAddIncidence( const TQString& type, bool rc = true; TemporarySilencer t( this ); // RAII if ( type != kmailCalendarContentsType && type != kmailTodoContentsType - && type != kmailJournalContentsType ) + && type != kmailJournalContentsType ) { // Not ours return false; - if ( !subresourceActive( subResource ) ) return true; + } + + if ( !subresourceActive( subResource ) ) { + return true; + } if ( format == KMailICalIface::StorageXML ) { // If this data file is one of ours, load it here - if ( type == kmailCalendarContentsType ) + if ( type == kmailCalendarContentsType ) { addEvent( data, subResource, sernum ); - else if ( type == kmailTodoContentsType ) + } else if ( type == kmailTodoContentsType ) { addTodo( data, subResource, sernum ); - else if ( type == kmailJournalContentsType ) + } else if ( type == kmailJournalContentsType ) { addJournal( data, subResource, sernum ); - else + } else { rc = false; + } } else { Incidence *inc = mFormat.fromString( data ); - if ( !inc ) - rc = false; - else + if ( inc ) { addIncidence( inc, subResource, sernum ); + } else { + rc = false; + } } return rc; } @@ -881,13 +1015,25 @@ void ResourceKolab::fromKMailDelIncidence( const TQString& type, // It's good to know if was deleted, but we are waiting on a new one to // replace it, so let's just sit tight. } else { + TQString uidToUse; + + QPair<TQString, TQString> p( uid, subResource ); + if ( mOriginalUID2fakeUID.contains( p ) ) { + // Incidence with the same uid in a different folder... + // use the UID that addIncidence(...) generated + uidToUse = mOriginalUID2fakeUID[p]; + } else { + uidToUse = uid; + } + // We didn't trigger this, so KMail did, remove the reference to the uid - KCal::Incidence* incidence = mCalendar.incidence( uid ); + KCal::Incidence* incidence = mCalendar.incidence( uidToUse ); if( incidence ) { incidence->unRegisterObserver( this ); mCalendar.deleteIncidence( incidence ); } - mUidMap.remove( uid ); + mUidMap.remove( uidToUse ); + mOriginalUID2fakeUID.remove( p ); mResourceChangedTimer.changeInterval( 100 ); } } @@ -1039,6 +1185,23 @@ void ResourceKolab::setSubresourceActive( const TQString &subresource, bool v ) } mResourceChangedTimer.changeInterval( 100 ); } + TQTimer::singleShot( 0, this, TQT_SLOT(writeConfig()) ); +} + +bool ResourceKolab::subresourceWritable( const TQString& subresource ) const +{ + // Workaround: The ResourceView in KOrganizer wants to know this + // before it opens the resource :-( Make sure we are open + const_cast<ResourceKolab*>( this )->doOpen(); + + if ( mEventSubResources.contains( subresource ) ) + return mEventSubResources[ subresource ].writable(); + if ( mTodoSubResources.contains( subresource ) ) + return mTodoSubResources[ subresource ].writable(); + if ( mJournalSubResources.contains( subresource ) ) + return mJournalSubResources[ subresource ].writable(); + + return false; //better a safe default } void ResourceKolab::slotEmitResourceChanged() @@ -1053,7 +1216,6 @@ KABC::Lock* ResourceKolab::lock() return new KABC::LockNull( true ); } - Kolab::ResourceMap* ResourceKolab::subResourceMap( const TQString& contentsType ) { if ( contentsType == kmailCalendarContentsType ) { @@ -1122,6 +1284,7 @@ bool ResourceKolab::unloadSubResource( const TQString& subResource ) const bool silent = mSilent; mSilent = true; Kolab::UidMap::Iterator mapIt = mUidMap.begin(); + TQPtrList<KCal::Incidence> incidences; while ( mapIt != mUidMap.end() ) { Kolab::UidMap::Iterator it = mapIt++; @@ -1130,11 +1293,18 @@ bool ResourceKolab::unloadSubResource( const TQString& subResource ) // FIXME incidence() is expensive KCal::Incidence* incidence = mCalendar.incidence( it.key() ); if( incidence ) { - incidence->unRegisterObserver( this ); - mCalendar.deleteIncidence( incidence ); + // register all observers first before actually deleting them + // in case of inter-incidence relations the other part will get + // the change notification otherwise + incidence->unRegisterObserver( this ); + incidences.append( incidence ); } mUidMap.remove( it ); } + TQPtrListIterator<KCal::Incidence> it( incidences ); + for ( ; it.current(); ++it ) { + mCalendar.deleteIncidence( it.current() ); + } mSilent = silent; return true; } @@ -1150,4 +1320,23 @@ TQString ResourceKolab::subresourceType( const TQString &resource ) return TQString(); } +void ResourceKolab::writeConfig() +{ + KConfig config( configFile() ); + writeResourceConfig( config, mEventSubResources ); + writeResourceConfig( config, mTodoSubResources ); + writeResourceConfig( config, mJournalSubResources ); +} + +void ResourceKolab::beginAddingIncidences() +{ + mBatchAddingInProgress = true; +} + +void ResourceKolab::endAddingIncidences() +{ + mBatchAddingInProgress = false; + mLastUsedResources.clear(); +} + #include "resourcekolab.moc" diff --git a/kresources/kolab/kcal/resourcekolab.h b/kresources/kolab/kcal/resourcekolab.h index e68c2c6bf..dda5ba32a 100644 --- a/kresources/kolab/kcal/resourcekolab.h +++ b/kresources/kolab/kcal/resourcekolab.h @@ -70,8 +70,9 @@ public: void doClose(); // The libkcal functions. See the resource for descriptions - bool addEvent( KCal::Event* anEvent ); - bool deleteEvent( KCal::Event* ); + KDE_DEPRECATED bool addEvent( KCal::Event *event ); + bool addEvent( KCal::Event *event, const TQString &subResource ); + bool deleteEvent( KCal::Event * ); KCal::Event* event( const TQString &UniqueStr ); KCal::Event::List rawEvents( EventSortField sortField = EventSortUnsorted, SortDirection sortDirection = SortDirectionAscending ); KCal::Event::List rawEventsForDate( @@ -82,15 +83,17 @@ public: KCal::Event::List rawEvents( const TQDate& start, const TQDate& end, bool inclusive = false ); - bool addTodo( KCal::Todo* todo ); - bool deleteTodo( KCal::Todo* ); - KCal::Todo* todo( const TQString& uid ); + KDE_DEPRECATED bool addTodo( KCal::Todo * todo ); + bool addTodo( KCal::Todo *todo, const TQString &subResource ); + bool deleteTodo( KCal::Todo * ); + KCal::Todo* todo( const TQString &uid ); KCal::Todo::List rawTodos( TodoSortField sortField = TodoSortUnsorted, SortDirection sortDirection = SortDirectionAscending ); KCal::Todo::List rawTodosForDate( const TQDate& date ); - bool addJournal( KCal::Journal* ); - bool deleteJournal( KCal::Journal* ); - KCal::Journal* journal( const TQString& uid ); + KDE_DEPRECATED bool addJournal( KCal::Journal * ); + bool addJournal( KCal::Journal *, const TQString &subResource ); + bool deleteJournal( KCal::Journal * ); + KCal::Journal* journal( const TQString &uid ); KCal::Journal::List rawJournals( JournalSortField sortField = JournalSortUnsorted, SortDirection sortDirection = SortDirectionAscending ); KCal::Journal::List rawJournalsForDate( const TQDate &date ); @@ -128,6 +131,9 @@ public: /** (De)activate the subresource */ virtual void setSubresourceActive( const TQString &, bool ); + /** Is this subresource writable? */ + bool subresourceWritable( const TQString& ) const; + /** What is the label for this subresource? */ virtual const TQString labelForSubresource( const TQString& resource ) const; @@ -140,10 +146,16 @@ public: KABC::Lock* lock(); + void beginAddingIncidences(); + + void endAddingIncidences(); + signals: void useGlobalMode(); protected slots: void slotEmitResourceChanged(); + void writeConfig(); + protected: /** * Return list of alarms which are relevant for the current user. These @@ -157,7 +169,11 @@ private: void addIncidence( const char* mimetype, const TQString& xml, const TQString& subResource, Q_UINT32 sernum ); - bool addIncidence( KCal::Incidence* i, const TQString& subresource, + + /** + Caller guarantees i is not null. + */ + bool addIncidence( KCal::Incidence *i, const TQString& subresource, Q_UINT32 sernum ); void addEvent( const TQString& xml, const TQString& subresource, @@ -215,6 +231,36 @@ private: */ TQMap<TQString, TQString> mNewIncidencesMap; int mProgressDialogIncidenceLimit; + + /** + * If a user has a subresource for viewing another user's folder then it can happen + * that addIncidence(...) adds an incidence with an already existing UID. + * + * When this happens, addIncidence(...) sets a new random UID and stores the + * original UID using incidence->setSchedulingID(uid) because KCal doesn't + * allow two incidences to have the same UID. + * + * This map keeps track of the generated UIDs (which are local) so we can delete the + * right incidence inside fromKMailDelIncidence(...) whenever we sync. + * + * The key is originalUID,subResource and the value is the fake UID. + */ + TQMap< QPair<TQString, TQString>, TQString > mOriginalUID2fakeUID; + + bool mBatchAddingInProgress; + TQMap<Kolab::ResourceType,TQString> mLastUsedResources; + + /** + Indexed by uid, it holds the last known revision of an incidence. + If we receive an update where the incidence still has the same + revision as the last known, we ignore it and don't send it to kmail, + because shortly after, IncidenceChanger will increment the revision + and that will trigger another update. + + If we didn't discard the first update, kmail would have been updated twice. + */ + TQMap<TQString,int> mLastKnownRevisions; + }; struct TemporarySilencer { diff --git a/kresources/kolab/kcal/task.cpp b/kresources/kolab/kcal/task.cpp index 7bbdba978..36876b7d5 100644 --- a/kresources/kolab/kcal/task.cpp +++ b/kresources/kolab/kcal/task.cpp @@ -38,6 +38,41 @@ using namespace Kolab; +// Kolab Storage Specification: +// "The priority can be a number between 1 and 5, with 1 being the highest priority." +// iCalendar (RFC 2445): +// "The priority is specified as an integer in the range +// zero to nine. A value of zero specifies an +// undefined priority. A value of one is the +// highest priority. A value of nine is the lowest +// priority." + +static int kcalPriorityToKolab( const int kcalPriority ) +{ + if ( kcalPriority >= 0 && kcalPriority <= 9 ) { + // We'll map undefined (0) to 3 (default) + // 0 1 2 3 4 5 6 7 8 9 + static const int priorityMap[10] = { 3, 1, 1, 2, 2, 3, 3, 4, 4, 5 }; + return priorityMap[kcalPriority]; + } + else { + kdWarning() << "kcalPriorityToKolab(): Got invalid priority " << kcalPriority << endl; + return 3; + } +} + +static int kolabPrioritytoKCal( const int kolabPriority ) +{ + if ( kolabPriority >= 1 && kolabPriority <= 5 ) { + // 1 2 3 4 5 + static const int priorityMap[5] = { 1, 3, 5, 7, 9 }; + return priorityMap[kolabPriority - 1]; + } + else { + kdWarning() << "kolabPrioritytoKCal(): Got invalid priority " << kolabPriority << endl; + return 5; + } +} KCal::Todo* Task::xmlToTask( const TQString& xml, const TQString& tz, KCal::ResourceKolab *res, const TQString& subResource, Q_UINT32 sernum ) @@ -115,6 +150,26 @@ void Task::setDueDate( const TQDateTime& date ) { mDueDate = date; mHasDueDate = true; + mFloatingStatus = HasTime; +} + +void Task::setDueDate( const TQDate &date ) +{ + mDueDate = date; + mHasDueDate = true; + mFloatingStatus = AllDay; +} + + +void Task::setDueDate( const TQString &date ) +{ + if ( date.length() > 10 ) { + // This is a date + time + setDueDate( stringToDateTime( date ) ); + } else { + // This is only a date + setDueDate( stringToDate( date ) ); + } } TQDateTime Task::dueDate() const @@ -159,10 +214,18 @@ bool Task::loadAttribute( TQDomElement& element ) if ( tagName == "priority" ) { bool ok; - int priority = element.text().toInt( &ok ); - if ( !ok || priority < 0 || priority > 9 ) - priority = 5; - setPriority( priority ); + mKolabPriorityFromDom = element.text().toInt( &ok ); + if ( !ok || mKolabPriorityFromDom < 1 || mKolabPriorityFromDom > 5 ) { + kdWarning() << "loadAttribute(): Invalid \"priority\" value: " << element.text() << endl; + mKolabPriorityFromDom = -1; + } + } else if ( tagName == "x-kcal-priority" ) { + bool ok; + mKCalPriorityFromDom = element.text().toInt( &ok ); + if ( !ok || mKCalPriorityFromDom < 0 || mKCalPriorityFromDom > 9 ) { + kdWarning() << "loadAttribute(): Invalid \"x-kcal-priority\" value: " << element.text() << endl; + mKCalPriorityFromDom = -1; + } } else if ( tagName == "completed" ) { bool ok; int percent = element.text().toInt( &ok ); @@ -182,13 +245,13 @@ bool Task::loadAttribute( TQDomElement& element ) else // Default setStatus( KCal::Incidence::StatusNone ); - } else if ( tagName == "due-date" ) - setDueDate( stringToDateTime( element.text() ) ); - else if ( tagName == "parent" ) + } else if ( tagName == "due-date" ) { + setDueDate( element.text() ); + } else if ( tagName == "parent" ) { setParent( element.text() ); - else if ( tagName == "x-completed-date" ) + } else if ( tagName == "x-completed-date" ) { setCompletedDate( stringToDateTime( element.text() ) ); - else if ( tagName == "start-date" ) { + } else if ( tagName == "start-date" ) { setHasStartDate( true ); setStartDate( element.text() ); } else @@ -203,7 +266,11 @@ bool Task::saveAttributes( TQDomElement& element ) const // Save the base class elements Incidence::saveAttributes( element ); - writeString( element, "priority", TQString::number( priority() ) ); + // We need to save x-kcal-priority as well, since the Kolab priority can only save values from + // 1 to 5, but we have values from 0 to 9, and do not want to loose them + writeString( element, "priority", TQString::number( kcalPriorityToKolab( priority() ) ) ); + writeString( element, "x-kcal-priority", TQString::number( priority() ) ); + writeString( element, "completed", TQString::number( percentCompleted() ) ); switch( status() ) { @@ -232,14 +299,21 @@ bool Task::saveAttributes( TQDomElement& element ) const break; } - if ( hasDueDate() ) - writeString( element, "due-date", dateTimeToString( dueDate() ) ); + if ( hasDueDate() ) { + if ( mFloatingStatus == HasTime ) { + writeString( element, "due-date", dateTimeToString( dueDate() ) ); + } else { + writeString( element, "due-date", dateToString( dueDate().date() ) ); + } + } - if ( !parent().isNull() ) + if ( !parent().isNull() ) { writeString( element, "parent", parent() ); + } - if ( hasCompletedDate() && percentCompleted() == 100) + if ( hasCompletedDate() && percentCompleted() == 100 ) { writeString( element, "x-completed-date", dateTimeToString( completedDate() ) ); + } return true; } @@ -247,6 +321,9 @@ bool Task::saveAttributes( TQDomElement& element ) const bool Task::loadXML( const TQDomDocument& document ) { + mKolabPriorityFromDom = -1; + mKCalPriorityFromDom = -1; + TQDomElement top = document.documentElement(); if ( top.tagName() != "task" ) { @@ -269,6 +346,7 @@ bool Task::loadXML( const TQDomDocument& document ) } loadAttachments(); + decideAndSetPriority(); return true; } @@ -278,7 +356,7 @@ TQString Task::saveXML() const TQDomElement element = document.createElement( "task" ); element.setAttribute( "version", "1.0" ); saveAttributes( element ); - if ( !hasStartDate() ) { + if ( !hasStartDate() && startDate().isValid() ) { // events and journals always have a start date, but tasks don't. // Remove the entry done by the inherited save above, because we // don't have one. @@ -299,21 +377,68 @@ void Task::setFields( const KCal::Todo* task ) setStatus( task->status() ); setHasStartDate( task->hasStartDate() ); - if ( task->hasDueDate() ) + if ( task->hasDueDate() ) { setDueDate( localToUTC( task->dtDue() ) ); - else + if ( task->doesFloat() ) { + // This is a floating task. Don't timezone move this one + mFloatingStatus = AllDay; + setDueDate( task->dtDue().date() ); + } else { + mFloatingStatus = HasTime; + setDueDate( localToUTC( task->dtDue() ) ); + } + } else { mHasDueDate = false; - if ( task->relatedTo() ) + } + + if ( task->relatedTo() ) { setParent( task->relatedTo()->uid() ); - else if ( !task->relatedToUid().isEmpty() ) - setParent( task->relatedToUid() ); - else + } else if ( !task->relatedToUid().isEmpty() ) { + setParent( task->relatedToUid( ) ); + } else { setParent( TQString::null ); + } - if ( task->hasCompletedDate() && task->percentComplete() == 100 ) + if ( task->hasCompletedDate() && task->percentComplete() == 100 ) { setCompletedDate( localToUTC( task->completed() ) ); - else + } else { mHasCompletedDate = false; + } +} + +void Task::decideAndSetPriority() +{ + // If we have both Kolab and KCal values in the XML, we prefer the KCal value, but only if the + // values are still in sync + if ( mKolabPriorityFromDom != -1 && mKCalPriorityFromDom != -1 ) { + const bool inSync = ( kcalPriorityToKolab( mKCalPriorityFromDom ) == mKolabPriorityFromDom ); + if ( inSync ) { + setPriority( mKCalPriorityFromDom ); + } + else { + // Out of sync, some other client changed the Kolab priority, so we have to ignore our + // KCal priority + setPriority( kolabPrioritytoKCal( mKolabPriorityFromDom ) ); + } + } + + // Only KCal priority set, use that. + else if ( mKolabPriorityFromDom == -1 && mKCalPriorityFromDom != -1 ) { + kdWarning() << "decideAndSetPriority(): No Kolab priority found, only the KCal priority!" << endl; + setPriority( mKCalPriorityFromDom ); + } + + // Only Kolab priority set, use that + else if ( mKolabPriorityFromDom != -1 && mKCalPriorityFromDom == -1 ) { + setPriority( kolabPrioritytoKCal( mKolabPriorityFromDom ) ); + } + + // No priority set, use the default + else { + // According the RFC 2445, we should use 0 here, for undefined priority, but AFAIK KOrganizer + // doesn't support that, so we'll use 5. + setPriority( 5 ); + } } void Task::saveTo( KCal::Todo* task ) diff --git a/kresources/kolab/kcal/task.h b/kresources/kolab/kcal/task.h index 5dfb55854..f7e7c6d51 100644 --- a/kresources/kolab/kcal/task.h +++ b/kresources/kolab/kcal/task.h @@ -86,7 +86,9 @@ public: virtual void setHasStartDate( bool ); virtual bool hasStartDate() const; - virtual void setDueDate( const TQDateTime& date ); + virtual void setDueDate( const TQDateTime &date ); + virtual void setDueDate( const TQString &date ); + virtual void setDueDate( const TQDate &date ); virtual TQDateTime dueDate() const; virtual bool hasDueDate() const; @@ -110,7 +112,19 @@ protected: // Read all known fields from this ical todo void setFields( const KCal::Todo* ); + // This sets the priority of this task by looking at mKolabPriorityFromDom and + // mKCalPriorityFromDom. + void decideAndSetPriority(); + + // This is the KCal priority, not the Kolab priority. + // See kcalPriorityToKolab() and kolabPrioritytoKCal(). int mPriority; + + // Those priority values are the raw values read by loadAttribute(). + // They will be converted later in decideAndSetPriority(). + int mKolabPriorityFromDom; + int mKCalPriorityFromDom; + int mPercentCompleted; KCal::Incidence::Status mStatus; TQString mParent; |