summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Mollekopf <chrigi_1@fastmail.fm>2013-02-19 16:59:23 (GMT)
committerChristian Mollekopf <chrigi_1@fastmail.fm>2013-02-19 16:59:23 (GMT)
commitd58df884dde2633c818845c2eaccf66486b39075 (patch)
tree8091b5798e957953b8936138c1d13ae2b4b58bd5
parentc289e69c72d3c34484e7ea4d5c13e4d70ce862ed (diff)
downloadlibcalendaring-d58df884dde2633c818845c2eaccf66486b39075.tar.gz
From kdepimlibs 7c0d110a01b6b0e38f373af95180b1f05d2f9e04 (wihtout debug message)
Don't mix up CREATED and DTSTAMP, and introduce X-KDE-IMPLEMENTATION-VERSION. CREATED is the creation-date of the icalendar object, and not the time of serialization. DTSTAMP is the last-modification-date without METHOD, and the time of serialization with a METHOD. This patch fixes the following problems: * dtstamp and created exchanged when writing any ical object * created date is not preserved * last-modification-date is not preserved * vFreebusy iTip messages contain a created date (but must not) Backwardscompatibility is ensured by only applying the fix for ical objects which have been written using the new implementation. For this X-KDE-IMPLEMENTATION-VERSION has been introduced to track the implementation version which makes it possible to provide proper backwards compatiblity. BUG: 310469 REVIEW: 107480 FIXED-IN: 4.9.5
-rw-r--r--kcalcore/compat.cpp80
-rw-r--r--kcalcore/compat.h81
-rw-r--r--kcalcore/icalformat_p.cpp62
-rw-r--r--kcalcore/icalformat_p.h6
-rw-r--r--kcalcore/tests/CMakeLists.txt1
-rw-r--r--kcalcore/tests/testcreateddatecompat.cpp80
-rw-r--r--kcalcore/tests/testcreateddatecompat.h34
7 files changed, 330 insertions, 14 deletions
diff --git a/kcalcore/compat.cpp b/kcalcore/compat.cpp
index 733e99d..efccbad 100644
--- a/kcalcore/compat.cpp
+++ b/kcalcore/compat.cpp
@@ -41,14 +41,13 @@
using namespace KCalCore;
-Compat *CompatFactory::createCompat( const QString &productId )
+Compat *CompatFactory::createCompat( const QString &productId, const QString &implementationVersion )
{
Compat *compat = 0;
int korg = productId.indexOf( "KOrganizer" );
int outl9 = productId.indexOf( "Outlook 9.0" );
- // TODO: Use the version of LibKCal to determine the compat class...
if ( korg >= 0 ) {
int versionStart = productId.indexOf( " ", korg );
if ( versionStart >= 0 ) {
@@ -83,10 +82,14 @@ Compat *CompatFactory::createCompat( const QString &productId )
qDebug() << "Generating compat for Outlook < 2000 (Outlook 9.0)";
compat = new CompatOutlook9;
}
-
if ( !compat ) {
compat = new Compat;
}
+ //Older implementations lacked the implementation version, so apply this fix if it is a file from kontact and the version is missing.
+ if ( implementationVersion.isEmpty() && ( productId.contains("libkcal") || productId.contains("KOrganizer") || productId.contains("KAlarm") ) ) {
+ compat = new CompatPre410( compat );
+ }
+
return compat;
}
@@ -143,6 +146,64 @@ bool Compat::useTimeZoneShift()
return true;
}
+void Compat::setCreatedToDtStamp(const Incidence::Ptr& incidence, const KDateTime& dtstamp)
+{
+ Q_UNUSED( incidence );
+ Q_UNUSED( dtstamp );
+}
+
+
+struct CompatDecorator::Private {
+ Compat *compat;
+};
+
+CompatDecorator::CompatDecorator(Compat *compat)
+: d(new CompatDecorator::Private)
+{
+ d->compat = compat;
+}
+
+CompatDecorator::~CompatDecorator()
+{
+ delete d->compat;
+ delete d;
+}
+
+void CompatDecorator::fixEmptySummary( const Incidence::Ptr &incidence )
+{
+ d->compat->fixEmptySummary( incidence );
+}
+
+void CompatDecorator::fixAlarms( const Incidence::Ptr &incidence )
+{
+ d->compat->fixAlarms( incidence );
+}
+
+void CompatDecorator::fixFloatingEnd( QDate &date )
+{
+ d->compat->fixFloatingEnd( date );
+}
+
+void CompatDecorator::fixRecurrence( const Incidence::Ptr &incidence )
+{
+ d->compat->fixRecurrence( incidence );
+}
+
+int CompatDecorator::fixPriority( int priority )
+{
+ return d->compat->fixPriority( priority );
+}
+
+bool CompatDecorator::useTimeZoneShift()
+{
+ return d->compat->useTimeZoneShift();
+}
+
+void CompatDecorator::setCreatedToDtStamp(const Incidence::Ptr& incidence, const KDateTime& dtstamp)
+{
+ d->compat->setCreatedToDtStamp( incidence, dtstamp );
+}
+
void CompatPre35::fixRecurrence( const Incidence::Ptr &incidence )
{
Recurrence *recurrence = incidence->recurrence();
@@ -278,3 +339,16 @@ bool Compat32PrereleaseVersions::useTimeZoneShift()
{
return false;
}
+
+CompatPre410::CompatPre410( Compat* decoratedCompat )
+: CompatDecorator( decoratedCompat )
+{
+
+}
+
+void CompatPre410::setCreatedToDtStamp( const Incidence::Ptr& incidence, const KDateTime &dtstamp )
+{
+ if ( dtstamp.isValid() ) {
+ incidence->setCreated( dtstamp );
+ }
+}
diff --git a/kcalcore/compat.h b/kcalcore/compat.h
index c74aa9d..d1fbf5b 100644
--- a/kcalcore/compat.h
+++ b/kcalcore/compat.h
@@ -58,7 +58,7 @@ class CompatFactory
a supported calendar format.
@return A pointer to a Compat object which is owned by the caller.
*/
- static Compat *createCompat( const QString &productId );
+ static Compat *createCompat( const QString &productId, const QString &implementationVersion );
};
/**
@@ -119,6 +119,11 @@ class Compat
*/
virtual bool useTimeZoneShift();
+ /**
+ Exchanges created and dtstamp.
+ */
+ virtual void setCreatedToDtStamp( const Incidence::Ptr &incidence, const KDateTime &dtstamp );
+
private:
//@cond PRIVATE
Q_DISABLE_COPY( Compat )
@@ -129,6 +134,59 @@ class Compat
/**
@brief
+ Decorator so multiple compatibility classes can be stacked.
+*/
+class CompatDecorator : public Compat
+{
+ public:
+ CompatDecorator(Compat *decoratedCompat);
+ virtual ~CompatDecorator();
+ /**
+ @copydoc
+ Compat::fixRecurrence()
+ */
+ virtual void fixRecurrence( const Incidence::Ptr &incidence );
+ /**
+ @copydoc
+ Compat::fixEmptySummary()
+ */
+ virtual void fixEmptySummary( const Incidence::Ptr &incidence );
+ /**
+ @copydoc
+ Compat::fixAlarms()
+ */
+ virtual void fixAlarms( const Incidence::Ptr &incidence );
+ /**
+ @copydoc
+ Compat::fixFloatingEnd()
+ */
+ virtual void fixFloatingEnd( QDate &date );
+ /**
+ @copydoc
+ Compat::fixPriority()
+ */
+ virtual int fixPriority( int priority );
+ /**
+ @copydoc
+ Compat::useTimeZoneShift()
+ */
+ virtual bool useTimeZoneShift();
+ /**
+ @copydoc
+ Compat::setCreatedToDtStamp()
+ */
+ virtual void setCreatedToDtStamp( const Incidence::Ptr &incidence, const KDateTime &dtstamp );
+
+ private:
+ //@cond PRIVATE
+ Q_DISABLE_COPY( CompatDecorator )
+ class Private;
+ Private *d;
+ //@endcond
+};
+
+/**
+ @brief
Compatibility class for KOrganizer pre-3.5 calendar files.
Before kde 3.5, the start date was not automatically a recurring date.
@@ -280,6 +338,27 @@ class CompatOutlook9 : public Compat
//@endcond
};
+/**
+ @brief
+ Compatibility class for Kontact < 4.10 calendar files.
+*/
+class CompatPre410 : public CompatDecorator
+{
+ public:
+ CompatPre410( Compat* decoratedCompat );
+ /**
+ @copydoc
+ Compat::exchangeCreatedDTStamp()
+ */
+ virtual void setCreatedToDtStamp( const Incidence::Ptr &incidence, const KDateTime &dtstamp );
+
+ private:
+ //@cond PRIVATE
+ class Private;
+ Private *d;
+ //@endcond
+};
+
}
#endif
diff --git a/kcalcore/icalformat_p.cpp b/kcalcore/icalformat_p.cpp
index dc864cc..c1c1088 100644
--- a/kcalcore/icalformat_p.cpp
+++ b/kcalcore/icalformat_p.cpp
@@ -55,6 +55,7 @@ using namespace KCalCore;
static const char APP_NAME_FOR_XPROPERTIES[] = "KCALCORE";
static const char ENABLED_ALARM_XPROPERTY[] = "ENABLED";
+static const char IMPLEMENTATION_VERSION_XPROPERTY[] = "X-KDE-ICAL-IMPLEMENTATION-VERSION";
/* Static helpers */
/*
@@ -411,10 +412,10 @@ void ICalFormatImpl::writeIncidence( icalcomponent *parent,
d->writeIncidenceBase( parent, incidence.staticCast<IncidenceBase>() );
- // creation time of ical object
+ // creation date in storage
icalcomponent_add_property(
parent, writeICalDateTimeProperty(
- ICAL_DTSTAMP_PROPERTY, incidence->created() ) );
+ ICAL_CREATED_PROPERTY, incidence->created() ) );
// unique id
// If the scheduling ID is different from the real UID, the real
@@ -619,11 +620,6 @@ void ICalFormatImpl::writeIncidence( icalcomponent *parent,
void ICalFormatImpl::Private::writeIncidenceBase( icalcomponent *parent,
IncidenceBase::Ptr incidenceBase )
{
- // creation date in storage
- icalcomponent_add_property(
- parent, writeICalDateTimeProperty(
- ICAL_CREATED_PROPERTY, KDateTime::currentUtcDateTime() ) );
-
// organizer stuff
if ( !incidenceBase->organizer()->isEmpty() ) {
icalproperty *p = mImpl->writeOrganizer( incidenceBase->organizer() );
@@ -632,6 +628,9 @@ void ICalFormatImpl::Private::writeIncidenceBase( icalcomponent *parent,
}
}
+ icalcomponent_add_property(
+ parent, icalproperty_new_dtstamp( writeICalUtcDateTime( incidenceBase->lastModified() ) ) );
+
// attendees
if ( incidenceBase->attendeeCount() > 0 ) {
Attendee::List::ConstIterator it;
@@ -1543,16 +1542,21 @@ void ICalFormatImpl::readIncidence( icalcomponent *parent,
int intvalue, inttext;
icaldurationtype icalduration;
KDateTime kdt;
+ KDateTime dtstamp;
QStringList categories;
while ( p ) {
icalproperty_kind kind = icalproperty_isa( p );
switch ( kind ) {
- case ICAL_DTSTAMP_PROPERTY:
+ case ICAL_CREATED_PROPERTY:
incidence->setCreated( readICalDateTimeProperty( p, tzlist ) );
break;
+ case ICAL_DTSTAMP_PROPERTY:
+ dtstamp = readICalDateTimeProperty( p, tzlist );
+ break;
+
case ICAL_SEQUENCE_PROPERTY: // sequence
intvalue = icalproperty_get_sequence( p );
incidence->setRevision( intvalue );
@@ -1785,9 +1789,11 @@ void ICalFormatImpl::readIncidence( icalcomponent *parent,
alarm = icalcomponent_get_next_component( parent, ICAL_VALARM_COMPONENT ) ) {
readAlarm( alarm, incidence, tzlist );
}
- // Fix incorrect alarm settings by other applications (like outloook 9)
+
if ( d->mCompat ) {
+ // Fix incorrect alarm settings by other applications (like outloook 9)
d->mCompat->fixAlarms( incidence );
+ d->mCompat->setCreatedToDtStamp( incidence, dtstamp );
}
}
@@ -2385,6 +2391,10 @@ KDateTime ICalFormatImpl::readICalDateTimeProperty( icalproperty *p,
icaldatetimeperiodtype tp;
icalproperty_kind kind = icalproperty_isa( p );
switch ( kind ) {
+ case ICAL_CREATED_PROPERTY: // UTC date/time
+ tp.time = icalproperty_get_created( p );
+ utc = true;
+ break;
case ICAL_DTSTAMP_PROPERTY: // UTC date/time
tp.time = icalproperty_get_dtstamp( p );
utc = true;
@@ -2524,6 +2534,11 @@ icalcomponent *ICalFormatImpl::createCalendarComponent( const Calendar::Ptr &cal
p = icalproperty_new_version( const_cast<char *>(_ICAL_VERSION) );
icalcomponent_add_property( calendar, p );
+ // Implementation Version
+ p = icalproperty_new_x( _ICAL_IMPLEMENTATION_VERSION );
+ icalproperty_set_x_name( p, IMPLEMENTATION_VERSION_XPROPERTY );
+ icalcomponent_add_property( calendar, p );
+
// Add time zone
// NOTE: Commented out since relevant timezones are added by the caller.
// Previously we got some timezones listed twice in the ical file.
@@ -2573,6 +2588,26 @@ bool ICalFormatImpl::populate( const Calendar::Ptr &cal, icalcomponent *calendar
icalproperty *p;
+ p = icalcomponent_get_first_property( calendar, ICAL_X_PROPERTY );
+ QString implementationVersion;
+
+ while ( p ) {
+ const char *name = icalproperty_get_x_name( p );
+ QByteArray nproperty( name );
+ if ( nproperty == QByteArray( IMPLEMENTATION_VERSION_XPROPERTY ) ) {
+ QString nvalue = QString::fromUtf8( icalproperty_get_x( p ) );
+ if ( nvalue.isEmpty() ) {
+ icalvalue *value = icalproperty_get_value( p );
+ if ( icalvalue_isa( value ) == ICAL_TEXT_VALUE ) {
+ nvalue = QString::fromUtf8( icalvalue_get_text( value ) );
+ }
+ }
+ implementationVersion = nvalue;
+ icalcomponent_remove_property( calendar, p );
+ }
+ p = icalcomponent_get_next_property( calendar, ICAL_X_PROPERTY );
+ }
+
p = icalcomponent_get_first_property( calendar, ICAL_PRODID_PROPERTY );
if ( !p ) {
qDebug() << "No PRODID property found";
@@ -2581,7 +2616,7 @@ bool ICalFormatImpl::populate( const Calendar::Ptr &cal, icalcomponent *calendar
d->mLoadedProductId = QString::fromUtf8( icalproperty_get_prodid( p ) );
delete d->mCompat;
- d->mCompat = CompatFactory::createCompat( d->mLoadedProductId );
+ d->mCompat = CompatFactory::createCompat( d->mLoadedProductId, implementationVersion );
}
p = icalcomponent_get_first_property( calendar, ICAL_VERSION_PROPERTY );
@@ -2862,6 +2897,13 @@ icalcomponent *ICalFormatImpl::createScheduleComponent( const IncidenceBase::Ptr
icalcomponent_add_property( message, icalproperty_new_method( icalmethod ) );
icalcomponent *inc = writeIncidence( incidence, method );
+
+ if ( method != KCalCore::iTIPNoMethod ) {
+ //Not very nice, but since dtstamp changes semantics if used in scheduling, we have to adapt
+ icalcomponent_set_dtstamp(
+ inc, writeICalUtcDateTime( KDateTime::currentUtcDateTime() ) );
+ }
+
/*
* RFC 2446 states in section 3.4.3 ( REPLY to a VTODO ), that
* a REQUEST-STATUS property has to be present. For the other two, event and
diff --git a/kcalcore/icalformat_p.h b/kcalcore/icalformat_p.h
index 5278615..4d26950 100644
--- a/kcalcore/icalformat_p.h
+++ b/kcalcore/icalformat_p.h
@@ -70,6 +70,12 @@ class Todo;
#define _ICAL_VERSION "2.0"
/**
+ Version of this library implementation
+ @internal
+*/
+#define _ICAL_IMPLEMENTATION_VERSION "1.0"
+
+/**
@brief
This class provides the libical dependent functions for ICalFormat.
diff --git a/kcalcore/tests/CMakeLists.txt b/kcalcore/tests/CMakeLists.txt
index 10f449d..40911dd 100644
--- a/kcalcore/tests/CMakeLists.txt
+++ b/kcalcore/tests/CMakeLists.txt
@@ -42,6 +42,7 @@ KCALCORE_UNIT_TESTS(
testsortablelist
testtodo
testtimesininterval
+ testcreateddatecompat
)
# this test cannot work with msvc because libical should not be altered
# and therefore we can't add KCALCORE_EXPORT there
diff --git a/kcalcore/tests/testcreateddatecompat.cpp b/kcalcore/tests/testcreateddatecompat.cpp
new file mode 100644
index 0000000..778c517
--- /dev/null
+++ b/kcalcore/tests/testcreateddatecompat.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2012 Christian Mollekopf <mollekopf@kolabsys.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "testcreateddatecompat.h"
+#include "icalformat.h"
+#include "memorycalendar.h"
+#include <iostream>
+
+#include <qtest_kde.h>
+//"X-KDE-ICAL-IMPLEMENTATION-VERSION:1.0\n"
+
+const char *icalFile32 =
+"BEGIN:VCALENDAR\n"
+"PRODID:-//K Desktop Environment//NONSGML libkcal 3.2//EN\n"
+"VERSION:2.0\n"
+"BEGIN:VEVENT\n"
+"DTSTAMP:20031213T204753Z\n"
+"ORGANIZER:MAILTO:nobody@nowhere\n"
+"CREATED:20031213T204152Z\n"
+"UID:uid\n"
+"SEQUENCE:0\n"
+"LAST-MODIFIED:20031213T204152Z\n"
+"SUMMARY:Holladiho\n"
+"DTSTART:20031213T071500Z\n"
+"END:VEVENT\n"
+"END:VCALENDAR\n";
+
+const char *icalFile33 =
+"BEGIN:VCALENDAR\n"
+"PRODID:-//K Desktop Environment//NONSGML libkcal 3.2//EN\n"
+"VERSION:2.0\n"
+"X-KDE-ICAL-IMPLEMENTATION-VERSION:1.0\n"
+"BEGIN:VEVENT\n"
+"DTSTAMP:20031213T204753Z\n"
+"ORGANIZER:MAILTO:nobody@nowhere\n"
+"CREATED:20031213T204152Z\n"
+"UID:uid\n"
+"SEQUENCE:0\n"
+"LAST-MODIFIED:20031213T204152Z\n"
+"SUMMARY:Holladiho\n"
+"DTSTART:20031213T071500Z\n"
+"END:VEVENT\n"
+"END:VCALENDAR\n";
+
+void CreatedDateCompatTest::testCompat32()
+{
+ KCalCore::MemoryCalendar::Ptr cal(new KCalCore::MemoryCalendar(KDateTime::UTC));
+ KCalCore::ICalFormat format;
+ format.fromRawString(cal, QByteArray(icalFile32));
+ KCalCore::Event::Ptr event = cal->event("uid");
+ QVERIFY(event);
+ QCOMPARE(event->created(), KDateTime(QDate(2003,12,13), QTime(20,47,53), KDateTime::UTC));
+}
+
+void CreatedDateCompatTest::testCompat33()
+{
+ KCalCore::MemoryCalendar::Ptr cal(new KCalCore::MemoryCalendar(KDateTime::UTC));
+ KCalCore::ICalFormat format;
+ format.fromRawString(cal, QByteArray(icalFile33));
+ KCalCore::Event::Ptr event = cal->event("uid");
+ QVERIFY(event);
+ QCOMPARE(event->created(), KDateTime(QDate(2003,12,13), QTime(20,41,52), KDateTime::UTC));
+ QVERIFY(!event->customProperties().contains("X-KDE-ICAL-IMPLEMENTATION-VERSION"));
+}
+
+QTEST_KDEMAIN( CreatedDateCompatTest, NoGUI )
diff --git a/kcalcore/tests/testcreateddatecompat.h b/kcalcore/tests/testcreateddatecompat.h
new file mode 100644
index 0000000..acbd5ae
--- /dev/null
+++ b/kcalcore/tests/testcreateddatecompat.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012 Christian Mollekopf <mollekopf@kolabsys.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TESTCREATEDDATECOMPAT_H
+#define TESTCREATEDDATECOMPAT_H
+
+class testcreateddatecompat
+{
+};
+#include <QtCore/QObject>
+
+class CreatedDateCompatTest : public QObject
+{
+ Q_OBJECT
+ private Q_SLOTS:
+ void testCompat32();
+ void testCompat33();
+};
+
+#endif // TESTCREATEDDATECOMPAT_H