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.
This commit is contained in:
Patrick Ohly 2010-11-30 17:45:40 +01:00
parent f4eab38b01
commit 60b63645b8
4 changed files with 94 additions and 15 deletions

View file

@ -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<char> 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<icalcomponent> 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<char> 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;

View file

@ -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

View file

@ -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

View file

@ -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<const SyncConfig> &context) :
m_context(context)
m_context(context),
m_googleUpdateHack(false),
m_googleChildHack(false),
m_googleAlarmHack(false)
{
if (m_context) {
vector<string> 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,