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:
parent
24665a97c9
commit
f0b22a0750
|
@ -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 ¶ms, 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
|
|
@ -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 ¶ms, 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
|
|
@ -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;
|
||||
|
|
|
@ -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 ¶ms) :
|
||||
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 ¤tSyncKey) { 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 ¶ms, EasItemType type) :
|
||||
ActiveSyncCalFormatSource(const SyncSourceParams ¶ms, 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
|
||||
|
||||
|
|
|
@ -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 ¶ms)
|
|||
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 ¶ms)
|
|||
if (isMe) {
|
||||
return
|
||||
#ifdef ENABLE_ACTIVESYNC
|
||||
new ActiveSyncCalendarSource(params, EAS_ITEM_JOURNAL)
|
||||
new ActiveSyncCalFormatSource(params, EAS_ITEM_JOURNAL)
|
||||
#else
|
||||
RegisterSyncSource::InactiveSource
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue