From 60b63645b802ba009586555b99cd3f8b5d18c7a1 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Tue, 30 Nov 2010 17:45:40 +0100 Subject: [PATCH] CalDAV: added Google Calendar alarm hack Google Calendar adds a VALARM when a VEVENT is created for the first time on the server via CalDAV. This is not what we want, if the VEVENT had no VALARM, that's how it should be stored. As a workaround we detect this special case (new VEVENT without VALARM) and send the original data again. Having to resend with higher SEQUENCE and LAST-MODIFIED/DTSTAMP values makes this a bit complex and slow, because we need to get the actual data from the server (cannot guess what time stamps were assigned). This leads to an interesting question: if the clock on the local side is unsynchronized, its LAST-MODIFIED time stamps might end up being lower than the values on the server, which prevents sending local updates. There's currently no solution for this in the backend. --- src/backends/webdav/CalDAVSource.cpp | 75 +++++++++++++++++++++++++--- src/backends/webdav/CalDAVSource.h | 6 ++- src/backends/webdav/NeonCXX.h | 10 +++- src/backends/webdav/WebDAVSource.cpp | 18 +++++-- 4 files changed, 94 insertions(+), 15 deletions(-) diff --git a/src/backends/webdav/CalDAVSource.cpp b/src/backends/webdav/CalDAVSource.cpp index 761f5f83..42c90a2e 100644 --- a/src/backends/webdav/CalDAVSource.cpp +++ b/src/backends/webdav/CalDAVSource.cpp @@ -202,17 +202,20 @@ SubSyncSource::SubItemResult CalDAVSource::insertSubItem(const std::string &luid InsertItemResult res; // Yahoo expects resource names to match UID + ".ics". std::string name = newEvent->m_UID + ".ics"; + std::string buffer; + const std::string *data; if (!settings().googleChildHack() || subid.empty()) { // avoid re-encoding item data - res = insertItem(name, item, true); + data = &item; } else { // sanitize item first: when adding child event without parent, // then the RECURRENCE-ID confuses Google eptr icalstr(ical_strdup(icalcomponent_as_ical_string(newEvent->m_calendar))); - std::string data = icalstr.get(); - Event::escapeRecurrenceID(data); - res = insertItem(name, data, true); + buffer = icalstr.get(); + Event::escapeRecurrenceID(buffer); + data = &buffer; } + res = insertItem(name, *data, true); subres.m_uid = res.m_luid; subres.m_subid = subid; subres.m_revision = res.m_revision; @@ -232,10 +235,58 @@ SubSyncSource::SubItemResult CalDAVSource::insertSubItem(const std::string &luid icalcomponent_merge_component(event.m_calendar, newEvent->m_calendar.release()); // function destroys merged calendar } else { - // add to cache - newEvent->m_DAVluid = res.m_luid; - newEvent->m_etag = res.m_revision; - m_cache[newEvent->m_DAVluid] = newEvent; + // Google Calendar adds a default alarm each time a VEVENT is added + // anew. Avoid that by resending our data if necessary (= no alarm set). + if (settings().googleAlarmHack() && + !icalcomponent_get_first_component(firstcomp, ICAL_VALARM_COMPONENT)) { + // add to cache, then update it + newEvent->m_DAVluid = res.m_luid; + newEvent->m_etag = res.m_revision; + m_cache[newEvent->m_DAVluid] = newEvent; + + // potentially need to know sequence and mod time on server: + // keep pointer (clears pointer in newEvent), + // then get and parse new copy from server + eptr calendar = newEvent->m_calendar; + + if (settings().googleUpdateHack()) { + loadItem(*newEvent); + + // increment in original data + newEvent->m_sequence++; + newEvent->m_lastmodtime++; + Event::setSequence(firstcomp, newEvent->m_sequence); + icalproperty *lastmod = icalcomponent_get_first_property(firstcomp, ICAL_LASTMODIFIED_PROPERTY); + if (lastmod) { + lastmodtime = icaltime_from_timet(newEvent->m_lastmodtime, false); + icalproperty_set_lastmodified(lastmod, lastmodtime); + } + icalproperty *dtstamp = icalcomponent_get_first_property(firstcomp, ICAL_DTSTAMP_PROPERTY); + if (dtstamp) { + icalproperty_set_dtstamp(dtstamp, lastmodtime); + } + // re-encode below + data = &buffer; + } + bool mangleRecurrenceID = settings().googleChildHack() && !subid.empty(); + if (data == &buffer || mangleRecurrenceID) { + eptr icalstr(ical_strdup(icalcomponent_as_ical_string(calendar))); + buffer = icalstr.get(); + } + if (mangleRecurrenceID) { + Event::escapeRecurrenceID(buffer); + } + SE_LOG_DEBUG(NULL, NULL, "resending VEVENT to get rid of VALARM"); + res = insertItem(name, *data, true); + newEvent->m_etag = + subres.m_revision = res.m_revision; + newEvent->m_calendar = calendar; + } else { + // add to cache without further changes + newEvent->m_DAVluid = res.m_luid; + newEvent->m_etag = res.m_revision; + m_cache[newEvent->m_DAVluid] = newEvent; + } } } else { if (subid != knownSubID) { @@ -490,6 +541,14 @@ CalDAVSource::Event &CalDAVSource::loadItem(Event &event) if (sequence > event.m_sequence) { event.m_sequence = sequence; } + icalproperty *lastmod = icalcomponent_get_first_property(comp, ICAL_LASTMODIFIED_PROPERTY); + if (lastmod) { + icaltimetype lastmodtime = icalproperty_get_lastmodified(lastmod); + time_t mod = icaltime_as_timet(lastmodtime); + if (mod > event.m_lastmodtime) { + event.m_lastmodtime = mod; + } + } } } return event; diff --git a/src/backends/webdav/CalDAVSource.h b/src/backends/webdav/CalDAVSource.h index a7c4adb3..b7930eee 100644 --- a/src/backends/webdav/CalDAVSource.h +++ b/src/backends/webdav/CalDAVSource.h @@ -76,7 +76,8 @@ class CalDAVSource : public WebDAVSource, class Event : boost::noncopyable { public: Event() : - m_sequence(0) + m_sequence(0), + m_lastmodtime(0) {} /** the ID used by WebDAVSource */ @@ -91,6 +92,9 @@ class CalDAVSource : public WebDAVSource, /** maximum sequence number of any sub item */ long m_sequence; + /** maximum modification time of any sub item */ + time_t m_lastmodtime; + /** * the list of simplified RECURRENCE-IDs (without time zone, * see icalTime2Str()), empty string for VEVENT without diff --git a/src/backends/webdav/NeonCXX.h b/src/backends/webdav/NeonCXX.h index 2cf18c83..24238437 100644 --- a/src/backends/webdav/NeonCXX.h +++ b/src/backends/webdav/NeonCXX.h @@ -75,14 +75,20 @@ class Settings { * if true, then manipulate SEQUENCE and LAST-MODIFIED properties * so that Google CalDAV server accepts updates */ - virtual bool googleUpdateHack() = 0; + virtual bool googleUpdateHack() const = 0; /** * if true, then avoid RECURRENCE-ID in sub items without * corresponding parent by replacing it with * X-SYNCEVOLUTION-RECURRENCE-ID */ - virtual bool googleChildHack() = 0; + virtual bool googleChildHack() const = 0; + + /** + * if true, then check whether server has added an unwanted alarm + * and resend to get rid of it + */ + virtual bool googleAlarmHack() const = 0; /** * use this to create a boost_shared pointer for a diff --git a/src/backends/webdav/WebDAVSource.cpp b/src/backends/webdav/WebDAVSource.cpp index 9badfcca..d5ca1668 100644 --- a/src/backends/webdav/WebDAVSource.cpp +++ b/src/backends/webdav/WebDAVSource.cpp @@ -24,11 +24,14 @@ class ContextSettings : public Neon::Settings { std::string m_url; bool m_googleUpdateHack; bool m_googleChildHack; - + bool m_googleAlarmHack; public: ContextSettings(const boost::shared_ptr &context) : - m_context(context) + m_context(context), + m_googleUpdateHack(false), + m_googleChildHack(false), + m_googleAlarmHack(false) { if (m_context) { vector urls = m_context->getSyncURL(); @@ -55,6 +58,12 @@ public: m_googleUpdateHack = true; } else if (boost::iequals(*flag, "ChildHack")) { m_googleChildHack = true; + } else if (boost::iequals(*flag, "AlarmHack")) { + m_googleAlarmHack = true; + } else if (boost::iequals(*flag, "Google")) { + m_googleUpdateHack = + m_googleChildHack = + m_googleAlarmHack = true; } else { SE_THROW(StringPrintf("unknown SyncEvolution flag %s in URL %s", std::string(flag->begin(), flag->end()).c_str(), @@ -92,8 +101,9 @@ public: } } - virtual bool googleUpdateHack() { return m_googleUpdateHack; } - virtual bool googleChildHack() { return m_googleChildHack; } + virtual bool googleUpdateHack() const { return m_googleUpdateHack; } + virtual bool googleChildHack() const { return m_googleChildHack; } + virtual bool googleAlarmHack() const { return m_googleChildHack; } virtual void getCredentials(const std::string &realm, std::string &username,