ActiveSync: map multiple VEVENTs with the same UID into the same ActiveSync item

The new ActiveSyncCalendarSource wraps the generic
ActiveSyncSource. It takes single VEVENTs per item and maps them to
ActiveSync items which combine all VEVENTs sharing the same UID.

The code is a combination of MapSyncSource and CalDAVSyncSourc. It
would have been nice to do this without duplicating code, but that
would have implied some pretty heavy core refactoring in code which is
currently in code freeze for SyncEvolution 1.2.

The new backend passes for the same tests as before. It fails for
those cases where ActiveSyncCalendarSource needs to do a partial
update of an ActiveSync item that was not modified on the server. In
that case, the item data is never sent to the client and with the
current activesyncd API it cannot retrieve the data on demand either.

ActiveSyncSource::readItem() needs to be extended. Right now it fails
with "internal error: item data for <eas ID> not available".

activesyncd commit ID:
61285b15a16a52c3c5ad1767047d0ca0ba1f32e8
This commit is contained in:
Patrick Ohly 2011-07-26 11:07:48 +02:00
parent 24665a97c9
commit f0b22a0750
5 changed files with 819 additions and 35 deletions

View File

@ -0,0 +1,591 @@
/*
* Copyright (C) 2010,2011 Intel Corporation
*
* This library 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 2.1 of the License, or (at your option) version 3.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include "config.h"
#ifdef ENABLE_ACTIVESYNC
// include first, it sets HANDLE_LIBICAL_MEMORY for us
#include <syncevo/icalstrdup.h>
#include "ActiveSyncCalendarSource.h"
#include <syncevo/GLibSupport.h>
#include <syncevo/declarations.h>
SE_BEGIN_CXX
ActiveSyncCalendarSource::ActiveSyncCalendarSource(const SyncSourceParams &params, EasItemType type) :
ActiveSyncCalFormatSource(params, type)
{
}
void ActiveSyncCalendarSource::beginSync(const std::string &lastToken, const std::string &resumeToken)
{
// claim item node for our change tracking
m_trackingNode.swap(m_itemNode);
// incremental sync (non-empty token) or start from scratch
setStartSyncKey(lastToken);
if (lastToken.empty()) {
// slow sync: wipe out cached list of IDs, will be filled anew below
SE_LOG_DEBUG(this, NULL, "starting slow sync");
m_trackingNode->clear();
} else {
// re-populate cache from storage, without any item data
ConfigProps props;
m_trackingNode->readProperties(props);
BOOST_FOREACH(const StringPair &prop, props) {
const std::string &easid = prop.first;
const std::string &value = prop.second;
size_t pos = value.find('/');
bool okay = false;
if (pos == 0) {
pos = value.find('/', 1);
if (pos != value.npos) {
std::string revision = m_escape.unescape(value.substr(1, pos - 1));
size_t nextpos = value.find('/', pos + 1);
if (nextpos != value.npos) {
std::string uid = m_escape.unescape(value.substr(pos + 1, nextpos - pos - 1));
boost::shared_ptr<Event> &eventptr = m_cache[easid];
if (!eventptr) {
eventptr = boost::shared_ptr<Event>(new Event);
}
eventptr->m_easid = easid;
eventptr->m_uid = uid;
pos = nextpos;
while ((nextpos = value.find('/', pos + 1)) != value.npos) {
std::string subid = m_escape.unescape(value.substr(pos + 1, nextpos - pos - 1));
eventptr->m_subids.insert(subid);
pos = nextpos;
}
okay = true;
}
}
}
if (!okay) {
SE_LOG_DEBUG(this, NULL, "unsupported or corrupt revision entry: %s = %s",
easid.c_str(),
value.c_str());
}
}
}
GErrorCXX gerror;
EASItemsCXX created, updated;
EASIdsCXX deleted;
gchar *buffer;
if (!eas_sync_handler_get_items(getHandler(),
getStartSyncKey().c_str(),
&buffer,
getEasType(),
getFolder().c_str(),
created, updated, deleted,
0,
gerror)) {
gerror.throwError("reading ActiveSync changes");
}
// TODO: Test that we really get an empty token here for an unexpected slow
// sync. If not, we'll start an incremental sync here and later the engine
// will ask us for older, unmodified item content which we won't have.
// populate ID lists and content cache
BOOST_FOREACH(EasItemInfo *item, created) {
string easid(item->server_id);
SE_LOG_DEBUG(this, NULL, "new eas item %s", easid.c_str());
Event &event = setItemData(easid, item->data);
BOOST_FOREACH(const std::string &subid, event.m_subids) {
SE_LOG_DEBUG(this, NULL, "new eas item %s = uid %s + rid %s",
easid.c_str(), event.m_uid.c_str(), subid.c_str());
addItem(createLUID(easid, subid), NEW);
}
}
BOOST_FOREACH(EasItemInfo *item, updated) {
string easid(item->server_id);
SE_LOG_DEBUG(this, NULL, "updated eas item %s", easid.c_str());
Event &event = setItemData(easid, item->data);
BOOST_FOREACH(const std::string &subid, event.m_subids) {
SE_LOG_DEBUG(this, NULL, "deleted eas item %s = uid %s + rid %s",
easid.c_str(), event.m_uid.c_str(), subid.c_str());
addItem(createLUID(easid, subid), UPDATED);
}
}
BOOST_FOREACH(const char *serverID, deleted) {
string easid(serverID);
Event &event = findItem(easid);
if (event.m_subids.empty()) {
SE_LOG_DEBUG(this, NULL, "deleted eas item %s empty?!", easid.c_str());
} else {
BOOST_FOREACH(const std::string &subid, event.m_subids) {
SE_LOG_DEBUG(this, NULL, "deleted eas item %s = uid %s + rid %s",
easid.c_str(), event.m_uid.c_str(), subid.c_str());
addItem(createLUID(easid, subid), DELETED);
}
}
m_cache.erase(easid);
}
// now also generate full list of all current items:
// old items + new (added to m_events above) - deleted (removed above)
BOOST_FOREACH(const EventCache::value_type &entry, m_cache) {
const std::string &easid = entry.first;
const boost::shared_ptr<Event> &eventptr = entry.second;
BOOST_FOREACH(const std::string &subid, eventptr->m_subids) {
SE_LOG_DEBUG(this, NULL, "existing eas item %s = uid %s + rid %s",
easid.c_str(), eventptr->m_uid.c_str(), subid.c_str());
addItem(createLUID(easid, subid), ANY);
}
}
// update key
setCurrentSyncKey(buffer);
}
std::string ActiveSyncCalendarSource::endSync(bool success)
{
m_trackingNode->clear();
if (success) {
BOOST_FOREACH(const EventCache::value_type &entry, m_cache) {
const std::string &easid = entry.first;
const boost::shared_ptr<Event> &eventptr = entry.second;
std::stringstream buffer;
buffer << "//"; // use same format as in MapSyncSource, just in case - was '/' << m_escape.escape(ids.m_revision) << '/';
buffer << m_escape.escape(eventptr->m_uid) << '/';
BOOST_FOREACH(const std::string &subid, eventptr->m_subids) {
buffer << m_escape.escape(subid) << '/';
}
m_trackingNode->setProperty(easid, buffer.str());
}
} else {
setCurrentSyncKey("");
}
// flush both nodes, just in case; in practice, the properties
// end up in the same file and only get flushed once
m_trackingNode->flush();
return getCurrentSyncKey();
}
std::string ActiveSyncCalendarSource::getDescription(const string &luid)
{
StringPair ids = MapSyncSource::splitLUID(luid);
const std::string &easid = ids.first;
const std::string &subid = ids.second;
Event &event = findItem(easid);
if (!event.m_calendar) {
// Don't load (expensive!) only to provide the description.
// Returning an empty string will trigger the fallback (logging the ID).
return "";
}
for (icalcomponent *comp = icalcomponent_get_first_component(event.m_calendar, ICAL_VEVENT_COMPONENT);
comp;
comp = icalcomponent_get_next_component(event.m_calendar, ICAL_VEVENT_COMPONENT)) {
if (Event::getSubID(comp) == subid) {
std::string descr;
const char *summary = icalcomponent_get_summary(comp);
if (summary && summary[0]) {
descr += summary;
}
if (true /* is event */) {
const char *location = icalcomponent_get_location(comp);
if (location && location[0]) {
if (!descr.empty()) {
descr += ", ";
}
descr += location;
}
}
// TODO: other item types
return descr;
}
}
return "";
}
ActiveSyncCalendarSource::Event &ActiveSyncCalendarSource::findItem(const std::string &easid)
{
EventCache::iterator it = m_cache.find(easid);
if (it == m_cache.end()) {
throwError("event not found");
}
return *it->second;
}
ActiveSyncCalendarSource::Event &ActiveSyncCalendarSource::loadItem(const std::string &easid)
{
Event &event = findItem(easid);
return loadItem(event);
}
ActiveSyncCalendarSource::Event &ActiveSyncCalendarSource::loadItem(Event &event)
{
if (!event.m_calendar) {
std::string item;
ActiveSyncSource::readItem(event.m_easid, item);
event.m_calendar.set(icalcomponent_new_from_string((char *)item.c_str()), // hack for old libical
"parsing iCalendar 2.0");
}
return event;
}
ActiveSyncCalendarSource::Event &ActiveSyncCalendarSource::setItemData(const std::string &easid, const std::string &data)
{
boost::shared_ptr<Event> &eventptr = m_cache[easid];
if (eventptr) {
eventptr->m_uid.clear();
eventptr->m_subids.clear();
} else {
eventptr = boost::shared_ptr<Event>(new Event);
}
Event &event = *eventptr;
event.m_easid = easid;
event.m_calendar.set(icalcomponent_new_from_string((char *)data.c_str()), // hack for old libical
"parsing iCalendar 2.0");
for (icalcomponent *comp = icalcomponent_get_first_component(event.m_calendar, ICAL_VEVENT_COMPONENT);
comp;
comp = icalcomponent_get_next_component(event.m_calendar, ICAL_VEVENT_COMPONENT)) {
if (event.m_uid.empty()) {
event.m_uid = Event::getUID(comp);
}
std::string subid = Event::getSubID(comp);
event.m_subids.insert(subid);
}
return event;
}
std::string ActiveSyncCalendarSource::Event::icalTime2Str(const icaltimetype &tt)
{
static const struct icaltimetype null = { 0 };
if (!memcmp(&tt, &null, sizeof(null))) {
return "";
} else {
eptr<char> timestr(ical_strdup(icaltime_as_ical_string(tt)));
if (!timestr) {
SE_THROW("cannot convert to time string");
}
return timestr.get();
}
}
std::string ActiveSyncCalendarSource::Event::getSubID(icalcomponent *comp)
{
struct icaltimetype rid = icalcomponent_get_recurrenceid(comp);
return icalTime2Str(rid);
}
std::string ActiveSyncCalendarSource::Event::getUID(icalcomponent *comp)
{
std::string uid;
icalproperty *prop = icalcomponent_get_first_property(comp, ICAL_UID_PROPERTY);
if (prop) {
uid = icalproperty_get_uid(prop);
}
return uid;
}
void ActiveSyncCalendarSource::Event::setUID(icalcomponent *comp, const std::string &uid)
{
icalproperty *prop = icalcomponent_get_first_property(comp, ICAL_UID_PROPERTY);
if (prop) {
icalproperty_set_uid(prop, uid.c_str());
} else {
icalcomponent_add_property(comp, icalproperty_new_uid(uid.c_str()));
}
}
ActiveSyncCalendarSource::EventCache::iterator ActiveSyncCalendarSource::EventCache::findByUID(const std::string &uid)
{
for (iterator it = begin();
it != end();
++it) {
if (it->second->m_uid == uid) {
return it;
}
}
return end();
}
SyncSourceRaw::InsertItemResult ActiveSyncCalendarSource::insertItem(const std::string &luid, const std::string &item)
{
StringPair ids = splitLUID(luid);
const std::string &callerEasID = ids.first;
std::string easid = ids.first;
const std::string &callerSubID = ids.second;
// parse new event
boost::shared_ptr<Event> newEvent(new Event);
newEvent->m_calendar.set(icalcomponent_new_from_string((char *)item.c_str()), // hack for old libical
"parsing iCalendar 2.0");
icalcomponent *firstcomp = NULL;
for (icalcomponent *comp = firstcomp = icalcomponent_get_first_component(newEvent->m_calendar, ICAL_VEVENT_COMPONENT);
comp;
comp = icalcomponent_get_next_component(newEvent->m_calendar, ICAL_VEVENT_COMPONENT)) {
std::string subid = Event::getSubID(comp);
EventCache::iterator it;
if (!luid.empty() &&
(it = m_cache.find(luid)) != m_cache.end()) {
// Additional sanity check: ensure that the expected UID is set.
// Necessary if the peer we synchronize with (aka the local
// data storage) doesn't support foreign UIDs. Maemo 5 calendar
// backend is one example.
Event::setUID(comp, it->second->m_uid);
newEvent->m_uid = it->second->m_uid;
} else {
newEvent->m_uid = Event::getUID(comp);
if (newEvent->m_uid.empty()) {
// create new UID
newEvent->m_uid = UUID();
Event::setUID(comp, newEvent->m_uid);
}
}
newEvent->m_subids.insert(subid);
}
if (newEvent->m_subids.size() != 1) {
SE_THROW("new CalDAV item did not contain exactly one VEVENT");
}
std::string subid = *newEvent->m_subids.begin();
// Determine whether we already know the merged item even though
// our caller didn't.
std::string knownSubID = callerSubID;
if (easid.empty()) {
EventCache::iterator it = m_cache.findByUID(newEvent->m_uid);
if (it != m_cache.end()) {
easid = it->first;
knownSubID = subid;
}
}
bool merged = false;
if (easid.empty()) {
// New VEVENT; should not be part of an existing merged item
// ("meeting series").
InsertItemResult res = ActiveSyncSource::insertItem("", item);
easid = res.m_luid;
EventCache::iterator it = m_cache.find(res.m_luid);
if (it != m_cache.end()) {
// merge into existing Event
Event &event = loadItem(*it->second);
if (event.m_subids.find(subid) != event.m_subids.end()) {
// was already in that item but caller didn't seem to know
merged = true;
} else {
// add to merged item
event.m_subids.insert(subid);
}
icalcomponent_merge_component(event.m_calendar,
newEvent->m_calendar.release()); // function destroys merged calendar
} else {
// add to cache without further changes
newEvent->m_easid = res.m_luid;
m_cache[newEvent->m_easid] = newEvent;
}
} else {
if (subid != knownSubID) {
SE_THROW(StringPrintf("update for eas item %s rid %s has wrong rid %s",
easid.c_str(),
knownSubID.c_str(),
subid.c_str()));
}
Event &event = findItem(easid);
if (event.m_subids.size() == 1 &&
*event.m_subids.begin() == subid) {
// special case: no need to load old data, replace it outright
event.m_calendar = newEvent->m_calendar;
} else {
// update cache: find old VEVENT and remove it before adding new one
icalcomponent *removeme = NULL;
for (icalcomponent *comp = icalcomponent_get_first_component(event.m_calendar, ICAL_VEVENT_COMPONENT);
comp;
comp = icalcomponent_get_next_component(event.m_calendar, ICAL_VEVENT_COMPONENT)) {
if (Event::getSubID(comp) == subid) {
removeme = comp;
break;
}
}
if (easid != callerEasID) {
// caller didn't know final UID: if found, then tell him that
// we merged the item for him, if not, then don't complain about
// it not being found (like we do when the item should exist
// but doesn't)
if (removeme) {
merged = true;
icalcomponent_remove_component(event.m_calendar, removeme);
} else {
event.m_subids.insert(subid);
}
} else {
if (removeme) {
// this is what we expect when the caller mentions the ActiveSync ID
icalcomponent_remove_component(event.m_calendar, removeme);
} else {
// caller confused?!
SE_THROW("event not found");
}
}
icalcomponent_merge_component(event.m_calendar,
newEvent->m_calendar.release()); // function destroys merged calendar
}
eptr<char> icalstr(ical_strdup(icalcomponent_as_ical_string(event.m_calendar)));
std::string data = icalstr.get();
// TODO: avoid updating item on server immediately?
InsertItemResult res = ActiveSyncSource::insertItem(event.m_easid, data);
if (res.m_merged ||
res.m_luid != event.m_easid) {
// should not merge with anything, if so, our cache was invalid
SE_THROW("CalDAV item not updated as expected");
}
}
return SyncSourceRaw::InsertItemResult(createLUID(easid, subid),
"", merged);
}
void ActiveSyncCalendarSource::readItem(const std::string &luid, std::string &item)
{
StringPair ids = splitLUID(luid);
const std::string &easid = ids.first;
const std::string &subid = ids.second;
Event &event = loadItem(easid);
if (event.m_subids.size() == 1) {
// simple case: convert existing VCALENDAR
if (*event.m_subids.begin() == subid) {
eptr<char> icalstr(ical_strdup(icalcomponent_as_ical_string(event.m_calendar)));
item = icalstr.get();
} else {
SE_THROW("event not found");
}
} else {
// complex case: create VCALENDAR with just the VTIMEZONE definition(s)
// and the one event, then convert that
eptr<icalcomponent> calendar(icalcomponent_new(ICAL_VCALENDAR_COMPONENT), "VCALENDAR");
for (icalcomponent *tz = icalcomponent_get_first_component(event.m_calendar, ICAL_VTIMEZONE_COMPONENT);
tz;
tz = icalcomponent_get_next_component(event.m_calendar, ICAL_VTIMEZONE_COMPONENT)) {
eptr<icalcomponent> clone(icalcomponent_new_clone(tz), "VTIMEZONE");
icalcomponent_add_component(calendar, clone.release());
}
bool found = false;
for (icalcomponent *comp = icalcomponent_get_first_component(event.m_calendar, ICAL_VEVENT_COMPONENT);
comp;
comp = icalcomponent_get_next_component(event.m_calendar, ICAL_VEVENT_COMPONENT)) {
if (Event::getSubID(comp) == subid) {
eptr<icalcomponent> clone(icalcomponent_new_clone(comp), "VEVENT");
icalcomponent_add_component(calendar, clone.release());
found = true;
break;
}
}
if (!found) {
SE_THROW("event not found");
}
eptr<char> icalstr(ical_strdup(icalcomponent_as_ical_string(calendar)));
item = icalstr.get();
}
}
void ActiveSyncCalendarSource::deleteItem(const string &luid)
{
StringPair ids = splitLUID(luid);
const std::string &easid = ids.first;
const std::string &subid = ids.second;
// find item in cache first, load only if it is not going to be
// removed entirely
Event &event = findItem(easid);
if (event.m_subids.size() == 1) {
// remove entire merged item, nothing will be left after removal
if (*event.m_subids.begin() != subid) {
SE_THROW("event not found");
} else {
event.m_subids.clear();
event.m_calendar = NULL;
ActiveSyncSource::deleteItem(ids.first);
}
m_cache.erase(easid);
} else {
loadItem(event);
bool found = false;
// bool parentRemoved = false;
for (icalcomponent *comp = icalcomponent_get_first_component(event.m_calendar, ICAL_VEVENT_COMPONENT);
comp;
comp = icalcomponent_get_next_component(event.m_calendar, ICAL_VEVENT_COMPONENT)) {
if (Event::getSubID(comp) == subid) {
icalcomponent_remove_component(event.m_calendar, comp);
icalcomponent_free(comp);
found = true;
// if (subid.empty()) {
// parentRemoved = true;
// }
}
}
if (!found) {
SE_THROW("event not found");
}
event.m_subids.erase(subid);
// TODO: avoid updating the item immediately
eptr<char> icalstr(ical_strdup(icalcomponent_as_ical_string(event.m_calendar)));
InsertItemResult res = ActiveSyncSource::insertItem(easid, icalstr.get());
if (res.m_merged ||
res.m_luid != easid) {
SE_THROW("unexpected result of removing sub event");
}
}
}
std::string ActiveSyncCalendarSource::createLUID(const std::string &uid, const std::string &subid)
{
std::string luid = m_escape.escape(uid);
if (!subid.empty()) {
luid += '/';
luid += m_escape.escape(subid);
}
return luid;
}
std::pair<std::string, std::string> ActiveSyncCalendarSource::splitLUID(const std::string &luid)
{
size_t index = luid.find('/');
if (index != luid.npos) {
return make_pair(m_escape.unescape(luid.substr(0, index)),
m_escape.unescape(luid.substr(index + 1)));
} else {
return make_pair(m_escape.unescape(luid), "");
}
}
StringEscape ActiveSyncCalendarSource::m_escape('%', "/");
SE_END_CXX
#endif // ENABLE_ACTIVESYNC

View File

@ -0,0 +1,146 @@
/*
* Copyright (C) 2010,2011 Intel Corporation
*
* This library 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 2.1 of the License, or (at your option) version 3.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#ifndef INCL_ACTIVESYNCCALENDARSOURCE
#define INCL_ACTIVESYNCCALENDARSOURCE
#include <config.h>
#ifdef ENABLE_ACTIVESYNC
#include "ActiveSyncSource.h"
#include <syncevo/MapSyncSource.h>
#include <syncevo/eds_abi_wrapper.h>
#include <syncevo/SmartPtr.h>
#include <boost/utility.hpp>
#include <boost/shared_ptr.hpp>
#include <syncevo/declarations.h>
SE_BEGIN_CXX
/**
* Similar to CalDAVSource (and partly copied from it): implements
* operations with one VEVENT per item in terms of operations
* on items which bundle all VEVENTs with the same UID in one item.
*
* It works by keeping all active items as VCALENDAR icalcomponent in
* memory and updating that when asked to add/update/remove VEVENTs.
* The update VCALENDAR items are then stored via the base class.
*
* Terminology:
* - easid = davLUID in CalDAVSource =
ActiveSync item ID used in base ActiveSyncSource and by ActiveSync peer,
* 1:1 mapping to uid
* - uid = mainid in MapSyncSource = the UID shared by multiple VEVENTs
* - rid = subid in MapSyncSource = RECURRENCE-ID, turned into a simple string by Event::getRid()
* - luid = easid/rid concatenated by createLUID() and split with splitLUID()
*/
class ActiveSyncCalendarSource : public ActiveSyncCalFormatSource
{
public:
ActiveSyncCalendarSource(const SyncSourceParams &params, EasItemType type);
// override operations in base class to work with luid == easid/rid instead
// of just easid
virtual std::string getDescription(const string &luid);
virtual void beginSync(const std::string &lastToken, const std::string &resumeToken);
virtual std::string endSync(bool success);
virtual void deleteItem(const string &luid);
virtual InsertItemResult insertItem(const std::string &luid, const std::string &item);
virtual void readItem(const std::string &luid, std::string &item);
private:
/** compose luid from mainid and subid */
static std::string createLUID(const std::string &uid, const std::string &rid);
/** split luid into uid (first) and rid (second) */
static StringPair splitLUID(const std::string &luid);
/** escape / in uid with %2F, so that splitMainIDValue() and splitLUID() can use / as separator */
static StringEscape m_escape;
/**
* Information about each merged item.
*/
class Event : boost::noncopyable {
public:
/** the ActiveSync ID */
std::string m_easid;
/** the iCalendar 2.0 UID */
std::string m_uid;
/**
* the list of simplified RECURRENCE-IDs (without time zone,
* see icalTime2Str()), empty string for VEVENT without
* RECURRENCE-ID
*/
std::set<std::string> m_subids;
/**
* parsed VCALENDAR component representing the current
* state of the item as it exists on the WebDAV server,
* must be kept up-to-date as we make changes, may be NULL
*/
eptr<icalcomponent> m_calendar;
/** date-time as string, without time zone */
static std::string icalTime2Str(const icaltimetype &tt);
/** RECURRENCE-ID, empty if none */
static std::string getSubID(icalcomponent *icomp);
/** UID, empty if none */
static std::string getUID(icalcomponent *icomp);
static void setUID(icalcomponent *icomp, const std::string &uid);
};
/**
* A cache of information about each merged item. Maps from
* easid to Event.
*/
class EventCache : public std::map<std::string, boost::shared_ptr<Event> >
{
public:
EventCache() : m_initialized(false) {}
bool m_initialized;
iterator findByUID(const std::string &uid);
} m_cache;
Event &findItem(const std::string &easid);
Event &loadItem(const std::string &easid);
Event &loadItem(Event &event);
/**
* create event (if necessary) and populate based on given iCalendar 2.0 VCALENDAR
*/
Event &setItemData(const std::string &easid, const std::string &data);
/**
* On-disk representation of m_cache (without the item data).
* Format same as in MapSyncSource (code copied, refactor!).
*/
boost::shared_ptr<ConfigNode> m_trackingNode;
};
SE_END_CXX
#endif // ENABLE_ACTIVESYNC
#endif // INCL_ACTIVESYNCCALENDARSOURCE

View File

@ -25,9 +25,6 @@
#ifdef ENABLE_ACTIVESYNC
#include "ActiveSyncSource.h"
#include <syncevo/GLibSupport.h>
#include <eas-item-info.h>
#include <stdlib.h>
#include <errno.h>
@ -35,6 +32,9 @@
#include <syncevo/declarations.h>
SE_BEGIN_CXX
void EASItemUnref(EasItemInfo *info) { g_object_unref(&info->parent_instance); }
void GStringUnref(char *str) { g_free(str); }
void ActiveSyncSource::enableServerMode()
{
SyncSourceAdmin::init(m_operations, this);
@ -72,21 +72,11 @@ void ActiveSyncSource::close()
m_handler.set(NULL);
}
void EASItemUnref(EasItemInfo *info) { g_object_unref(&info->parent_instance); }
/** non-copyable list of EasItemInfo pointers, owned by list */
typedef GListCXX<EasItemInfo, GSList, EASItemUnref> EASItemsCXX;
void GStringUnref(char *str) { g_free(str); }
/** non-copyable list of strings, owned by list */
typedef GListCXX<char, GSList, GStringUnref> EASIdsCXX;
/** non-copyable smart pointer to an EasItemInfo, unrefs when going out of scope */
typedef eptr<EasItemInfo, GObject> EASItemPtr;
void ActiveSyncSource::beginSync(const std::string &lastToken, const std::string &resumeToken)
{
// claim item node for ids
m_ids.swap(m_itemNode);
// incremental sync (non-empty token) or start from scratch
m_startSyncKey = lastToken;
if (lastToken.empty()) {
@ -184,8 +174,10 @@ void ActiveSyncSource::deleteItem(const string &luid)
}
// remove from item list
m_items.erase(luid);
m_ids->removeProperty(luid);
if (m_ids) {
m_items.erase(luid);
m_ids->removeProperty(luid);
}
// update key
m_currentSyncKey = buffer;
@ -247,8 +239,10 @@ SyncSourceSerialize::InsertItemResult ActiveSyncSource::insertItem(const std::st
}
// add/update in cache
m_items[res.m_luid] = data;
m_ids->setProperty(res.m_luid, "1");
if (m_ids) {
m_items[res.m_luid] = data;
m_ids->setProperty(res.m_luid, "1");
}
// update key
m_currentSyncKey = buffer;

View File

@ -1,6 +1,6 @@
/*
* Copyright (C) 2007-2009 Patrick Ohly <patrick.ohly@gmx.de>
* Copyright (C) 2011 Patrick Ohly <patrick.ohly@intel.com>
* Copyright (C) 2011 Intel Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -29,6 +29,7 @@
#include <syncevo/PrefixConfigNode.h>
#include <syncevo/SafeConfigNode.h>
#include <syncevo/SmartPtr.h>
#include <syncevo/GLibSupport.h>
#include <boost/bind.hpp>
@ -36,10 +37,12 @@
#include <map>
#include "libeassync.h"
#include <eas-item-info.h>
#include <syncevo/declarations.h>
SE_BEGIN_CXX
/**
* Synchronizes contacts, events, tasks and journals with an
* ActiveSync server. Sub-classes provide the necessary specialization
@ -53,8 +56,16 @@ SE_BEGIN_CXX
* src/syncevo/profiles/datatypes/01vcard-profile.xml for an extensive
* example how that works.
*
* Each item is a single calendar item, in other words, multiple
* VEVENTs with the same UID are treated as separate items.
* Each ActiveSync calendar item is a VCALENDAR which contains all
* VEVENTs with the same UID. The SyncEvolution/Synthesis engine works
* with individual VEVENTs per item. This implies that someone has to
* map between the two concepts. This is done in the derived
* ActiveSyncCalendarSource, similar to the existing MapSyncSource.
*
* Reusing that class was considered, but discarded because
* MapSyncSource assumes that the class that it wraps uses id/revision
* string pairs for change tracking, which is not the case for
* ActiveSync.
*
* A sync session is done like this:
* - The Synthesis sync anchor directly maps to the
@ -77,8 +88,22 @@ SE_BEGIN_CXX
* that any changes made by other ActiveSync server clients
* will be reported when asking for changes based on that updated
* key without including changes made by our own client, even
* when these changes happen concurrently.
* when these changes happen concurrently. This is true for
* Exchange 2010 + ActiveSync 12.1.
* TODO: write a test program to verify that assumption.
* Google + ActiveSync 12.0 do not support this.
* TODO: deal with server-side changes reported to us at the
* time when we make changes ourselves.
* - When multiple events are in one item, it can happen that
* the event series has to be retrieved individually from the
* server. Example:
* - nothing changed on server => nothing sent at start of sync,
* cache empty
* - ActiveSyncCalendarSource must delete a detached recurrence
* inside a series
* - retrieve series
* - remove recurrence
* - sent back the updated series
* - At the end of the sync, the updated ID list is stored and
* the updated sync key is returned to the Synthesis engine.
* - If anything goes wrong, a fatal error is returned to the
@ -105,13 +130,13 @@ class ActiveSyncSource :
public:
ActiveSyncSource(const SyncSourceParams &params) :
TestingSyncSource(params),
m_context(params.m_context),
m_account(0),
// Ensure that arbitrary keys can be stored (SafeConfigNode) and
// that we use a common prefix, so that we can use the key/value store
// also for other keys if the need ever arises).
m_ids(new PrefixConfigNode("item-",
boost::shared_ptr<ConfigNode>(new SafeConfigNode(params.m_nodes.getTrackingNode()))))
m_itemNode(new PrefixConfigNode("item-",
boost::shared_ptr<ConfigNode>(new SafeConfigNode(params.m_nodes.getTrackingNode())))),
m_context(params.m_context),
m_account(0)
{
if (!m_context) {
m_context.reset(new SyncConfig());
@ -122,7 +147,6 @@ class ActiveSyncSource :
SyncConfig &getSyncConfig() { return *m_context; }
protected:
/* partial implementation of SyncSource */
virtual void enableServerMode();
virtual bool serverModeEnabled() const;
@ -147,6 +171,16 @@ class ActiveSyncSource :
/** to be provided by derived class */
virtual EasItemType getEasType() const = 0;
protected:
EasSyncHandler *getHandler() { return m_handler.get(); }
std::string getFolder() { return m_folder; }
std::string getStartSyncKey() { return m_startSyncKey; }
void setStartSyncKey(const std::string &startSyncKey) { m_startSyncKey = startSyncKey; }
std::string getCurrentSyncKey() { return m_currentSyncKey; }
void setCurrentSyncKey(const std::string &currentSyncKey) { m_currentSyncKey = currentSyncKey; }
boost::shared_ptr<ConfigNode> m_itemNode;
private:
/** "source-config@<context>" instance which holds our username == ActiveSync account ID */
boost::shared_ptr<SyncConfig> m_context;
@ -166,10 +200,16 @@ class ActiveSyncSource :
/** current sync key, set when session starts and updated as changes are made */
std::string m_currentSyncKey;
/** server-side IDs of all items, updated as changes are reported and/or are made */
/**
* server-side IDs of all items, updated as changes are reported and/or are made;
* NULL if not using change tracking
*/
boost::shared_ptr<ConfigNode> m_ids;
/** cache of all items, filled at begin of session and updated as changes are made */
/**
* cache of all items, filled at begin of session and updated as
* changes are made (if doing change tracking)
*/
std::map<std::string, std::string> m_items;
};
@ -211,12 +251,12 @@ class ActiveSyncContactSource : public ActiveSyncSource
/**
* used for all iCalendar 2.0 items (events, todos, journals)
*/
class ActiveSyncCalendarSource : public ActiveSyncSource
class ActiveSyncCalFormatSource : public ActiveSyncSource
{
EasItemType m_type;
public:
ActiveSyncCalendarSource(const SyncSourceParams &params, EasItemType type) :
ActiveSyncCalFormatSource(const SyncSourceParams &params, EasItemType type) :
ActiveSyncSource(params),
m_type(type)
{}
@ -229,6 +269,18 @@ class ActiveSyncCalendarSource : public ActiveSyncSource
EasItemType getEasType() const { return m_type; }
};
void EASItemUnref(EasItemInfo *info);
/** non-copyable list of EasItemInfo pointers, owned by list */
typedef GListCXX<EasItemInfo, GSList, EASItemUnref> EASItemsCXX;
void GStringUnref(char *str);
/** non-copyable list of strings, owned by list */
typedef GListCXX<char, GSList, GStringUnref> EASIdsCXX;
/** non-copyable smart pointer to an EasItemInfo, unrefs when going out of scope */
typedef eptr<EasItemInfo, GObject> EASItemPtr;
SE_END_CXX

View File

@ -19,6 +19,7 @@
*/
#include "ActiveSyncSource.h"
#include "ActiveSyncCalendarSource.h"
#ifdef HAVE_CONFIG_H
# include "config.h"
@ -63,7 +64,7 @@ static SyncSource *createSource(const SyncSourceParams &params)
if (isMe) {
return
#ifdef ENABLE_ACTIVESYNC
new ActiveSyncCalendarSource(params, EAS_ITEM_TODO)
new ActiveSyncCalFormatSource(params, EAS_ITEM_TODO)
#else
RegisterSyncSource::InactiveSource
#endif
@ -74,7 +75,7 @@ static SyncSource *createSource(const SyncSourceParams &params)
if (isMe) {
return
#ifdef ENABLE_ACTIVESYNC
new ActiveSyncCalendarSource(params, EAS_ITEM_JOURNAL)
new ActiveSyncCalFormatSource(params, EAS_ITEM_JOURNAL)
#else
RegisterSyncSource::InactiveSource
#endif