725 lines
28 KiB
C++
725 lines
28 KiB
C++
/*
|
|
* 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)
|
|
{
|
|
// erase content which might have been set in a previous call
|
|
reset();
|
|
|
|
// claim item node for our change tracking, if not done already
|
|
if (m_itemNode && !m_trackingNode) {
|
|
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(getDisplayName(), "sync key empty, starting slow sync");
|
|
m_trackingNode->clear();
|
|
} else {
|
|
SE_LOG_DEBUG(getDisplayName(), "sync key %s, starting incremental sync", lastToken.c_str());
|
|
|
|
// 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(getDisplayName(), "unsupported or corrupt revision entry: %s = %s",
|
|
easid.c_str(),
|
|
value.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
gboolean moreAvailable = TRUE;
|
|
|
|
setCurrentSyncKey(getStartSyncKey());
|
|
|
|
// same logic as in ActiveSyncSource::beginSync()
|
|
|
|
// TODO: use slow sync? bool slowSync = false;
|
|
for (bool firstIteration = true;
|
|
moreAvailable;
|
|
firstIteration = false) {
|
|
gchar *buffer = NULL;
|
|
GErrorCXX gerror;
|
|
EASItemsCXX created, updated;
|
|
EASIdsCXX deleted;
|
|
bool wasSlowSync = getCurrentSyncKey().empty();
|
|
|
|
if (!eas_sync_handler_get_items(getHandler(),
|
|
getCurrentSyncKey().c_str(),
|
|
&buffer,
|
|
getEasType(),
|
|
getFolder().c_str(),
|
|
created, updated, deleted,
|
|
&moreAvailable,
|
|
gerror)) {
|
|
if (gerror.m_gerror &&
|
|
/*
|
|
gerror.m_gerror->domain == EAS_TYPE_CONECTION_ERROR &&
|
|
gerror.m_gerror->code == EAS_CONNECTION_SYNC_ERROR_INVALIDSYNCKEY && */
|
|
gerror.m_gerror->message &&
|
|
!strstr(gerror.m_gerror->message, "Sync error: Invalid synchronization key") &&
|
|
firstIteration) {
|
|
// fall back to slow sync
|
|
// slowSync = true;
|
|
setCurrentSyncKey("");
|
|
m_trackingNode->clear();
|
|
continue;
|
|
}
|
|
|
|
gerror.throwError(SE_HERE, "reading ActiveSync changes");
|
|
}
|
|
GStringPtr bufferOwner(buffer, "reading changes: empty sync key returned");
|
|
|
|
// 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) {
|
|
if (!item->server_id) {
|
|
throwError(SE_HERE, "no server ID for new eas item");
|
|
}
|
|
string easid(item->server_id);
|
|
if (easid.empty()) {
|
|
throwError(SE_HERE, "empty server ID for new eas item");
|
|
}
|
|
SE_LOG_DEBUG(getDisplayName(), "new eas item %s", easid.c_str());
|
|
if (!item->data) {
|
|
throwError(SE_HERE, StringPrintf("no body returned for 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(getDisplayName(), "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) {
|
|
if (!item->server_id) {
|
|
throwError(SE_HERE, "no server ID for updated eas item");
|
|
}
|
|
string easid(item->server_id);
|
|
if (easid.empty()) {
|
|
throwError(SE_HERE, "empty server ID for updated eas item");
|
|
}
|
|
SE_LOG_DEBUG(getDisplayName(), "updated eas item %s", easid.c_str());
|
|
if (!item->data) {
|
|
throwError(SE_HERE, StringPrintf("no body returned for 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(getDisplayName(), "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) {
|
|
if (!serverID) {
|
|
throwError(SE_HERE, "no server ID for deleted eas item");
|
|
}
|
|
string easid(serverID);
|
|
if (easid.empty()) {
|
|
throwError(SE_HERE, "empty server ID for deleted eas item");
|
|
}
|
|
Event &event = findItem(easid);
|
|
if (event.m_subids.empty()) {
|
|
SE_LOG_DEBUG(getDisplayName(), "deleted eas item %s empty?!", easid.c_str());
|
|
} else {
|
|
BOOST_FOREACH(const std::string &subid, event.m_subids) {
|
|
SE_LOG_DEBUG(getDisplayName(), "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);
|
|
}
|
|
|
|
// update key
|
|
setCurrentSyncKey(buffer);
|
|
|
|
// Google hack: if we started with an empty sync key (= slow sync)
|
|
// and got no results (= existing items), then try one more time,
|
|
// because Google only seems to report results when asked with
|
|
// a valid sync key. As an additional sanity check make sure that
|
|
// we have a valid sync key now.
|
|
if (wasSlowSync &&
|
|
created.empty() &&
|
|
!getCurrentSyncKey().empty()) {
|
|
moreAvailable = true;
|
|
}
|
|
}
|
|
|
|
// 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(getDisplayName(), "existing eas item %s = uid %s + rid %s",
|
|
easid.c_str(), eventptr->m_uid.c_str(), subid.c_str());
|
|
addItem(createLUID(easid, subid), ANY);
|
|
}
|
|
}
|
|
}
|
|
|
|
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();
|
|
|
|
std::string newSyncKey = getCurrentSyncKey();
|
|
SE_LOG_DEBUG(getDisplayName(), "next sync key %s", newSyncKey.empty() ? "empty" : newSyncKey.c_str());
|
|
return newSyncKey;
|
|
}
|
|
|
|
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(SE_HERE, STATUS_NOT_FOUND, "merged event not found: " + easid);
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
InsertItemResultState state = ITEM_OKAY;
|
|
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
|
|
state = ITEM_MERGED;
|
|
} 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 && !subid.empty()) {
|
|
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 (subid.empty() && subid != knownSubID) {
|
|
// fix incomplete iCalendar 2.0 item: should have had a RECURRENCE-ID
|
|
icalcomponent *newcomp =
|
|
icalcomponent_get_first_component(newEvent->m_calendar, ICAL_VEVENT_COMPONENT);
|
|
icalproperty *prop = icalcomponent_get_first_property(newcomp, ICAL_RECURRENCEID_PROPERTY);
|
|
if (prop) {
|
|
icalcomponent_remove_property(newcomp, prop);
|
|
icalproperty_free(prop);
|
|
}
|
|
|
|
// reconstruct RECURRENCE-ID with known value and TZID from start time of
|
|
// the parent event or (if not found) the current event
|
|
eptr<icalproperty> rid(icalproperty_new_recurrenceid(icaltime_from_string(knownSubID.c_str())),
|
|
"new rid");
|
|
icalproperty *dtstart = NULL;
|
|
icalcomponent *comp;
|
|
// look for parent first
|
|
for (comp = icalcomponent_get_first_component(event.m_calendar, ICAL_VEVENT_COMPONENT);
|
|
comp && !dtstart;
|
|
comp = icalcomponent_get_next_component(event.m_calendar, ICAL_VEVENT_COMPONENT)) {
|
|
if (!icalcomponent_get_first_property(comp, ICAL_RECURRENCEID_PROPERTY)) {
|
|
dtstart = icalcomponent_get_first_property(comp, ICAL_DTSTART_PROPERTY);
|
|
}
|
|
}
|
|
// fall back to current event
|
|
if (!dtstart) {
|
|
dtstart = icalcomponent_get_first_property(newcomp, ICAL_DTSTART_PROPERTY);
|
|
}
|
|
// ignore missing TZID
|
|
if (dtstart) {
|
|
icalparameter *tzid = icalproperty_get_first_parameter(dtstart, ICAL_TZID_PARAMETER);
|
|
if (tzid) {
|
|
icalproperty_set_parameter(rid, icalparameter_new_clone(tzid));
|
|
}
|
|
}
|
|
|
|
// finally add RECURRENCE-ID and fix newEvent's meta information
|
|
icalcomponent_add_property(newcomp, rid.release());
|
|
subid = knownSubID;
|
|
newEvent->m_subids.erase("");
|
|
newEvent->m_subids.insert(subid);
|
|
}
|
|
|
|
if (event.m_subids.size() == 1 &&
|
|
*event.m_subids.begin() == subid) {
|
|
// special case: no need to load old data, replace or request merge immediately
|
|
event.m_calendar = newEvent->m_calendar;
|
|
if (easid != callerEasID) {
|
|
state = ITEM_NEEDS_MERGE;
|
|
goto done;
|
|
}
|
|
} else {
|
|
// populate event
|
|
loadItem(event);
|
|
|
|
// 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 to
|
|
// merge the items, if not, then don't complain about
|
|
// it not being found (like we do when the item should exist
|
|
// but doesn't)
|
|
if (removeme) {
|
|
state = ITEM_NEEDS_MERGE;
|
|
goto done;
|
|
} 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);
|
|
icalcomponent_free(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_state == ITEM_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");
|
|
}
|
|
}
|
|
|
|
done:
|
|
return SyncSourceRaw::InsertItemResult(createLUID(easid, subid),
|
|
"", state);
|
|
}
|
|
|
|
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 {
|
|
throwError(SE_HERE, STATUS_NOT_FOUND, "sub event not found: " + subid + " in " + easid);
|
|
}
|
|
} 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) {
|
|
throwError(SE_HERE, STATUS_NOT_FOUND, "sub event not found: " + subid + " in " + easid);
|
|
}
|
|
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) {
|
|
throwError(SE_HERE, STATUS_NOT_FOUND, "sub event not found: " + subid + " in " + easid);
|
|
} 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) {
|
|
throwError(SE_HERE, STATUS_NOT_FOUND, "sub event not found: " + subid + " in " + easid);
|
|
}
|
|
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_state != ITEM_OKAY ||
|
|
res.m_luid != easid) {
|
|
SE_THROW("unexpected result of removing sub event");
|
|
}
|
|
}
|
|
}
|
|
|
|
void ActiveSyncCalendarSource::removeAllItems()
|
|
{
|
|
BOOST_FOREACH(const EventCache::value_type &entry, m_cache) {
|
|
ActiveSyncSource::deleteItem(entry.first);
|
|
}
|
|
m_cache.clear();
|
|
}
|
|
|
|
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
|