00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019 #include <memory>
00020 using namespace std;
00021
00022 #include "config.h"
00023
00024 #ifdef ENABLE_ECAL
00025
00026
00027 #include "libical/icalstrdup.h"
00028
00029 #include "EvolutionSyncClient.h"
00030 #include "EvolutionCalendarSource.h"
00031 #include "EvolutionMemoSource.h"
00032 #include "EvolutionSmartPtr.h"
00033 #include "e-cal-check-timezones.h"
00034
00035 #include <common/base/Log.h>
00036
00037 #include <boost/foreach.hpp>
00038
00039 static const string
00040 EVOLUTION_CALENDAR_PRODID("PRODID:-//ACME//NONSGML SyncEvolution//EN"),
00041 EVOLUTION_CALENDAR_VERSION("VERSION:2.0");
00042
00043 class unrefECalObjectList {
00044 public:
00045
00046 static void unref(GList *pointer) {
00047 if (pointer) {
00048 e_cal_free_object_list(pointer);
00049 }
00050 }
00051 };
00052
00053
00054 EvolutionCalendarSource::EvolutionCalendarSource(ECalSourceType type,
00055 const EvolutionSyncSourceParams ¶ms) :
00056 TrackingSyncSource(params),
00057 m_type(type)
00058 {
00059 }
00060
00061 EvolutionCalendarSource::EvolutionCalendarSource( const EvolutionCalendarSource &other ) :
00062 TrackingSyncSource(other),
00063 m_type(other.m_type)
00064 {
00065 switch (m_type) {
00066 case E_CAL_SOURCE_TYPE_EVENT:
00067 m_typeName = "calendar";
00068 m_newSystem = e_cal_new_system_calendar;
00069 break;
00070 case E_CAL_SOURCE_TYPE_TODO:
00071 m_typeName = "task list";
00072 m_newSystem = e_cal_new_system_tasks;
00073 break;
00074 case E_CAL_SOURCE_TYPE_JOURNAL:
00075 m_typeName = "memo list";
00076
00077
00078
00079 m_newSystem = NULL ;
00080 break;
00081 default:
00082 EvolutionSyncClient::throwError("internal error, invalid calendar type");
00083 break;
00084 }
00085 }
00086
00087 EvolutionSyncSource::Databases EvolutionCalendarSource::getDatabases()
00088 {
00089 ESourceList *sources = NULL;
00090 GError *gerror = NULL;
00091 Databases result;
00092
00093 if (!e_cal_get_sources(&sources, m_type, &gerror)) {
00094
00095
00096 if (!gerror) {
00097 return result;
00098 }
00099 throwError("unable to access backend databases", gerror);
00100 }
00101
00102 bool first = true;
00103 for (GSList *g = e_source_list_peek_groups (sources); g; g = g->next) {
00104 ESourceGroup *group = E_SOURCE_GROUP (g->data);
00105 for (GSList *s = e_source_group_peek_sources (group); s; s = s->next) {
00106 ESource *source = E_SOURCE (s->data);
00107 eptr<char> uri(e_source_get_uri(source));
00108 result.push_back(Database(e_source_peek_name(source),
00109 uri ? uri.get() : "",
00110 first));
00111 first = false;
00112 }
00113 }
00114 return result;
00115 }
00116
00117 char *EvolutionCalendarSource::authenticate(const char *prompt,
00118 const char *key)
00119 {
00120 const char *passwd = getPassword();
00121
00122 LOG.debug("%s: authentication requested, prompt \"%s\", key \"%s\" => %s",
00123 getName(), prompt, key,
00124 passwd && passwd[0] ? "returning configured password" : "no password configured");
00125 return passwd && passwd[0] ? strdup(passwd) : NULL;
00126 }
00127
00128 void EvolutionCalendarSource::open()
00129 {
00130 ESourceList *sources;
00131 GError *gerror = NULL;
00132
00133 if (!e_cal_get_sources(&sources, m_type, &gerror)) {
00134 throwError("unable to access backend databases", gerror);
00135 }
00136
00137 string id = getDatabaseID();
00138 ESource *source = findSource(sources, id);
00139 bool onlyIfExists = true;
00140 if (!source) {
00141
00142
00143 if (id == "<<system>>" && m_newSystem) {
00144 m_calendar.set(m_newSystem(), (string("system ") + m_typeName).c_str());
00145 } else if (!id.compare(0, 7, "file://")) {
00146 m_calendar.set(e_cal_new_from_uri(id.c_str(), m_type), (string("creating ") + m_typeName).c_str());
00147 } else {
00148 throwError(string("not found: '") + id + "'");
00149 }
00150 onlyIfExists = false;
00151 } else {
00152 m_calendar.set(e_cal_new(source, m_type), m_typeName.c_str());
00153 }
00154
00155 e_cal_set_auth_func(m_calendar, eCalAuthFunc, this);
00156
00157 if (!e_cal_open(m_calendar, onlyIfExists, &gerror)) {
00158
00159 g_clear_error(&gerror);
00160 sleep(5);
00161 if (!e_cal_open(m_calendar, onlyIfExists, &gerror)) {
00162 throwError(string("opening ") + m_typeName, gerror );
00163 }
00164 }
00165
00166 g_signal_connect_after(m_calendar,
00167 "backend-died",
00168 G_CALLBACK(EvolutionSyncClient::fatalError),
00169 (void *)"Evolution Data Server has died unexpectedly, database no longer available.");
00170 }
00171
00172 void EvolutionCalendarSource::listAllItems(RevisionMap_t &revisions)
00173 {
00174 GError *gerror = NULL;
00175 GList *nextItem;
00176
00177 m_allLUIDs.clear();
00178 if (!e_cal_get_object_list_as_comp(m_calendar,
00179 "#t",
00180 &nextItem,
00181 &gerror)) {
00182 throwError( "reading all items", gerror );
00183 }
00184 eptr<GList> listptr(nextItem);
00185 while (nextItem) {
00186 ECalComponent *ecomp = E_CAL_COMPONENT(nextItem->data);
00187 ItemID id = getItemID(ecomp);
00188 string luid = id.getLUID();
00189 string modTime = getItemModTime(ecomp);
00190
00191 m_allLUIDs.insert(luid);
00192 revisions[luid] = modTime;
00193 nextItem = nextItem->next;
00194 }
00195 }
00196
00197 void EvolutionCalendarSource::close()
00198 {
00199
00200
00201
00202
00203 static int secs = 5;
00204 static bool checked = false;
00205 if (!checked) {
00206
00207 const char *delay = getenv("SYNC_EVOLUTION_EVO_CALENDAR_DELAY");
00208 if (delay) {
00209 secs = atoi(delay);
00210 }
00211 checked = true;
00212 }
00213
00214 sleepSinceModification(secs);
00215
00216 m_calendar = NULL;
00217 }
00218
00219 void EvolutionCalendarSource::exportData(ostream &out)
00220 {
00221 GList *nextItem;
00222 GError *gerror = NULL;
00223
00224 if (!e_cal_get_object_list_as_comp(m_calendar,
00225 "(contains? \"any\" \"\")",
00226 &nextItem,
00227 &gerror)) {
00228 throwError( "reading all items", gerror );
00229 }
00230 eptr<GList> listptr(nextItem);
00231 while (nextItem) {
00232 ItemID id = getItemID(E_CAL_COMPONENT(nextItem->data));
00233 out << retrieveItemAsString(id);
00234 out << "\r\n";
00235 nextItem = nextItem->next;
00236 }
00237 }
00238
00239 SyncItem *EvolutionCalendarSource::createItem(const string &luid)
00240 {
00241 logItem( luid, "extracting from EV", true );
00242
00243 ItemID id(luid);
00244 string icalstr = retrieveItemAsString(id);
00245
00246 auto_ptr<SyncItem> item(new SyncItem(luid.c_str()));
00247 item->setData(icalstr.c_str(), icalstr.size());
00248 item->setDataType("text/calendar");
00249 item->setModificationTime(0);
00250
00251 return item.release();
00252 }
00253
00254 void EvolutionCalendarSource::setItemStatusThrow(const char *key, int status)
00255 {
00256 switch (status) {
00257 case STC_CONFLICT_RESOLVED_WITH_SERVER_DATA:
00258 LOG.error("%s: item %.80s: conflict, will be replaced by server\n",
00259 getName(), key);
00260 break;
00261 }
00262 TrackingSyncSource::setItemStatusThrow(key, status);
00263 }
00264
00265 EvolutionCalendarSource::InsertItemResult EvolutionCalendarSource::insertItem(const string &luid, const SyncItem &item)
00266 {
00267 bool update = !luid.empty();
00268 bool merged = false;
00269 bool detached = false;
00270 string newluid = luid;
00271 string data = (const char *)item.getData();
00272 string modTime;
00273
00274
00275
00276
00277
00278
00279
00280
00281
00282 size_t propstart = data.find("\nCATEGORIES");
00283 bool modified = false;
00284 while (propstart != data.npos) {
00285 size_t eol = data.find('\n', propstart + 1);
00286 size_t comma = data.find(',', propstart);
00287
00288 while (eol != data.npos &&
00289 comma != data.npos &&
00290 comma < eol) {
00291 if (data[comma-1] != '\\') {
00292 data.insert(comma, "\\");
00293 comma++;
00294 modified = true;
00295 }
00296 comma = data.find(',', comma + 1);
00297 }
00298 propstart = data.find("\nCATEGORIES", propstart + 1);
00299 }
00300 if (modified) {
00301 LOG.debug("after replacing , with \\, in CATEGORIES:\n%s", data.c_str());
00302 }
00303
00304 eptr<icalcomponent> icomp(icalcomponent_new_from_string((char *)data.c_str()));
00305
00306 if( !icomp ) {
00307 throwError(string("failure parsing ical") + data);
00308 }
00309
00310 GError *gerror = NULL;
00311
00312
00313 if (!e_cal_check_timezones(icomp,
00314 NULL,
00315 e_cal_tzlookup_ecal,
00316 (const void *)m_calendar.get(),
00317 &gerror)) {
00318 throwError(string("fixing timezones") + data,
00319 gerror);
00320 }
00321
00322
00323
00324 for (icalcomponent *tcomp = icalcomponent_get_first_component(icomp, ICAL_VTIMEZONE_COMPONENT);
00325 tcomp;
00326 tcomp = icalcomponent_get_next_component(icomp, ICAL_VTIMEZONE_COMPONENT)) {
00327 eptr<icaltimezone> zone(icaltimezone_new(), "icaltimezone");
00328 icaltimezone_set_component(zone, tcomp);
00329
00330 GError *gerror = NULL;
00331 gboolean success = e_cal_add_timezone(m_calendar, zone, &gerror);
00332 if (!success) {
00333 throwError(string("error adding VTIMEZONE ") + icaltimezone_get_tzid(zone),
00334 gerror);
00335 }
00336 }
00337
00338
00339
00340
00341 icalcomponent *subcomp = icalcomponent_get_first_component(icomp,
00342 getCompType());
00343 if (!subcomp) {
00344 throwError("extracting event");
00345 }
00346
00347
00348
00349
00350 icalproperty *modprop;
00351 while ((modprop = icalcomponent_get_first_property(subcomp, ICAL_LASTMODIFIED_PROPERTY)) != NULL) {
00352 icalcomponent_remove_property(subcomp, modprop);
00353 }
00354
00355 if (!update) {
00356 ItemID id = getItemID(subcomp);
00357 const char *uid = NULL;
00358
00359
00360
00361
00362
00363
00364
00365
00366
00367
00368
00369
00370
00371
00372
00373
00374 newluid = id.getLUID();
00375 if (m_allLUIDs.find(newluid) != m_allLUIDs.end()) {
00376 logItem(item, "exists already, updating instead");
00377 merged = true;
00378 } else {
00379
00380
00381
00382 if (!id.m_rid.empty() &&
00383 m_allLUIDs.find(ItemID::getLUID(id.m_uid, "")) != m_allLUIDs.end()) {
00384 detached = true;
00385 } else {
00386
00387
00388
00389
00390
00391
00392
00393
00394 ICalComps_t children;
00395 if (id.m_rid.empty()) {
00396 children = removeEvents(id.m_uid, true);
00397 }
00398
00399
00400 if(e_cal_create_object(m_calendar, subcomp, (char **)&uid, &gerror)) {
00401
00402
00403
00404 ItemID newid(!id.m_uid.empty() ? id.m_uid : uid, id.m_rid);
00405 newluid = newid.getLUID();
00406 modTime = getItemModTime(newid);
00407 m_allLUIDs.insert(newluid);
00408 } else {
00409 throwError("storing new item", gerror);
00410 }
00411
00412
00413
00414 BOOST_FOREACH(boost::shared_ptr< eptr<icalcomponent> > &icalcomp, children) {
00415 if (!e_cal_modify_object(m_calendar, *icalcomp,
00416 CALOBJ_MOD_THIS,
00417 &gerror)) {
00418 throwError(string("recreating item ") + item.getKey(), gerror);
00419 }
00420 }
00421 }
00422 }
00423 }
00424
00425 if (update || merged || detached) {
00426 ItemID id(newluid);
00427 bool isParent = id.m_rid.empty();
00428
00429
00430 if (update && !id.m_uid.empty()) {
00431 icalcomponent_set_uid(subcomp, id.m_uid.c_str());
00432 }
00433
00434 if (isParent) {
00435
00436
00437
00438
00439
00440 bool hasChildren = false;
00441 BOOST_FOREACH(ItemID existingId, m_allLUIDs) {
00442 if (existingId.m_uid == id.m_uid &&
00443 existingId.m_rid.size()) {
00444 hasChildren = true;
00445 break;
00446 }
00447 }
00448
00449 if (hasChildren) {
00450
00451
00452
00453 ICalComps_t children = removeEvents(id.m_uid, true);
00454
00455
00456 const char *uid = NULL;
00457 if(!e_cal_create_object(m_calendar, subcomp, (char **)&uid, &gerror)) {
00458 throwError(string("creating updated item ") + item.getKey(), gerror);
00459 }
00460
00461
00462
00463 BOOST_FOREACH(boost::shared_ptr< eptr<icalcomponent> > &icalcomp, children) {
00464 if (!e_cal_modify_object(m_calendar, *icalcomp,
00465 CALOBJ_MOD_THIS,
00466 &gerror)) {
00467 throwError(string("recreating item ") + item.getKey(), gerror);
00468 }
00469 }
00470 } else {
00471
00472 if (!e_cal_modify_object(m_calendar, subcomp,
00473 CALOBJ_MOD_ALL,
00474 &gerror)) {
00475 throwError(string("updating item ") + item.getKey(), gerror);
00476 }
00477 }
00478 } else {
00479
00480 if (!e_cal_modify_object(m_calendar, subcomp,
00481 CALOBJ_MOD_THIS,
00482 &gerror)) {
00483 throwError(string("updating item ") + item.getKey(), gerror);
00484 }
00485 }
00486
00487 ItemID newid = getItemID(subcomp);
00488 newluid = newid.getLUID();
00489 modTime = getItemModTime(newid);
00490 }
00491
00492 return InsertItemResult(newluid, modTime, merged);
00493 }
00494
00495 EvolutionCalendarSource::ICalComps_t EvolutionCalendarSource::removeEvents(const string &uid, bool returnOnlyChildren)
00496 {
00497 ICalComps_t events;
00498
00499 BOOST_FOREACH(const string &luid, m_allLUIDs) {
00500 ItemID id(luid);
00501
00502 if (id.m_uid == uid) {
00503 icalcomponent *icomp = retrieveItem(id);
00504 if (icomp) {
00505 if (id.m_rid.empty() && returnOnlyChildren) {
00506 icalcomponent_free(icomp);
00507 } else {
00508 events.push_back(ICalComps_t::value_type(new eptr<icalcomponent>(icomp)));
00509 }
00510 }
00511 }
00512 }
00513
00514
00515 GError *gerror = NULL;
00516 if(!e_cal_remove_object(m_calendar,
00517 uid.c_str(),
00518 &gerror)) {
00519 if (gerror->domain == E_CALENDAR_ERROR &&
00520 gerror->code == E_CALENDAR_STATUS_OBJECT_NOT_FOUND) {
00521 LOG.debug("%s: %s: request to delete non-existant item ignored",
00522 getName(), uid.c_str());
00523 g_clear_error(&gerror);
00524 } else {
00525 throwError(string("deleting item " ) + uid, gerror);
00526 }
00527 }
00528
00529 return events;
00530 }
00531
00532 void EvolutionCalendarSource::deleteItem(const string &luid)
00533 {
00534 GError *gerror = NULL;
00535 ItemID id(luid);
00536
00537 if (id.m_rid.empty()) {
00538
00539
00540
00541
00542
00543
00544
00545 ICalComps_t children = removeEvents(id.m_uid, true);
00546
00547
00548 BOOST_FOREACH(boost::shared_ptr< eptr<icalcomponent> > &icalcomp, children) {
00549 char *uid;
00550
00551 if (!e_cal_create_object(m_calendar, *icalcomp, &uid, &gerror)) {
00552 throwError(string("recreating item ") + luid, gerror);
00553 }
00554 }
00555 } else if(!e_cal_remove_object_with_mod(m_calendar,
00556 id.m_uid.c_str(),
00557 id.m_rid.c_str(),
00558 CALOBJ_MOD_THIS,
00559 &gerror)) {
00560 if (gerror->domain == E_CALENDAR_ERROR &&
00561 gerror->code == E_CALENDAR_STATUS_OBJECT_NOT_FOUND) {
00562 LOG.debug("%s: %s: request to delete non-existant item ignored",
00563 getName(), luid.c_str());
00564 g_clear_error(&gerror);
00565 } else {
00566 throwError(string("deleting item " ) + luid, gerror);
00567 }
00568 }
00569 m_allLUIDs.erase(luid);
00570 }
00571
00572 void EvolutionCalendarSource::logItem(const string &luid, const string &info, bool debug)
00573 {
00574 if (LOG.getLevel() >= (debug ? LOG_LEVEL_DEBUG : LOG_LEVEL_INFO)) {
00575 (LOG.*(debug ? &Log::debug : &Log::info))("%s: %s: %s", getName(), luid.c_str(), info.c_str());
00576 }
00577 }
00578
00579
00580
00581
00582
00583 static string extractProp(const char *data, const char *keyword)
00584 {
00585 string prop;
00586
00587 const char *keyptr = strstr(data, keyword);
00588 if (keyptr) {
00589 const char *end = strpbrk(keyptr + 1, "\n\r");
00590 if (end) {
00591 prop.assign(keyptr + strlen(keyword), end - keyptr - strlen(keyword));
00592 } else {
00593 prop.assign(keyptr + strlen(keyword));
00594 }
00595 }
00596 return prop;
00597 }
00598
00599 void EvolutionCalendarSource::logItem(const SyncItem &item, const string &info, bool debug)
00600 {
00601 if (LOG.getLevel() >= (debug ? LOG_LEVEL_DEBUG : LOG_LEVEL_INFO)) {
00602 const char *keyptr = item.getKey();
00603 string key;
00604 if (!keyptr || !keyptr[0]) {
00605
00606 const char *data = (const char *)item.getData();
00607 string uid = extractProp(data, "\nUID:");
00608 string rid = extractProp(data, "\nRECURRENCE-ID:");
00609 if (uid.empty()) {
00610 key = "<<no UID>>";
00611 } else {
00612 key = ItemID::getLUID(uid, rid);
00613 }
00614 } else {
00615 key = keyptr;
00616 }
00617 (LOG.*(debug ? &Log::debug : &Log::info))("%s: %s: %s", getName(), key.c_str(), info.c_str());
00618 }
00619 }
00620
00621 icalcomponent *EvolutionCalendarSource::retrieveItem(const ItemID &id)
00622 {
00623 GError *gerror = NULL;
00624 icalcomponent *comp;
00625
00626 if (!e_cal_get_object(m_calendar,
00627 id.m_uid.c_str(),
00628 !id.m_rid.empty() ? id.m_rid.c_str() : NULL,
00629 &comp,
00630 &gerror)) {
00631 throwError(string("retrieving item: ") + id.getLUID(), gerror);
00632 }
00633 if (!comp) {
00634 throwError(string("retrieving item: ") + id.getLUID());
00635 }
00636
00637 return comp;
00638 }
00639
00640 string EvolutionCalendarSource::retrieveItemAsString(const ItemID &id)
00641 {
00642 eptr<icalcomponent> comp(retrieveItem(id));
00643 eptr<char> icalstr;
00644
00645 icalstr = e_cal_get_component_as_string(m_calendar, comp);
00646 if (!icalstr) {
00647 throwError(string("could not encode item as iCal: ") + id.getLUID());
00648 }
00649
00650
00651
00652
00653
00654
00655
00656
00657
00658 string data = string(icalstr);
00659 size_t propstart = data.find("\nCATEGORIES");
00660 bool modified = false;
00661 while (propstart != data.npos) {
00662 size_t eol = data.find('\n', propstart + 1);
00663 size_t comma = data.find(',', propstart);
00664
00665 while (eol != data.npos &&
00666 comma != data.npos &&
00667 comma < eol) {
00668 if (data[comma-1] == '\\') {
00669 data.erase(comma - 1, 1);
00670 comma--;
00671 modified = true;
00672 }
00673 comma = data.find(',', comma + 1);
00674 }
00675 propstart = data.find("\nCATEGORIES", propstart + 1);
00676 }
00677 if (modified) {
00678 LOG.debug("after replacing \\, with , in CATEGORIES:\n%s", data.c_str());
00679 }
00680
00681 return data;
00682 }
00683
00684 string EvolutionCalendarSource::ItemID::getLUID() const
00685 {
00686 return getLUID(m_uid, m_rid);
00687 }
00688
00689 string EvolutionCalendarSource::ItemID::getLUID(const string &uid, const string &rid)
00690 {
00691 return uid + "-rid" + rid;
00692 }
00693
00694 EvolutionCalendarSource::ItemID::ItemID(const string &luid)
00695 {
00696 size_t ridoff = luid.rfind("-rid");
00697 if (ridoff != luid.npos) {
00698 const_cast<string &>(m_uid) = luid.substr(0, ridoff);
00699 const_cast<string &>(m_rid) = luid.substr(ridoff + strlen("-rid"));
00700 } else {
00701 const_cast<string &>(m_uid) = luid;
00702 }
00703 }
00704
00705 EvolutionCalendarSource::ItemID EvolutionCalendarSource::getItemID(ECalComponent *ecomp)
00706 {
00707 icalcomponent *icomp = e_cal_component_get_icalcomponent(ecomp);
00708 if (!icomp) {
00709 throwError("internal error in getItemID(): ECalComponent without icalcomp");
00710 }
00711 return getItemID(icomp);
00712 }
00713
00714 EvolutionCalendarSource::ItemID EvolutionCalendarSource::getItemID(icalcomponent *icomp)
00715 {
00716 const char *uid;
00717 struct icaltimetype rid;
00718
00719 uid = icalcomponent_get_uid(icomp);
00720 rid = icalcomponent_get_recurrenceid(icomp);
00721 return ItemID(uid ? uid : "",
00722 icalTime2Str(rid));
00723 }
00724
00725 string EvolutionCalendarSource::getItemModTime(ECalComponent *ecomp)
00726 {
00727 struct icaltimetype *modTime;
00728 e_cal_component_get_last_modified(ecomp, &modTime);
00729 eptr<struct icaltimetype, struct icaltimetype, EvolutionUnrefFree<struct icaltimetype> > modTimePtr(modTime);
00730 if (!modTimePtr) {
00731 return "";
00732 } else {
00733 return icalTime2Str(*modTimePtr);
00734 }
00735 }
00736
00737 string EvolutionCalendarSource::getItemModTime(const ItemID &id)
00738 {
00739 eptr<icalcomponent> icomp(retrieveItem(id));
00740 icalproperty *lastModified = icalcomponent_get_first_property(icomp, ICAL_LASTMODIFIED_PROPERTY);
00741 if (!lastModified) {
00742 return "";
00743 } else {
00744 struct icaltimetype modTime = icalproperty_get_lastmodified(lastModified);
00745 return icalTime2Str(modTime);
00746 }
00747 }
00748
00749 string EvolutionCalendarSource::icalTime2Str(const icaltimetype &tt)
00750 {
00751 static const struct icaltimetype null = { 0 };
00752 if (!memcmp(&tt, &null, sizeof(null))) {
00753 return "";
00754 } else {
00755 eptr<char> timestr(ical_strdup(icaltime_as_ical_string(tt)));
00756 if (!timestr) {
00757 throwError("cannot convert to time string");
00758 }
00759 return timestr.get();
00760 }
00761 }
00762
00763 #endif
00764
00765 #ifdef ENABLE_MODULES
00766 # include "EvolutionCalendarSourceRegister.cpp"
00767 #endif