2 * Evolution calendar - caldav backend
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) version 3.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with the program; if not, see <http://www.gnu.org/licenses/>
19 * Christian Kellner <gicmo@gnome.org>
21 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
28 #include <gconf/gconf-client.h>
29 #include <glib/gi18n-lib.h>
30 #include "libedataserver/e-xml-hash-utils.h"
31 #include "libedataserver/e-proxy.h"
32 #include <libecal/e-cal-recur.h>
33 #include <libecal/e-cal-util.h>
34 #include <libecal/e-cal-time-util.h>
35 #include <libedata-cal/e-cal-backend-cache.h>
36 #include <libedata-cal/e-cal-backend-util.h>
37 #include <libedata-cal/e-cal-backend-sexp.h>
39 /* LibXML2 includes */
40 #include <libxml/parser.h>
41 #include <libxml/tree.h>
42 #include <libxml/xpath.h>
43 #include <libxml/xpathInternals.h>
45 /* LibSoup includes */
46 #include <libsoup/soup.h>
48 #include "e-cal-backend-caldav.h"
52 #define CALDAV_CTAG_KEY "CALDAV_CTAG"
55 #define DEFAULT_REFRESH_TIME 60
65 /* Private part of the ECalBackendHttp structure */
66 struct _ECalBackendCalDAVPrivate {
71 /* The local disk cache */
72 ECalBackendCache *cache;
74 /* should we sync for offline mode? */
77 /* TRUE after caldav_open */
80 /* lock to protect cache */
83 /* cond to synch threads */
86 /* cond to know the slave gone */
87 GCond *slave_gone_cond;
90 const GThread *synch_slave; /* just for a reference, whether thread exists */
91 SlaveCommand slave_cmd;
92 GTimeVal refresh_time;
95 /* The main soup session */
99 /* well, guess what */
105 /* Authentication info */
113 icaltimezone *default_zone;
115 /* support for 'getctag' extension */
116 gboolean ctag_supported;
118 /* TRUE when 'calendar-schedule' supported on the server */
119 gboolean calendar_schedule;
120 /* with 'calendar-schedule' supported, here's an outbox url
121 for queries of free/busy information */
122 gchar *schedule_outbox_url;
125 /* ************************************************************************* */
128 #define DEBUG_MESSAGE "message"
129 #define DEBUG_MESSAGE_HEADER "message:header"
130 #define DEBUG_MESSAGE_BODY "message:body"
131 #define DEBUG_SERVER_ITEMS "items"
133 static gboolean caldav_debug_all = FALSE;
134 static GHashTable *caldav_debug_table = NULL;
137 add_debug_key (const gchar *start, const gchar *end)
146 debug_key = debug_value = g_strndup (start, end - start);
148 debug_key = g_strchug (debug_key);
149 debug_key = g_strchomp (debug_key);
151 if (strlen (debug_key) == 0) {
152 g_free (debug_value);
156 g_hash_table_insert (caldav_debug_table,
160 d(g_debug ("Adding %s to enabled debugging keys", debug_key));
164 caldav_debug_init_once (gpointer data)
168 dbg = g_getenv ("CALDAV_DEBUG");
173 d(g_debug ("Got debug env variable: [%s]", dbg));
175 caldav_debug_table = g_hash_table_new (g_str_hash,
180 while (*ptr != '\0') {
181 if (*ptr == ',' || *ptr == ':') {
183 add_debug_key (dbg, ptr);
194 add_debug_key (dbg, ptr);
197 if (g_hash_table_lookup (caldav_debug_table, "all")) {
198 caldav_debug_all = TRUE;
199 g_hash_table_destroy (caldav_debug_table);
200 caldav_debug_table = NULL;
208 caldav_debug_init (void)
210 static GOnce debug_once = G_ONCE_INIT;
213 caldav_debug_init_once,
218 caldav_debug_show (const gchar *component)
220 if (G_UNLIKELY (caldav_debug_all)) {
222 } else if (G_UNLIKELY (caldav_debug_table != NULL) &&
223 g_hash_table_lookup (caldav_debug_table, component)) {
230 #define DEBUG_MAX_BODY_SIZE (100 * 1024 * 1024)
233 caldav_debug_setup (SoupSession *session)
236 SoupLoggerLogLevel level;
238 if (caldav_debug_show (DEBUG_MESSAGE_BODY))
239 level = SOUP_LOGGER_LOG_BODY;
240 else if (caldav_debug_show (DEBUG_MESSAGE_HEADER))
241 level = SOUP_LOGGER_LOG_HEADERS;
243 level = SOUP_LOGGER_LOG_MINIMAL;
245 logger = soup_logger_new (level, DEBUG_MAX_BODY_SIZE);
246 soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
247 g_object_unref (logger);
250 static ECalBackendSyncClass *parent_class = NULL;
252 static icaltimezone *caldav_internal_get_default_timezone (ECalBackend *backend);
253 static icaltimezone *caldav_internal_get_timezone (ECalBackend *backend, const gchar *tzid);
255 static gboolean remove_comp_from_cache (ECalBackendCalDAV *cbdav, const gchar *uid, const gchar *rid);
256 static gboolean put_comp_to_cache (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp, const gchar *href, const gchar *etag);
258 /* ************************************************************************* */
259 /* Misc. utility functions */
260 #define X_E_CALDAV "X-EVOLUTION-CALDAV-"
263 icomp_x_prop_set (icalcomponent *comp, const gchar *key, const gchar *value)
267 /* Find the old one first */
268 xprop = icalcomponent_get_first_property (comp, ICAL_X_PROPERTY);
271 const gchar *str = icalproperty_get_x_name (xprop);
273 if (!strcmp (str, key)) {
275 icalproperty_set_value_from_string (xprop, value, "NO");
277 icalcomponent_remove_property (comp, xprop);
278 icalproperty_free (xprop);
283 xprop = icalcomponent_get_next_property (comp, ICAL_X_PROPERTY);
286 if (!xprop && value) {
287 xprop = icalproperty_new_x (value);
288 icalproperty_set_x_name (xprop, key);
289 icalcomponent_add_property (comp, xprop);
295 icomp_x_prop_get (icalcomponent *comp, const gchar *key)
299 /* Find the old one first */
300 xprop = icalcomponent_get_first_property (comp, ICAL_X_PROPERTY);
303 const gchar *str = icalproperty_get_x_name (xprop);
305 if (!strcmp (str, key)) {
309 xprop = icalcomponent_get_next_property (comp, ICAL_X_PROPERTY);
313 return icalproperty_get_value_as_string_r (xprop);
319 /* passing NULL as 'href' removes the property */
321 ecalcomp_set_href (ECalComponent *comp, const gchar *href)
323 icalcomponent *icomp;
325 icomp = e_cal_component_get_icalcomponent (comp);
326 g_return_if_fail (icomp != NULL);
328 icomp_x_prop_set (icomp, X_E_CALDAV "HREF", href);
332 ecalcomp_get_href (ECalComponent *comp)
334 icalcomponent *icomp;
338 icomp = e_cal_component_get_icalcomponent (comp);
339 g_return_val_if_fail (icomp != NULL, NULL);
341 str = icomp_x_prop_get (icomp, X_E_CALDAV "HREF");
346 /* passing NULL as 'etag' removes the property */
348 ecalcomp_set_etag (ECalComponent *comp, const gchar *etag)
350 icalcomponent *icomp;
352 icomp = e_cal_component_get_icalcomponent (comp);
353 g_return_if_fail (icomp != NULL);
355 icomp_x_prop_set (icomp, X_E_CALDAV "ETAG", etag);
359 ecalcomp_get_etag (ECalComponent *comp)
361 icalcomponent *icomp;
365 icomp = e_cal_component_get_icalcomponent (comp);
366 g_return_val_if_fail (icomp != NULL, NULL);
368 str = icomp_x_prop_get (icomp, X_E_CALDAV "ETAG");
375 / * object is in synch,
376 * now isnt that ironic? :) * /
377 ECALCOMP_IN_SYNCH = 0,
379 / * local changes * /
380 ECALCOMP_LOCALLY_CREATED,
381 ECALCOMP_LOCALLY_DELETED,
382 ECALCOMP_LOCALLY_MODIFIED
386 / * oos = out of synch * /
388 ecalcomp_set_synch_state (ECalComponent *comp, ECalCompSyncState state)
390 icalcomponent *icomp;
393 icomp = e_cal_component_get_icalcomponent (comp);
395 state_string = g_strdup_printf ("%d", state);
397 icomp_x_prop_set (icomp, X_E_CALDAV "ETAG", state_string);
399 g_free (state_string);
403 /* gen uid, set it internally and report it back so we can instantly
405 * and btw FIXME!!! */
407 ecalcomp_gen_href (ECalComponent *comp)
411 icalcomponent *icomp;
413 iso = isodate_from_time_t (time (NULL));
415 href = g_strconcat (iso, ".ics", NULL);
419 icomp = e_cal_component_get_icalcomponent (comp);
420 icomp_x_prop_set (icomp, X_E_CALDAV "HREF", href);
425 /* ensure etag is quoted (to workaround potential server bugs) */
427 quote_etag (const gchar *etag)
431 if (etag && (strlen (etag) < 2 || etag[strlen (etag) - 1] != '\"')) {
432 ret = g_strdup_printf ("\"%s\"", etag);
434 ret = g_strdup (etag);
440 /* ************************************************************************* */
442 static ECalBackendSyncStatus
443 status_code_to_result (guint status_code, ECalBackendCalDAVPrivate *priv)
445 ECalBackendSyncStatus result;
447 if (SOUP_STATUS_IS_SUCCESSFUL (status_code)) {
448 return GNOME_Evolution_Calendar_Success;
451 switch (status_code) {
454 result = GNOME_Evolution_Calendar_NoSuchCal;
458 result = GNOME_Evolution_Calendar_AuthenticationFailed;
462 if (priv && priv->need_auth)
463 result = GNOME_Evolution_Calendar_AuthenticationFailed;
465 result = GNOME_Evolution_Calendar_AuthenticationRequired;
469 d(g_debug ("CalDAV:%s: Unhandled status code %d\n", G_STRFUNC, status_code));
470 result = GNOME_Evolution_Calendar_OtherError;
476 /* !TS, call with lock held */
477 static ECalBackendSyncStatus
478 check_state (ECalBackendCalDAV *cbdav, gboolean *online)
480 ECalBackendCalDAVPrivate *priv;
482 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
487 return GNOME_Evolution_Calendar_OtherError;
490 if (priv->mode == CAL_MODE_LOCAL) {
492 if (! priv->do_offline) {
493 return GNOME_Evolution_Calendar_RepositoryOffline;
500 return GNOME_Evolution_Calendar_Success;
503 /* ************************************************************************* */
504 /* XML Parsing code */
506 static xmlXPathObjectPtr
507 xpath_eval (xmlXPathContextPtr ctx, const gchar *format, ...)
509 xmlXPathObjectPtr result;
517 va_start (args, format);
518 expr = g_strdup_vprintf (format, args);
521 result = xmlXPathEvalExpression ((xmlChar *) expr, ctx);
524 if (result == NULL) {
528 if (result->type == XPATH_NODESET &&
529 xmlXPathNodeSetIsEmpty (result->nodesetval)) {
530 xmlXPathFreeObject (result);
539 parse_status_node (xmlNodePtr node, guint *status_code)
544 content = xmlNodeGetContent (node);
546 res = soup_headers_parse_status_line ((gchar *) content,
557 xp_object_get_string (xmlXPathObjectPtr result)
564 if (result->type == XPATH_STRING) {
565 ret = g_strdup ((gchar *) result->stringval);
568 xmlXPathFreeObject (result);
572 /* as get_string but will normailze it (i.e. only take
573 * the last part of the href) */
575 xp_object_get_href (xmlXPathObjectPtr result)
583 if (result->type == XPATH_STRING) {
584 val = (gchar *) result->stringval;
586 if ((ret = g_strrstr (val, "/")) == NULL) {
589 ret++; /* skip the unwanted "/" */
592 ret = g_strdup (ret);
594 if (caldav_debug_show (DEBUG_SERVER_ITEMS))
595 printf ("CalDAV found href: %s\n", ret);
598 xmlXPathFreeObject (result);
602 /* like get_string but will quote the etag if necessary */
604 xp_object_get_etag (xmlXPathObjectPtr result)
612 if (result->type == XPATH_STRING) {
613 str = (gchar *) result->stringval;
615 ret = quote_etag (str);
618 xmlXPathFreeObject (result);
623 xp_object_get_status (xmlXPathObjectPtr result)
631 if (result->type == XPATH_STRING) {
632 res = soup_headers_parse_status_line ((gchar *) result->stringval,
642 xmlXPathFreeObject (result);
648 xp_object_get_number (xmlXPathObjectPtr result)
655 if (result->type == XPATH_STRING) {
656 ret = result->boolval;
659 xmlXPathFreeObject (result);
664 /*** *** *** *** *** *** */
665 #define XPATH_HREF "string(/D:multistatus/D:response[%d]/D:href)"
666 #define XPATH_STATUS "string(/D:multistatus/D:response[%d]/D:propstat/D:status)"
667 #define XPATH_GETETAG_STATUS "string(/D:multistatus/D:response[%d]/D:propstat/D:prop/D:getetag/../../D:status)"
668 #define XPATH_GETETAG "string(/D:multistatus/D:response[%d]/D:propstat/D:prop/D:getetag)"
669 #define XPATH_CALENDAR_DATA "string(/D:multistatus/D:response[%d]/C:calendar-data)"
670 #define XPATH_GETCTAG_STATUS "string(/D:multistatus/D:response/D:propstat/D:prop/CS:getctag/../../D:status)"
671 #define XPATH_GETCTAG "string(/D:multistatus/D:response/D:propstat/D:prop/CS:getctag)"
672 #define XPATH_OWNER_STATUS "string(/D:multistatus/D:response/D:propstat/D:prop/D:owner/D:href/../../../D:status)"
673 #define XPATH_OWNER "string(/D:multistatus/D:response/D:propstat/D:prop/D:owner/D:href)"
674 #define XPATH_SCHEDULE_OUTBOX_URL_STATUS "string(/D:multistatus/D:response/D:propstat/D:prop/C:schedule-outbox-URL/D:href/../../../D:status)"
675 #define XPATH_SCHEDULE_OUTBOX_URL "string(/D:multistatus/D:response/D:propstat/D:prop/C:schedule-outbox-URL/D:href)"
677 typedef struct _CalDAVObject CalDAVObject;
679 struct _CalDAVObject {
690 caldav_object_free (CalDAVObject *object, gboolean free_object_itself)
692 g_free (object->href);
693 g_free (object->etag);
694 g_free (object->cdata);
696 if (free_object_itself) {
702 parse_report_response (SoupMessage *soup_message, CalDAVObject **objs, gint *len)
704 xmlXPathContextPtr xpctx;
705 xmlXPathObjectPtr result;
710 g_return_val_if_fail (soup_message != NULL, FALSE);
711 g_return_val_if_fail (objs != NULL || len != NULL, FALSE);
714 doc = xmlReadMemory (soup_message->response_body->data,
715 soup_message->response_body->length,
724 xpctx = xmlXPathNewContext (doc);
726 xmlXPathRegisterNs (xpctx, (xmlChar *) "D",
729 xmlXPathRegisterNs (xpctx, (xmlChar *) "C",
730 (xmlChar *) "urn:ietf:params:xml:ns:caldav");
732 result = xpath_eval (xpctx, "/D:multistatus/D:response");
734 if (result == NULL || result->type != XPATH_NODESET) {
740 n = xmlXPathNodeSetGetLength (result->nodesetval);
743 *objs = g_new0 (CalDAVObject, n);
745 for (i = 0; i < n; i++) {
746 CalDAVObject *object;
747 xmlXPathObjectPtr xpres;
750 /* see if we got a status child in the response element */
752 xpres = xpath_eval (xpctx, XPATH_HREF, i + 1);
753 object->href = xp_object_get_href (xpres);
755 xpres = xpath_eval (xpctx,XPATH_STATUS , i + 1);
756 object->status = xp_object_get_status (xpres);
758 if (object->status && object->status != 200) {
762 xpres = xpath_eval (xpctx, XPATH_GETETAG_STATUS, i + 1);
763 object->status = xp_object_get_status (xpres);
765 if (object->status != 200) {
769 xpres = xpath_eval (xpctx, XPATH_GETETAG, i + 1);
770 object->etag = xp_object_get_etag (xpres);
772 xpres = xpath_eval (xpctx, XPATH_CALENDAR_DATA, i + 1);
773 object->cdata = xp_object_get_string (xpres);
778 xmlXPathFreeObject (result);
779 xmlXPathFreeContext (xpctx);
784 /* returns whether was able to read the xpath_value from the server's response; *value contains the result */
786 parse_propfind_response (SoupMessage *message, const gchar *xpath_status, const gchar *xpath_value, gchar **value)
788 xmlXPathContextPtr xpctx;
790 gboolean res = FALSE;
792 g_return_val_if_fail (message != NULL, FALSE);
793 g_return_val_if_fail (value != NULL, FALSE);
795 doc = xmlReadMemory (message->response_body->data,
796 message->response_body->length,
805 xpctx = xmlXPathNewContext (doc);
806 xmlXPathRegisterNs (xpctx, (xmlChar *) "D", (xmlChar *) "DAV:");
807 xmlXPathRegisterNs (xpctx, (xmlChar *) "C", (xmlChar *) "urn:ietf:params:xml:ns:caldav");
808 xmlXPathRegisterNs (xpctx, (xmlChar *) "CS", (xmlChar *) "http://calendarserver.org/ns/");
810 if (xpath_status == NULL || xp_object_get_status (xpath_eval (xpctx, xpath_status)) == 200) {
811 gchar *txt = xp_object_get_string (xpath_eval (xpctx, xpath_value));
814 gint len = strlen (txt);
816 if (*txt == '\"' && len > 2 && txt [len - 1] == '\"') {
818 *value = g_strndup (txt + 1, len - 2);
824 res = (*value) != NULL;
830 xmlXPathFreeContext (xpctx);
836 /* ************************************************************************* */
837 /* Authentication helpers for libsoup */
840 soup_authenticate (SoupSession *session,
846 ECalBackendCalDAVPrivate *priv;
847 ECalBackendCalDAV *cbdav;
849 cbdav = E_CAL_BACKEND_CALDAV (data);
850 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
852 /* do not send same password twice, but keep it for later use */
854 soup_auth_authenticate (auth, priv->username, priv->password);
857 /* ************************************************************************* */
858 /* direct CalDAV server access functions */
861 redirect_handler (SoupMessage *msg, gpointer user_data)
863 if (SOUP_STATUS_IS_REDIRECTION (msg->status_code)) {
864 SoupSession *soup_session = user_data;
866 const gchar *new_loc;
868 new_loc = soup_message_headers_get (msg->response_headers, "Location");
872 new_uri = soup_uri_new_with_base (soup_message_get_uri (msg), new_loc);
874 soup_message_set_status_full (msg,
875 SOUP_STATUS_MALFORMED,
876 "Invalid Redirect URL");
880 soup_message_set_uri (msg, new_uri);
881 soup_session_requeue_message (soup_session, msg);
883 soup_uri_free (new_uri);
888 send_and_handle_redirection (SoupSession *soup_session, SoupMessage *msg, gchar **new_location)
890 gchar *old_uri = NULL;
893 old_uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
895 soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT);
896 soup_message_add_header_handler (msg, "got_body", "Location", G_CALLBACK (redirect_handler), soup_session);
897 soup_session_send_message (soup_session, msg);
900 gchar *new_loc = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
902 if (new_loc && old_uri && !g_str_equal (new_loc, old_uri))
903 *new_location = new_loc;
912 caldav_generate_uri (ECalBackendCalDAV *cbdav, const gchar *target)
914 ECalBackendCalDAVPrivate *priv;
917 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
919 /* priv->uri *have* trailing slash already */
920 uri = g_strconcat (priv->uri, target, NULL);
925 static ECalBackendSyncStatus
926 caldav_server_open_calendar (ECalBackendCalDAV *cbdav)
928 ECalBackendCalDAVPrivate *priv;
929 SoupMessage *message;
931 gboolean calendar_access;
932 gboolean put_allowed;
933 gboolean delete_allowed;
935 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
937 /* FIXME: setup text_uri */
939 message = soup_message_new (SOUP_METHOD_OPTIONS, priv->uri);
941 return GNOME_Evolution_Calendar_NoSuchCal;
942 soup_message_headers_append (message->request_headers,
943 "User-Agent", "Evolution/" VERSION);
945 send_and_handle_redirection (priv->session, message, NULL);
947 if (! SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
948 guint status_code = message->status_code;
950 g_object_unref (message);
952 return status_code_to_result (status_code, priv);
955 /* parse the dav header, we are intreseted in the
956 * calendar-access bit only at the moment */
957 header = soup_message_headers_get (message->response_headers, "DAV");
959 calendar_access = soup_header_contains (header, "calendar-access");
960 priv->calendar_schedule = soup_header_contains (header, "calendar-schedule");
962 calendar_access = FALSE;
963 priv->calendar_schedule = FALSE;
966 /* parse the Allow header and look for PUT, DELETE at the
967 * moment (maybe we should check more here, for REPORT eg) */
968 header = soup_message_headers_get (message->response_headers, "Allow");
970 put_allowed = soup_header_contains (header, "PUT");
971 delete_allowed = soup_header_contains (header, "DELETE");
973 put_allowed = delete_allowed = FALSE;
975 g_object_unref (message);
977 if (calendar_access) {
978 priv->read_only = ! (put_allowed && delete_allowed);
979 priv->do_synch = TRUE;
980 return GNOME_Evolution_Calendar_Success;
983 return GNOME_Evolution_Calendar_NoSuchCal;
986 /* Returns whether calendar changed on the server. This works only when server
987 supports 'getctag' extension. */
989 check_calendar_changed_on_server (ECalBackendCalDAV *cbdav)
991 ECalBackendCalDAVPrivate *priv;
992 xmlOutputBufferPtr buf;
993 SoupMessage *message;
995 xmlNodePtr root, node;
997 gboolean result = TRUE;
999 g_return_val_if_fail (cbdav != NULL, TRUE);
1001 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1003 /* no support for 'getctag', thus update cache */
1004 if (!priv->ctag_supported)
1007 /* Prepare the soup message */
1008 message = soup_message_new ("PROPFIND", priv->uri);
1009 if (message == NULL)
1012 doc = xmlNewDoc ((xmlChar *) "1.0");
1013 root = xmlNewDocNode (doc, NULL, (xmlChar *) "propfind", NULL);
1014 xmlDocSetRootElement (doc, root);
1015 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", NULL);
1016 ns = xmlNewNs (root, (xmlChar *) "http://calendarserver.org/ns/", (xmlChar *) "CS");
1018 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1019 node = xmlNewTextChild (node, nsdav, (xmlChar *) "getctag", NULL);
1020 xmlSetNs (node, ns);
1022 buf = xmlAllocOutputBuffer (NULL);
1023 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1024 xmlOutputBufferFlush (buf);
1026 soup_message_headers_append (message->request_headers,
1027 "User-Agent", "Evolution/" VERSION);
1028 soup_message_headers_append (message->request_headers,
1031 soup_message_set_request (message,
1034 (gchar *) buf->buffer->content,
1037 /* Send the request now */
1038 send_and_handle_redirection (priv->session, message, NULL);
1040 /* Clean up the memory */
1041 xmlOutputBufferClose (buf);
1044 /* Check the result */
1045 if (message->status_code != 207) {
1046 /* does not support it, but report calendar changed to update cache */
1047 priv->ctag_supported = FALSE;
1051 if (parse_propfind_response (message, XPATH_GETCTAG_STATUS, XPATH_GETCTAG, &ctag)) {
1052 const gchar *my_ctag = e_cal_backend_cache_get_key_value (priv->cache, CALDAV_CTAG_KEY);
1054 if (ctag && my_ctag && g_str_equal (ctag, my_ctag)) {
1055 /* ctag is same, no change in the calendar */
1058 e_cal_backend_cache_put_key_value (priv->cache, CALDAV_CTAG_KEY, ctag);
1063 priv->ctag_supported = FALSE;
1067 g_object_unref (message);
1073 caldav_server_list_objects (ECalBackendCalDAV *cbdav, CalDAVObject **objs, gint *len)
1075 ECalBackendCalDAVPrivate *priv;
1076 xmlOutputBufferPtr buf;
1077 SoupMessage *message;
1086 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1087 /* Allocate the soup message */
1088 message = soup_message_new ("REPORT", priv->uri);
1089 if (message == NULL)
1092 /* Maybe we should just do a g_strdup_printf here? */
1093 /* Prepare request body */
1094 doc = xmlNewDoc ((xmlChar *) "1.0");
1095 root = xmlNewDocNode (doc, NULL, (xmlChar *) "calendar-query", NULL);
1096 nscd = xmlNewNs (root, (xmlChar *) "urn:ietf:params:xml:ns:caldav", (xmlChar *) "C");
1097 xmlSetNs (root, nscd);
1098 xmlDocSetRootElement (doc, root);
1100 /* Add webdav tags */
1101 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", (xmlChar *) "D");
1102 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1103 xmlNewTextChild (node, nsdav, (xmlChar *) "getetag", NULL);
1105 node = xmlNewTextChild (root, nscd, (xmlChar *) "filter", NULL);
1106 node = xmlNewTextChild (node, nscd, (xmlChar *) "comp-filter", NULL);
1107 xmlSetProp (node, (xmlChar *) "name", (xmlChar *) "VCALENDAR");
1109 sn = xmlNewTextChild (node, nscd, (xmlChar *) "comp-filter", NULL);
1110 switch (e_cal_backend_get_kind (E_CAL_BACKEND (cbdav))) {
1112 case ICAL_VEVENT_COMPONENT:
1113 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VEVENT");
1115 case ICAL_VJOURNAL_COMPONENT:
1116 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VJOURNAL");
1118 case ICAL_VTODO_COMPONENT:
1119 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VTODO");
1122 /* ^^^ add timerange for performance? */
1125 buf = xmlAllocOutputBuffer (NULL);
1126 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1127 xmlOutputBufferFlush (buf);
1129 /* Prepare the soup message */
1130 soup_message_headers_append (message->request_headers,
1131 "User-Agent", "Evolution/" VERSION);
1132 soup_message_headers_append (message->request_headers,
1135 soup_message_set_request (message,
1138 (gchar *) buf->buffer->content,
1141 /* Send the request now */
1142 send_and_handle_redirection (priv->session, message, NULL);
1144 /* Clean up the memory */
1145 xmlOutputBufferClose (buf);
1148 /* Check the result */
1149 if (message->status_code != 207) {
1150 g_warning ("Server did not response with 207, but with code %d (%s)", message->status_code, soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : "Unknown code");
1152 g_object_unref (message);
1156 /* Parse the response body */
1157 result = parse_report_response (message, objs, len);
1159 g_object_unref (message);
1164 static ECalBackendSyncStatus
1165 caldav_server_get_object (ECalBackendCalDAV *cbdav, CalDAVObject *object)
1167 ECalBackendCalDAVPrivate *priv;
1168 ECalBackendSyncStatus result;
1169 SoupMessage *message;
1173 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1174 result = GNOME_Evolution_Calendar_Success;
1176 g_assert (object != NULL && object->href != NULL);
1178 uri = caldav_generate_uri (cbdav, object->href);
1179 message = soup_message_new (SOUP_METHOD_GET, uri);
1180 if (message == NULL) {
1182 return GNOME_Evolution_Calendar_NoSuchCal;
1185 soup_message_headers_append (message->request_headers,
1186 "User-Agent", "Evolution/" VERSION);
1188 send_and_handle_redirection (priv->session, message, NULL);
1190 if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1191 guint status_code = message->status_code;
1192 g_object_unref (message);
1194 g_warning ("Could not fetch object '%s' from server, status:%d (%s)", uri, status_code, soup_status_get_phrase (status_code) ? soup_status_get_phrase (status_code) : "Unknown code");
1196 return status_code_to_result (status_code, priv);
1199 hdr = soup_message_headers_get (message->response_headers, "Content-Type");
1201 if (hdr == NULL || g_ascii_strncasecmp (hdr, "text/calendar", 13)) {
1202 result = GNOME_Evolution_Calendar_InvalidObject;
1203 g_object_unref (message);
1204 g_warning ("Object to fetch '%s' not of type text/calendar", uri);
1209 hdr = soup_message_headers_get (message->response_headers, "ETag");
1212 g_free (object->etag);
1213 object->etag = quote_etag (hdr);
1214 } else if (!object->etag) {
1215 g_warning ("UUHH no ETag, now that's bad! (at '%s')", uri);
1219 g_free (object->cdata);
1220 object->cdata = g_strdup (message->response_body->data);
1222 g_object_unref (message);
1227 static ECalBackendSyncStatus
1228 caldav_post_freebusy (ECalBackendCalDAV *cbdav, const gchar *url, gchar **post_fb)
1230 ECalBackendCalDAVPrivate *priv;
1231 SoupMessage *message;
1233 g_return_val_if_fail (cbdav != NULL, GNOME_Evolution_Calendar_OtherError);
1234 g_return_val_if_fail (url != NULL, GNOME_Evolution_Calendar_OtherError);
1235 g_return_val_if_fail (post_fb != NULL, GNOME_Evolution_Calendar_OtherError);
1236 g_return_val_if_fail (*post_fb != NULL, GNOME_Evolution_Calendar_OtherError);
1238 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1240 message = soup_message_new (SOUP_METHOD_POST, url);
1241 if (message == NULL) {
1242 return GNOME_Evolution_Calendar_NoSuchCal;
1245 soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1246 soup_message_set_request (message,
1247 "text/calendar; charset=utf-8",
1249 *post_fb, strlen (*post_fb));
1251 send_and_handle_redirection (priv->session, message, NULL);
1253 if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1254 guint status_code = message->status_code;
1255 g_object_unref (message);
1257 g_warning ("Could not post free/busy request to '%s', status:%d (%s)", url, status_code, soup_status_get_phrase (status_code) ? soup_status_get_phrase (status_code) : "Unknown code");
1258 return status_code_to_result (status_code, priv);
1262 *post_fb = g_strdup (message->response_body->data);
1264 g_object_unref (message);
1266 return GNOME_Evolution_Calendar_Success;
1269 static ECalBackendSyncStatus
1270 caldav_server_put_object (ECalBackendCalDAV *cbdav, CalDAVObject *object, icalcomponent *icalcomp)
1272 ECalBackendCalDAVPrivate *priv;
1273 ECalBackendSyncStatus result;
1274 SoupMessage *message;
1278 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1279 result = GNOME_Evolution_Calendar_Success;
1282 g_assert (object != NULL && object->cdata != NULL);
1284 uri = caldav_generate_uri (cbdav, object->href);
1285 message = soup_message_new (SOUP_METHOD_PUT, uri);
1287 if (message == NULL)
1288 return GNOME_Evolution_Calendar_NoSuchCal;
1290 soup_message_headers_append (message->request_headers,
1291 "User-Agent", "Evolution/" VERSION);
1293 /* For new items we use the If-None-Match so we don't
1294 * acidently override resources, for item updates we
1295 * use the If-Match header to avoid the Lost-update
1297 if (object->etag == NULL) {
1298 soup_message_headers_append (message->request_headers, "If-None-Match", "*");
1300 soup_message_headers_append (message->request_headers,
1301 "If-Match", object->etag);
1304 soup_message_set_request (message,
1305 "text/calendar; charset=utf-8",
1308 strlen (object->cdata));
1311 send_and_handle_redirection (priv->session, message, &uri);
1314 gchar *file = strrchr (uri, '/');
1316 /* there was a redirect, update href properly */
1320 g_free (object->href);
1322 decoded = soup_uri_decode (file + 1);
1323 object->href = soup_uri_encode (decoded ? decoded : (file + 1), NULL);
1331 result = status_code_to_result (message->status_code, priv);
1333 if (result == GNOME_Evolution_Calendar_Success) {
1334 gboolean was_get = FALSE;
1336 hdr = soup_message_headers_get (message->response_headers, "ETag");
1338 g_free (object->etag);
1339 object->etag = quote_etag (hdr);
1341 /* no ETag header returned, check for it with a GET */
1342 hdr = soup_message_headers_get (message->response_headers, "Location");
1344 /* reflect possible href change first */
1345 gchar *file = strrchr (hdr, '/');
1350 g_free (object->href);
1352 decoded = soup_uri_decode (file + 1);
1353 object->href = soup_uri_encode (decoded ? decoded : (file + 1), NULL);
1359 result = caldav_server_get_object (cbdav, object);
1363 if (result == GNOME_Evolution_Calendar_Success) {
1364 icalcomponent *use_comp = NULL;
1366 if (object->cdata && was_get) {
1367 /* maybe server also modified component, thus rather store the server's */
1368 use_comp = icalparser_parse_string (object->cdata);
1372 use_comp = icalcomp;
1374 put_comp_to_cache (cbdav, use_comp, object->href, object->etag);
1376 if (use_comp != icalcomp)
1377 icalcomponent_free (use_comp);
1381 g_object_unref (message);
1385 static ECalBackendSyncStatus
1386 caldav_server_delete_object (ECalBackendCalDAV *cbdav, CalDAVObject *object)
1388 ECalBackendCalDAVPrivate *priv;
1389 ECalBackendSyncStatus result;
1390 SoupMessage *message;
1393 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1394 result = GNOME_Evolution_Calendar_Success;
1396 g_assert (object != NULL && object->href != NULL);
1398 uri = caldav_generate_uri (cbdav, object->href);
1399 message = soup_message_new (SOUP_METHOD_DELETE, uri);
1401 if (message == NULL)
1402 return GNOME_Evolution_Calendar_NoSuchCal;
1404 soup_message_headers_append (message->request_headers,
1405 "User-Agent", "Evolution/" VERSION);
1407 if (object->etag != NULL) {
1408 soup_message_headers_append (message->request_headers,
1409 "If-Match", object->etag);
1412 send_and_handle_redirection (priv->session, message, NULL);
1414 result = status_code_to_result (message->status_code, priv);
1416 g_object_unref (message);
1422 caldav_receive_schedule_outbox_url (ECalBackendCalDAV *cbdav)
1424 ECalBackendCalDAVPrivate *priv;
1425 SoupMessage *message;
1426 xmlOutputBufferPtr buf;
1428 xmlNodePtr root, node;
1430 gchar *owner = NULL;
1432 g_return_val_if_fail (cbdav != NULL, FALSE);
1434 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1435 g_return_val_if_fail (priv != NULL, FALSE);
1436 g_return_val_if_fail (priv->schedule_outbox_url == NULL, TRUE);
1438 /* Prepare the soup message */
1439 message = soup_message_new ("PROPFIND", priv->uri);
1440 if (message == NULL)
1443 doc = xmlNewDoc ((xmlChar *) "1.0");
1444 root = xmlNewDocNode (doc, NULL, (xmlChar *) "propfind", NULL);
1445 xmlDocSetRootElement (doc, root);
1446 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", NULL);
1448 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1449 node = xmlNewTextChild (node, nsdav, (xmlChar *) "owner", NULL);
1451 buf = xmlAllocOutputBuffer (NULL);
1452 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1453 xmlOutputBufferFlush (buf);
1455 soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1456 soup_message_headers_append (message->request_headers, "Depth", "0");
1458 soup_message_set_request (message,
1461 (gchar *) buf->buffer->content,
1464 /* Send the request now */
1465 send_and_handle_redirection (priv->session, message, NULL);
1467 /* Clean up the memory */
1468 xmlOutputBufferClose (buf);
1471 /* Check the result */
1472 if (message->status_code == 207 && parse_propfind_response (message, XPATH_OWNER_STATUS, XPATH_OWNER, &owner) && owner && *owner) {
1476 g_object_unref (message);
1478 /* owner is a full path to the user's URL, thus change it in
1479 calendar's uri when asking for schedule-outbox-URL */
1480 suri = soup_uri_new (priv->uri);
1481 soup_uri_set_path (suri, owner);
1483 owner = soup_uri_to_string (suri, FALSE);
1484 soup_uri_free (suri);
1486 message = soup_message_new ("PROPFIND", owner);
1487 if (message == NULL) {
1492 doc = xmlNewDoc ((xmlChar *) "1.0");
1493 root = xmlNewDocNode (doc, NULL, (xmlChar *) "propfind", NULL);
1494 xmlDocSetRootElement (doc, root);
1495 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", NULL);
1496 nscd = xmlNewNs (root, (xmlChar *) "urn:ietf:params:xml:ns:caldav", (xmlChar *) "C");
1498 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1499 node = xmlNewTextChild (node, nscd, (xmlChar *) "schedule-outbox-URL", NULL);
1501 buf = xmlAllocOutputBuffer (NULL);
1502 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1503 xmlOutputBufferFlush (buf);
1505 soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1506 soup_message_headers_append (message->request_headers, "Depth", "0");
1508 soup_message_set_request (message,
1511 (gchar *) buf->buffer->content,
1514 /* Send the request now */
1515 send_and_handle_redirection (priv->session, message, NULL);
1517 if (message->status_code == 207 && parse_propfind_response (message, XPATH_SCHEDULE_OUTBOX_URL_STATUS, XPATH_SCHEDULE_OUTBOX_URL, &priv->schedule_outbox_url)) {
1518 if (!*priv->schedule_outbox_url) {
1519 g_free (priv->schedule_outbox_url);
1520 priv->schedule_outbox_url = NULL;
1522 /* make it a full URI */
1523 suri = soup_uri_new (priv->uri);
1524 soup_uri_set_path (suri, priv->schedule_outbox_url);
1525 g_free (priv->schedule_outbox_url);
1526 priv->schedule_outbox_url = soup_uri_to_string (suri, FALSE);
1527 soup_uri_free (suri);
1531 /* Clean up the memory */
1532 xmlOutputBufferClose (buf);
1537 g_object_unref (message);
1541 return priv->schedule_outbox_url != NULL;
1544 /* ************************************************************************* */
1545 /* Synchronization foo */
1547 struct put_to_cache_data
1549 icalcomponent *icomp;
1555 put_to_cache_data_and_free_cb (gpointer pdata, gpointer user_data)
1557 struct put_to_cache_data *data = pdata;
1558 ECalBackendCalDAV *cbdav = user_data;
1560 g_return_if_fail (data != NULL);
1561 g_return_if_fail (data->icomp != NULL);
1563 put_comp_to_cache (cbdav, data->icomp, data->href, data->etag);
1565 icalcomponent_free (data->icomp);
1566 g_free (data->href);
1567 g_free (data->etag);
1571 static gboolean extract_timezones (ECalBackendCalDAV *cbdav, icalcomponent *icomp);
1574 synchronize_object (ECalBackendCalDAV *cbdav,
1575 CalDAVObject *object,
1576 ECalComponent *old_comp,
1579 GList **put_to_cache)
1581 ECalBackendCalDAVPrivate *priv;
1582 ECalBackendSyncStatus result;
1584 icalcomponent *icomp, *subcomp;
1585 icalcomponent_kind kind;
1589 result = caldav_server_get_object (cbdav, object);
1591 if (result != GNOME_Evolution_Calendar_Success)
1594 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1596 icomp = icalparser_parse_string (object->cdata);
1597 kind = icalcomponent_isa (icomp);
1598 bkend = E_CAL_BACKEND (cbdav);
1600 extract_timezones (cbdav, icomp);
1602 if (kind == ICAL_VCALENDAR_COMPONENT) {
1603 ECalComponent *comp;
1604 struct put_to_cache_data *data;
1606 kind = e_cal_backend_get_kind (bkend);
1609 for (subcomp = icalcomponent_get_first_component (icomp, kind);
1611 subcomp = icalcomponent_get_next_component (icomp, kind)) {
1614 comp = e_cal_component_new ();
1615 if (e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (subcomp))) {
1616 ecalcomp_set_href (comp, object->href);
1617 ecalcomp_set_etag (comp, object->etag);
1619 if (old_comp == NULL) {
1620 *created = g_list_prepend (*created, g_object_ref (comp));
1622 /* they will be in the opposite order in the list */
1623 *modified = g_list_prepend (*modified, g_object_ref (old_comp));
1624 *modified = g_list_prepend (*modified, g_object_ref (comp));
1628 g_object_unref (comp);
1631 data = g_new0 (struct put_to_cache_data, 1);
1632 data->icomp = icomp;
1633 data->href = g_strdup (object->href);
1634 data->etag = g_strdup (object->etag);
1636 *put_to_cache = g_list_prepend (*put_to_cache, data);
1639 icalcomponent_free (icomp);
1645 #define etags_match(_tag1, _tag2) ((_tag1 == _tag2) ? TRUE : \
1646 g_str_equal (_tag1 != NULL ? _tag1 : "", \
1647 _tag2 != NULL ? _tag2 : ""))
1650 synchronize_cache (ECalBackendCalDAV *cbdav)
1652 ECalBackendCalDAVPrivate *priv;
1654 ECalBackendCache *bcache;
1655 CalDAVObject *sobjs;
1656 CalDAVObject *object;
1658 GList *cobjs, *created = NULL, *modified = NULL, *put_to_cache = NULL;
1664 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1665 bkend = E_CAL_BACKEND (cbdav);
1666 bcache = priv->cache;
1670 if (!check_calendar_changed_on_server (cbdav)) {
1671 /* no changes on the server, no update required */
1675 res = caldav_server_list_objects (cbdav, &sobjs, &len);
1680 hindex = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1681 cobjs = e_cal_backend_cache_get_components (bcache);
1683 /* build up a index for the href entry */
1684 for (citer = cobjs; citer; citer = g_list_next (citer)) {
1685 ECalComponent *ccomp = E_CAL_COMPONENT (citer->data);
1688 href = ecalcomp_get_href (ccomp);
1691 g_warning ("href of object NULL :(");
1695 /* prefer master object from a detached instance */
1696 if (g_hash_table_lookup (hindex, href) && e_cal_component_is_instance (ccomp)) {
1701 g_hash_table_insert (hindex, (gpointer) href, ccomp);
1704 /* see if we have to update or add some objects */
1705 for (i = 0, object = sobjs; i < len; i++, object++) {
1706 ECalComponent *ccomp;
1709 if (object->status != 200) {
1710 /* just continue here, so that the object
1711 * doesnt get removed from the cobjs list
1712 * - therefore it will be removed */
1717 ccomp = g_hash_table_lookup (hindex, object->href);
1719 if (ccomp != NULL) {
1720 etag = ecalcomp_get_etag (ccomp);
1723 if (!etag || !etags_match (etag, object->etag)) {
1724 res = synchronize_object (cbdav, object, ccomp, &created, &modified, &put_to_cache);
1728 cobjs = g_list_remove (cobjs, ccomp);
1730 g_object_unref (ccomp);
1733 caldav_object_free (object, FALSE);
1737 /* remove old (not on server anymore) items from cache... */
1738 for (citer = cobjs; citer; citer = g_list_next (citer)) {
1739 ECalComponent *comp = E_CAL_COMPONENT (citer->data);
1741 /* keep detached instances in a cache, they will be removed with the master object */
1742 if (!e_cal_component_is_instance (comp)) {
1743 const gchar *uid = NULL;
1745 e_cal_component_get_uid (comp, &uid);
1747 if (remove_comp_from_cache (cbdav, uid, NULL)) {
1748 gchar *str = e_cal_component_get_as_string (comp);
1749 ECalComponentId *id = e_cal_component_get_id (comp);
1751 e_cal_backend_notify_object_removed (E_CAL_BACKEND (cbdav), id, str, NULL);
1752 e_cal_component_free_id (id);
1757 g_object_unref (comp);
1760 /* ... then update cache with new objects... */
1761 g_list_foreach (put_to_cache, put_to_cache_data_and_free_cb, cbdav);
1763 /* ... then notify created ... */
1764 for (citer = created; citer; citer = citer->next) {
1765 ECalComponent *comp = citer->data;
1766 gchar *comp_str = e_cal_component_get_as_string (comp);
1768 e_cal_backend_notify_object_created (bkend, comp_str);
1771 g_object_unref (comp);
1774 /* ... and modified components */
1775 for (citer = modified; citer; citer = citer->next) {
1776 ECalComponent *comp, *old_comp;
1777 gchar *new_str, *old_str;
1779 /* always even number of items in the 'modified' list */
1781 citer = citer->next;
1782 old_comp = citer->data;
1784 new_str = e_cal_component_get_as_string (comp);
1785 old_str = e_cal_component_get_as_string (old_comp);
1787 e_cal_backend_notify_object_modified (bkend, old_str, new_str);
1791 g_object_unref (comp);
1792 g_object_unref (old_comp);
1795 g_hash_table_destroy (hindex);
1796 g_list_free (cobjs);
1797 g_list_free (created);
1798 g_list_free (modified);
1799 g_list_free (put_to_cache);
1802 /* ************************************************************************* */
1804 synch_slave_loop (gpointer data)
1806 ECalBackendCalDAVPrivate *priv;
1807 ECalBackendCalDAV *cbdav;
1809 cbdav = E_CAL_BACKEND_CALDAV (data);
1810 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1812 g_mutex_lock (priv->lock);
1814 while (priv->slave_cmd != SLAVE_SHOULD_DIE) {
1815 GTimeVal alarm_clock;
1816 if (priv->slave_cmd == SLAVE_SHOULD_SLEEP) {
1817 /* just sleep until we get woken up again */
1818 g_cond_wait (priv->cond, priv->lock);
1820 /* check if we should die, work or sleep again */
1824 /* Ok here we go, do some real work
1825 * Synch it baby one more time ...
1827 synchronize_cache (cbdav);
1829 /* puhh that was hard, get some rest :) */
1830 g_get_current_time (&alarm_clock);
1831 alarm_clock.tv_sec += priv->refresh_time.tv_sec;
1832 g_cond_timed_wait (priv->cond,
1838 /* signal we are done */
1839 g_cond_signal (priv->slave_gone_cond);
1841 priv->synch_slave = NULL;
1843 /* we got killed ... */
1844 g_mutex_unlock (priv->lock);
1848 /* ************************************************************************* */
1849 /* ********** ECalBackendSync virtual function implementation ************* */
1851 static ECalBackendSyncStatus
1852 caldav_is_read_only (ECalBackendSync *backend,
1854 gboolean *read_only)
1856 ECalBackendCalDAV *cbdav;
1857 ECalBackendCalDAVPrivate *priv;
1859 cbdav = E_CAL_BACKEND_CALDAV (backend);
1860 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1862 /* no write support in offline mode yet! */
1863 if (priv->mode == CAL_MODE_LOCAL) {
1866 *read_only = priv->read_only;
1869 return GNOME_Evolution_Calendar_Success;
1873 static ECalBackendSyncStatus
1874 caldav_get_cal_address (ECalBackendSync *backend,
1879 return GNOME_Evolution_Calendar_Success;
1884 static ECalBackendSyncStatus
1885 caldav_get_ldap_attribute (ECalBackendSync *backend,
1890 return GNOME_Evolution_Calendar_Success;
1893 static ECalBackendSyncStatus
1894 caldav_get_alarm_email_address (ECalBackendSync *backend,
1899 return GNOME_Evolution_Calendar_Success;
1902 static ECalBackendSyncStatus
1903 caldav_get_static_capabilities (ECalBackendSync *backend,
1905 gchar **capabilities)
1907 *capabilities = g_strdup (CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS ","
1908 CAL_STATIC_CAPABILITY_NO_THISANDFUTURE ","
1909 CAL_STATIC_CAPABILITY_NO_THISANDPRIOR);
1911 return GNOME_Evolution_Calendar_Success;
1914 static ECalBackendSyncStatus
1915 initialize_backend (ECalBackendCalDAV *cbdav)
1917 ECalBackendSyncStatus result;
1918 ECalBackendCalDAVPrivate *priv;
1920 const gchar *os_val;
1923 const gchar *refresh;
1925 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1927 result = GNOME_Evolution_Calendar_Success;
1928 source = e_cal_backend_get_source (E_CAL_BACKEND (cbdav));
1930 os_val = e_source_get_property (source, "offline_sync");
1932 if (!os_val || !g_str_equal (os_val, "1")) {
1933 priv->do_offline = FALSE;
1936 os_val = e_source_get_property (source, "auth");
1937 priv->need_auth = os_val != NULL;
1939 os_val = e_source_get_property(source, "ssl");
1940 uri = e_cal_backend_get_uri (E_CAL_BACKEND (cbdav));
1944 if (g_str_has_prefix (uri, "caldav://")) {
1947 if (os_val && os_val[0] == '1') {
1953 priv->uri = g_strconcat (proto, uri + 9, NULL);
1955 priv->uri = g_strdup (uri);
1959 SoupURI *suri = soup_uri_new (priv->uri);
1961 /* properly encode uri */
1962 if (suri && suri->path) {
1963 gchar *tmp = soup_uri_encode (suri->path, NULL);
1964 gchar *path = soup_uri_normalize (tmp, "/");
1966 soup_uri_set_path (suri, path);
1972 priv->uri = soup_uri_to_string (suri, FALSE);
1975 soup_uri_free (suri);
1978 /* remove trailing slashes... */
1979 len = strlen (priv->uri);
1981 if (priv->uri[len] == '/') {
1982 priv->uri[len] = '\0';
1988 /* ...and append exactly one slash */
1989 if (priv->uri && *priv->uri) {
1990 gchar *tmp = priv->uri;
1992 priv->uri = g_strconcat (priv->uri, "/", NULL);
1997 if (priv->cache == NULL) {
1998 ECalSourceType source_type;
2000 switch (e_cal_backend_get_kind (E_CAL_BACKEND (cbdav))) {
2002 case ICAL_VEVENT_COMPONENT:
2003 source_type = E_CAL_SOURCE_TYPE_EVENT;
2005 case ICAL_VTODO_COMPONENT:
2006 source_type = E_CAL_SOURCE_TYPE_TODO;
2008 case ICAL_VJOURNAL_COMPONENT:
2009 source_type = E_CAL_SOURCE_TYPE_JOURNAL;
2013 priv->cache = e_cal_backend_cache_new (priv->uri, source_type);
2015 if (priv->cache == NULL) {
2016 result = GNOME_Evolution_Calendar_OtherError;
2022 refresh = e_source_get_property (source, "refresh");
2023 priv->refresh_time.tv_sec = (refresh && atoi (refresh) > 0) ? (60 * atoi (refresh)) : (DEFAULT_REFRESH_TIME);
2025 if (!priv->synch_slave) {
2028 priv->slave_cmd = SLAVE_SHOULD_SLEEP;
2029 slave = g_thread_create (synch_slave_loop, cbdav, FALSE, NULL);
2031 if (slave == NULL) {
2032 g_warning ("Could not create synch slave");
2033 result = GNOME_Evolution_Calendar_OtherError;
2036 priv->synch_slave = slave;
2043 proxy_settings_changed (EProxy *proxy, gpointer user_data)
2045 SoupURI *proxy_uri = NULL;
2046 ECalBackendCalDAVPrivate *priv = (ECalBackendCalDAVPrivate *) user_data;
2048 if (!priv || !priv->uri || !priv->session)
2051 /* use proxy if necessary */
2052 if (e_proxy_require_proxy_for_uri (proxy, priv->uri)) {
2053 proxy_uri = e_proxy_peek_uri_for (proxy, priv->uri);
2056 g_object_set (priv->session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL);
2059 static ECalBackendSyncStatus
2060 caldav_do_open (ECalBackendSync *backend,
2062 gboolean only_if_exists,
2063 const gchar *username,
2064 const gchar *password)
2066 ECalBackendCalDAV *cbdav;
2067 ECalBackendCalDAVPrivate *priv;
2068 ECalBackendSyncStatus status;
2070 cbdav = E_CAL_BACKEND_CALDAV (backend);
2071 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2073 status = GNOME_Evolution_Calendar_Success;
2075 g_mutex_lock (priv->lock);
2077 /* let it decide the 'getctag' extension availability again */
2078 priv->ctag_supported = TRUE;
2080 if (!priv->loaded) {
2081 status = initialize_backend (cbdav);
2084 if (status != GNOME_Evolution_Calendar_Success) {
2085 g_mutex_unlock (priv->lock);
2089 if (priv->need_auth) {
2090 if ((username == NULL || password == NULL)) {
2091 g_mutex_unlock (priv->lock);
2092 return GNOME_Evolution_Calendar_AuthenticationRequired;
2095 g_free (priv->username);
2096 priv->username = g_strdup (username);
2097 g_free (priv->password);
2098 priv->password = g_strdup (password);
2101 if (!priv->do_offline && priv->mode == CAL_MODE_LOCAL) {
2102 g_mutex_unlock (priv->lock);
2103 return GNOME_Evolution_Calendar_RepositoryOffline;
2106 priv->loaded = TRUE;
2108 if (priv->mode == CAL_MODE_REMOTE) {
2109 /* set forward proxy */
2110 proxy_settings_changed (priv->proxy, priv);
2112 status = caldav_server_open_calendar (cbdav);
2114 if (status == GNOME_Evolution_Calendar_Success) {
2115 priv->slave_cmd = SLAVE_SHOULD_WORK;
2116 g_cond_signal (priv->cond);
2119 priv->read_only = TRUE;
2122 g_mutex_unlock (priv->lock);
2127 static ECalBackendSyncStatus
2128 caldav_remove (ECalBackendSync *backend,
2131 ECalBackendCalDAV *cbdav;
2132 ECalBackendCalDAVPrivate *priv;
2133 ECalBackendSyncStatus status;
2136 cbdav = E_CAL_BACKEND_CALDAV (backend);
2137 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2139 g_mutex_lock (priv->lock);
2141 if (!priv->loaded) {
2142 g_mutex_unlock (priv->lock);
2143 return GNOME_Evolution_Calendar_Success;
2146 status = check_state (cbdav, &online);
2148 /* lie here a bit, but otherwise the calendar will not be removed, even it should */
2149 if (status != GNOME_Evolution_Calendar_Success)
2150 g_print (G_STRLOC ": %s", e_cal_backend_status_to_string (status));
2152 e_file_cache_remove (E_FILE_CACHE (priv->cache));
2154 priv->loaded = FALSE;
2155 priv->slave_cmd = SLAVE_SHOULD_DIE;
2157 if (priv->synch_slave) {
2158 g_cond_signal (priv->cond);
2160 /* wait until the slave died */
2161 g_cond_wait (priv->slave_gone_cond, priv->lock);
2164 g_mutex_unlock (priv->lock);
2166 return GNOME_Evolution_Calendar_Success;
2170 remove_comp_from_cache_cb (gpointer value, gpointer user_data)
2172 ECalComponent *comp = value;
2173 ECalBackendCache *cache = user_data;
2174 ECalComponentId *id;
2176 g_return_if_fail (comp != NULL);
2177 g_return_if_fail (cache != NULL);
2179 id = e_cal_component_get_id (comp);
2180 g_return_if_fail (id != NULL);
2182 e_cal_backend_cache_remove_component (cache, id->uid, id->rid);
2183 e_cal_component_free_id (id);
2187 remove_comp_from_cache (ECalBackendCalDAV *cbdav, const gchar *uid, const gchar *rid)
2189 ECalBackendCalDAVPrivate *priv;
2190 gboolean res = FALSE;
2192 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2194 if (!rid || !*rid) {
2195 /* get with detached instances */
2196 GSList *objects = e_cal_backend_cache_get_components_by_uid (priv->cache, uid);
2199 g_slist_foreach (objects, (GFunc)remove_comp_from_cache_cb, priv->cache);
2200 g_slist_foreach (objects, (GFunc)g_object_unref, NULL);
2201 g_slist_free (objects);
2206 res = e_cal_backend_cache_remove_component (priv->cache, uid, rid);
2213 add_detached_recur_to_vcalendar_cb (gpointer value, gpointer user_data)
2215 icalcomponent *recurrence = e_cal_component_get_icalcomponent (value);
2216 icalcomponent *vcalendar = user_data;
2218 icalcomponent_add_component (
2220 icalcomponent_new_clone (recurrence));
2224 sort_master_first (gconstpointer a, gconstpointer b)
2226 icalcomponent *ca, *cb;
2228 ca = e_cal_component_get_icalcomponent ((ECalComponent *)a);
2229 cb = e_cal_component_get_icalcomponent ((ECalComponent *)b);
2240 return icaltime_compare (icalcomponent_get_recurrenceid (ca), icalcomponent_get_recurrenceid (cb));
2243 /* Returns new icalcomponent, with all detached instances stored in a cache.
2244 The cache lock should be locked when called this function.
2246 static icalcomponent *
2247 get_comp_from_cache (ECalBackendCalDAV *cbdav, const gchar *uid, const gchar *rid, gchar **href, gchar **etag)
2249 ECalBackendCalDAVPrivate *priv;
2250 icalcomponent *icalcomp = NULL;
2252 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2254 if (rid == NULL || !*rid) {
2255 /* get with detached instances */
2256 GSList *objects = e_cal_backend_cache_get_components_by_uid (priv->cache, uid);
2262 if (g_slist_length (objects) == 1) {
2263 ECalComponent *comp = objects->data;
2265 /* will be unreffed a bit later */
2267 icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
2269 /* if we have detached recurrences, return a VCALENDAR */
2270 icalcomp = e_cal_util_new_top_level ();
2272 objects = g_slist_sort (objects, sort_master_first);
2274 /* add all detached recurrences and the master object */
2275 g_slist_foreach (objects, add_detached_recur_to_vcalendar_cb, icalcomp);
2278 /* every component has set same href and etag, thus it doesn't matter where it will be read */
2280 *href = ecalcomp_get_href (objects->data);
2282 *etag = ecalcomp_get_etag (objects->data);
2284 g_slist_foreach (objects, (GFunc)g_object_unref, NULL);
2285 g_slist_free (objects);
2287 /* get the exact object */
2288 ECalComponent *comp = e_cal_backend_cache_get_component (priv->cache, uid, rid);
2291 icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
2293 *href = ecalcomp_get_href (comp);
2295 *etag = ecalcomp_get_etag (comp);
2296 g_object_unref (comp);
2304 put_comp_to_cache (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp, const gchar *href, const gchar *etag)
2306 ECalBackendCalDAVPrivate *priv;
2307 icalcomponent_kind my_kind;
2308 ECalComponent *comp;
2309 gboolean res = FALSE;
2311 g_return_val_if_fail (cbdav != NULL, FALSE);
2312 g_return_val_if_fail (icalcomp != NULL, FALSE);
2314 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2316 my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
2317 comp = e_cal_component_new ();
2319 if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
2320 icalcomponent *subcomp;
2322 /* remove all old components from the cache first */
2323 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
2325 subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
2326 remove_comp_from_cache (cbdav, icalcomponent_get_uid (subcomp), NULL);
2329 /* then put new. It's because some detached instances could be removed on the server. */
2330 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
2332 subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
2333 /* because reusing the same comp doesn't clear recur_id member properly */
2334 g_object_unref (comp);
2335 comp = e_cal_component_new ();
2337 if (e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (subcomp))) {
2339 ecalcomp_set_href (comp, href);
2341 ecalcomp_set_etag (comp, etag);
2343 if (e_cal_backend_cache_put_component (priv->cache, comp))
2347 } else if (icalcomponent_isa (icalcomp) == my_kind) {
2348 remove_comp_from_cache (cbdav, icalcomponent_get_uid (icalcomp), NULL);
2350 if (e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (icalcomp))) {
2352 ecalcomp_set_href (comp, href);
2354 ecalcomp_set_etag (comp, etag);
2356 res = e_cal_backend_cache_put_component (priv->cache, comp);
2360 g_object_unref (comp);
2366 remove_property (gpointer prop, gpointer icomp)
2368 icalcomponent_remove_property (icomp, prop);
2369 icalproperty_free (prop);
2373 strip_x_evolution_caldav (icalcomponent *icomp)
2376 GSList *to_remove = NULL;
2378 g_return_if_fail (icomp != NULL);
2379 g_return_if_fail (icalcomponent_isa (icomp) != ICAL_VCALENDAR_COMPONENT);
2381 for (prop = icalcomponent_get_first_property (icomp, ICAL_X_PROPERTY);
2383 prop = icalcomponent_get_next_property (icomp, ICAL_X_PROPERTY)) {
2384 if (g_str_has_prefix (icalproperty_get_x_name (prop), X_E_CALDAV)) {
2385 to_remove = g_slist_prepend (to_remove, prop);
2389 g_slist_foreach (to_remove, remove_property, icomp);
2390 g_slist_free (to_remove);
2393 /* callback for icalcomponent_foreach_tzid */
2395 ECalBackendCache *cache;
2396 icalcomponent *vcal_comp;
2397 icalcomponent *icalcomp;
2401 add_timezone_cb (icalparameter *param, gpointer data)
2405 icalcomponent *vtz_comp;
2406 ForeachTzidData *f_data = (ForeachTzidData *) data;
2408 tzid = icalparameter_get_tzid (param);
2412 tz = icalcomponent_get_timezone (f_data->vcal_comp, tzid);
2416 tz = icalcomponent_get_timezone (f_data->icalcomp, tzid);
2418 tz = icaltimezone_get_builtin_timezone_from_tzid (tzid);
2420 tz = (icaltimezone *) e_cal_backend_cache_get_timezone (f_data->cache, tzid);
2425 vtz_comp = icaltimezone_get_component (tz);
2429 icalcomponent_add_component (f_data->vcal_comp,
2430 icalcomponent_new_clone (vtz_comp));
2434 add_timezones_from_component (ECalBackendCalDAV *cbdav, icalcomponent *vcal_comp, icalcomponent *icalcomp)
2436 ForeachTzidData f_data;
2437 ECalBackendCalDAVPrivate *priv;
2439 g_return_if_fail (cbdav != NULL);
2440 g_return_if_fail (vcal_comp != NULL);
2441 g_return_if_fail (icalcomp != NULL);
2443 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2445 f_data.cache = priv->cache;
2446 f_data.vcal_comp = vcal_comp;
2447 f_data.icalcomp = icalcomp;
2449 icalcomponent_foreach_tzid (icalcomp, add_timezone_cb, &f_data);
2452 /* also removes X-EVOLUTION-CALDAV from all the components */
2454 pack_cobj (ECalBackendCalDAV *cbdav, icalcomponent *icomp)
2456 ECalBackendCalDAVPrivate *priv;
2457 icalcomponent *calcomp;
2460 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2462 if (icalcomponent_isa (icomp) != ICAL_VCALENDAR_COMPONENT) {
2463 icalcomponent *cclone;
2465 calcomp = e_cal_util_new_top_level ();
2467 cclone = icalcomponent_new_clone (icomp);
2468 strip_x_evolution_caldav (cclone);
2469 icalcomponent_add_component (calcomp, cclone);
2470 add_timezones_from_component (cbdav, calcomp, cclone);
2472 icalcomponent *subcomp;
2473 icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
2475 calcomp = icalcomponent_new_clone (icomp);
2476 for (subcomp = icalcomponent_get_first_component (calcomp, my_kind);
2478 subcomp = icalcomponent_get_next_component (calcomp, my_kind)) {
2479 strip_x_evolution_caldav (subcomp);
2480 add_timezones_from_component (cbdav, calcomp, subcomp);
2484 objstr = icalcomponent_as_ical_string_r (calcomp);
2485 icalcomponent_free (calcomp);
2494 sanitize_component (ECalBackend *cb, ECalComponent *comp)
2496 ECalComponentDateTime dt;
2497 icaltimezone *zone, *default_zone;
2499 /* Check dtstart, dtend and due's timezone, and convert it to local
2500 * default timezone if the timezone is not in our builtin timezone
2502 e_cal_component_get_dtstart (comp, &dt);
2503 if (dt.value && dt.tzid) {
2504 zone = caldav_internal_get_timezone (cb, dt.tzid);
2506 default_zone = caldav_internal_get_default_timezone (cb);
2507 g_free ((gchar *)dt.tzid);
2508 dt.tzid = g_strdup (icaltimezone_get_tzid (default_zone));
2509 e_cal_component_set_dtstart (comp, &dt);
2512 e_cal_component_free_datetime (&dt);
2514 e_cal_component_get_dtend (comp, &dt);
2515 if (dt.value && dt.tzid) {
2516 zone = caldav_internal_get_timezone (cb, dt.tzid);
2518 default_zone = caldav_internal_get_default_timezone (cb);
2519 g_free ((gchar *)dt.tzid);
2520 dt.tzid = g_strdup (icaltimezone_get_tzid (default_zone));
2521 e_cal_component_set_dtend (comp, &dt);
2524 e_cal_component_free_datetime (&dt);
2526 e_cal_component_get_due (comp, &dt);
2527 if (dt.value && dt.tzid) {
2528 zone = caldav_internal_get_timezone (cb, dt.tzid);
2530 default_zone = caldav_internal_get_default_timezone (cb);
2531 g_free ((gchar *)dt.tzid);
2532 dt.tzid = g_strdup (icaltimezone_get_tzid (default_zone));
2533 e_cal_component_set_due (comp, &dt);
2536 e_cal_component_free_datetime (&dt);
2537 e_cal_component_abort_sequence (comp);
2541 cache_contains (ECalBackendCalDAV *cbdav, const gchar *uid, const gchar *rid)
2543 ECalBackendCalDAVPrivate *priv;
2545 ECalComponent *comp;
2547 g_return_val_if_fail (cbdav != NULL, FALSE);
2548 g_return_val_if_fail (uid != NULL, FALSE);
2550 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2551 g_return_val_if_fail (priv != NULL && priv->cache != NULL, FALSE);
2553 comp = e_cal_backend_cache_get_component (priv->cache, uid, rid);
2557 g_object_unref (comp);
2562 /* Returns subcomponent of icalcomp, which is a master object, or icalcomp itself, if it's not a VCALENDAR;
2563 Do not free returned pointer, it'll be freed together with the icalcomp.
2565 static icalcomponent *
2566 get_master_comp (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp)
2568 icalcomponent *master = icalcomp;
2570 g_return_val_if_fail (cbdav != NULL, NULL);
2571 g_return_val_if_fail (icalcomp != NULL, NULL);
2573 if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
2574 icalcomponent *subcomp;
2575 icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
2579 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
2581 subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
2582 struct icaltimetype sub_rid = icalcomponent_get_recurrenceid (subcomp);
2584 if (icaltime_is_null_time (sub_rid)) {
2595 remove_instance (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp, struct icaltimetype rid, CalObjModType mod, gboolean also_exdate)
2597 icalcomponent *master = icalcomp;
2598 gboolean res = FALSE;
2600 g_return_val_if_fail (icalcomp != NULL, res);
2601 g_return_val_if_fail (!icaltime_is_null_time (rid), res);
2603 /* remove an instance only */
2604 if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
2605 icalcomponent *subcomp;
2606 icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
2608 gboolean start_first = FALSE;
2612 /* remove old instance first */
2613 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
2615 subcomp = start_first ? icalcomponent_get_first_component (icalcomp, my_kind) : icalcomponent_get_next_component (icalcomp, my_kind)) {
2616 struct icaltimetype sub_rid = icalcomponent_get_recurrenceid (subcomp);
2618 start_first = FALSE;
2620 if (icaltime_is_null_time (sub_rid)) {
2623 } else if (icaltime_compare (sub_rid, rid) == 0) {
2624 icalcomponent_remove_component (icalcomp, subcomp);
2625 icalcomponent_free (subcomp);
2629 /* either no master or master not as the first component, thus rescan */
2638 /* whether left at least one instance or a master object */
2644 if (master && also_exdate) {
2645 e_cal_util_remove_instances (master, rid, mod);
2651 static icalcomponent *
2652 replace_master (ECalBackendCalDAV *cbdav, icalcomponent *old_comp, icalcomponent *new_master)
2654 icalcomponent *old_master;
2655 if (icalcomponent_isa (old_comp) != ICAL_VCALENDAR_COMPONENT) {
2656 icalcomponent_free (old_comp);
2660 old_master = get_master_comp (cbdav, old_comp);
2662 /* no master, strange */
2663 icalcomponent_free (new_master);
2665 icalcomponent_remove_component (old_comp, old_master);
2666 icalcomponent_free (old_master);
2668 icalcomponent_add_component (old_comp, new_master);
2674 /* priv->lock is supposed to be locked already, when calling this function */
2675 static ECalBackendSyncStatus
2676 do_create_object (ECalBackendCalDAV *cbdav, gchar **calobj, gchar **uid)
2678 ECalBackendCalDAVPrivate *priv;
2679 ECalBackendSyncStatus status;
2680 ECalComponent *comp;
2682 struct icaltimetype current;
2683 icalcomponent *icalcomp;
2684 const gchar *comp_uid;
2686 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2688 status = check_state (cbdav, &online);
2690 if (status != GNOME_Evolution_Calendar_Success) {
2694 comp = e_cal_component_new_from_string (*calobj);
2697 return GNOME_Evolution_Calendar_InvalidObject;
2700 icalcomp = e_cal_component_get_icalcomponent (comp);
2701 if (icalcomp == NULL) {
2702 g_object_unref (comp);
2703 return GNOME_Evolution_Calendar_InvalidObject;
2706 comp_uid = icalcomponent_get_uid (icalcomp);
2710 new_uid = e_cal_component_gen_uid ();
2712 g_object_unref (comp);
2713 return GNOME_Evolution_Calendar_InvalidObject;
2716 icalcomponent_set_uid (icalcomp, new_uid);
2717 comp_uid = icalcomponent_get_uid (icalcomp);
2722 /* check the object is not in our cache */
2723 if (cache_contains (cbdav, comp_uid, NULL)) {
2724 g_object_unref (comp);
2725 return GNOME_Evolution_Calendar_ObjectIdAlreadyExists;
2728 /* Set the created and last modified times on the component */
2729 current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
2730 e_cal_component_set_created (comp, ¤t);
2731 e_cal_component_set_last_modified (comp, ¤t);
2733 /* sanitize the component*/
2734 sanitize_component ((ECalBackend *)cbdav, comp);
2737 CalDAVObject object;
2739 object.href = ecalcomp_gen_href (comp);
2741 object.cdata = pack_cobj (cbdav, icalcomp);
2743 status = caldav_server_put_object (cbdav, &object, icalcomp);
2745 caldav_object_free (&object, FALSE);
2747 /* mark component as out of synch */
2748 /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_CREATED); */
2751 if (status == GNOME_Evolution_Calendar_Success) {
2753 *uid = g_strdup (comp_uid);
2755 icalcomp = get_comp_from_cache (cbdav, comp_uid, NULL, NULL, NULL);
2758 icalcomponent *master = get_master_comp (cbdav, icalcomp);
2761 *calobj = e_cal_component_get_as_string (comp);
2763 *calobj = icalcomponent_as_ical_string_r (master);
2765 icalcomponent_free (icalcomp);
2767 *calobj = e_cal_component_get_as_string (comp);
2771 g_object_unref (comp);
2776 /* priv->lock is supposed to be locked already, when calling this function */
2777 static ECalBackendSyncStatus
2778 do_modify_object (ECalBackendCalDAV *cbdav, const gchar *calobj, CalObjModType mod, gchar **old_object, gchar **new_object)
2780 ECalBackendCalDAVPrivate *priv;
2781 ECalBackendSyncStatus status;
2782 ECalComponent *comp;
2783 icalcomponent *cache_comp;
2785 ECalComponentId *id;
2786 struct icaltimetype current;
2787 gchar *href = NULL, *etag = NULL;
2789 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2794 status = check_state (cbdav, &online);
2795 if (status != GNOME_Evolution_Calendar_Success) {
2799 comp = e_cal_component_new_from_string (calobj);
2802 return GNOME_Evolution_Calendar_InvalidObject;
2805 if (!e_cal_component_get_icalcomponent (comp) ||
2806 icalcomponent_isa (e_cal_component_get_icalcomponent (comp)) != e_cal_backend_get_kind (E_CAL_BACKEND (cbdav))) {
2807 g_object_unref (comp);
2808 return GNOME_Evolution_Calendar_InvalidObject;
2811 /* Set the last modified time on the component */
2812 current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
2813 e_cal_component_set_last_modified (comp, ¤t);
2815 /* sanitize the component */
2816 sanitize_component ((ECalBackend *)cbdav, comp);
2818 id = e_cal_component_get_id (comp);
2819 g_return_val_if_fail (id != NULL, GNOME_Evolution_Calendar_OtherError);
2821 /* fetch full component from cache, it will be pushed to the server */
2822 cache_comp = get_comp_from_cache (cbdav, id->uid, NULL, &href, &etag);
2824 if (cache_comp == NULL) {
2825 e_cal_component_free_id (id);
2826 g_object_unref (comp);
2829 return GNOME_Evolution_Calendar_ObjectNotFound;
2833 /* mark component as out of synch */
2834 /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_MODIFIED);*/
2840 if (e_cal_component_is_instance (comp)) {
2841 /* set detached instance as the old object, if any */
2842 ECalComponent *old_instance = e_cal_backend_cache_get_component (priv->cache, id->uid, id->rid);
2845 *old_object = e_cal_component_get_as_string (old_instance);
2846 g_object_unref (old_instance);
2851 icalcomponent *master = get_master_comp (cbdav, cache_comp);
2854 /* set full component as the old object */
2855 *old_object = icalcomponent_as_ical_string_r (master);
2861 case CALOBJ_MOD_THIS:
2862 if (e_cal_component_is_instance (comp)) {
2863 icalcomponent *new_comp = e_cal_component_get_icalcomponent (comp);
2865 /* new object is only this instance */
2867 *new_object = e_cal_component_get_as_string (comp);
2869 /* add the detached instance */
2870 if (icalcomponent_isa (cache_comp) == ICAL_VCALENDAR_COMPONENT) {
2871 /* do not modify the EXDATE, as the component will be put back */
2872 remove_instance (cbdav, cache_comp, icalcomponent_get_recurrenceid (new_comp), mod, FALSE);
2874 /* this is only a master object, thus make is a VCALENDAR component */
2875 icalcomponent *icomp;
2877 icomp = e_cal_util_new_top_level();
2878 icalcomponent_add_component (icomp, cache_comp);
2880 /* no need to free the cache_comp, as it is inside icomp */
2884 /* add the detached instance finally */
2885 icalcomponent_add_component (cache_comp, icalcomponent_new_clone (new_comp));
2887 cache_comp = replace_master (cbdav, cache_comp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
2890 case CALOBJ_MOD_ALL:
2891 cache_comp = replace_master (cbdav, cache_comp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
2893 case CALOBJ_MOD_THISANDPRIOR:
2894 case CALOBJ_MOD_THISANDFUTURE:
2899 CalDAVObject object;
2903 object.cdata = pack_cobj (cbdav, cache_comp);
2905 status = caldav_server_put_object (cbdav, &object, cache_comp);
2907 caldav_object_free (&object, FALSE);
2911 /* mark component as out of synch */
2912 /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_MODIFIED);*/
2915 if (status == GNOME_Evolution_Calendar_Success) {
2916 if (new_object && !*new_object) {
2917 icalcomponent *master = get_master_comp (cbdav, cache_comp);
2920 *new_object = icalcomponent_as_ical_string_r (master);
2924 e_cal_component_free_id (id);
2925 icalcomponent_free (cache_comp);
2926 g_object_unref (comp);
2933 /* priv->lock is supposed to be locked already, when calling this function */
2934 static ECalBackendSyncStatus
2935 do_remove_object (ECalBackendCalDAV *cbdav, const gchar *uid, const gchar *rid, CalObjModType mod, gchar **old_object, gchar **object)
2937 ECalBackendCalDAVPrivate *priv;
2938 ECalBackendSyncStatus status;
2939 icalcomponent *cache_comp;
2941 gchar *href = NULL, *etag = NULL;
2943 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2948 status = check_state (cbdav, &online);
2949 if (status != GNOME_Evolution_Calendar_Success) {
2953 cache_comp = get_comp_from_cache (cbdav, uid, NULL, &href, &etag);
2955 if (cache_comp == NULL) {
2956 return GNOME_Evolution_Calendar_ObjectNotFound;
2960 ECalComponent *old = e_cal_backend_cache_get_component (priv->cache, uid, rid);
2963 *old_object = e_cal_component_get_as_string (old);
2964 g_object_unref (old);
2966 icalcomponent *master = get_master_comp (cbdav, cache_comp);
2969 *old_object = icalcomponent_as_ical_string_r (master);
2974 case CALOBJ_MOD_THIS:
2976 /* remove one instance from the component */
2977 if (remove_instance (cbdav, cache_comp, icaltime_from_string (rid), mod, TRUE)) {
2979 icalcomponent *master = get_master_comp (cbdav, cache_comp);
2982 *object = icalcomponent_as_ical_string_r (master);
2985 /* this was the last instance, thus delete whole component */
2987 remove_comp_from_cache (cbdav, uid, NULL);
2990 /* remove whole object */
2991 remove_comp_from_cache (cbdav, uid, NULL);
2994 case CALOBJ_MOD_ALL:
2995 remove_comp_from_cache (cbdav, uid, NULL);
2997 case CALOBJ_MOD_THISANDPRIOR:
2998 case CALOBJ_MOD_THISANDFUTURE:
3003 CalDAVObject caldav_object;
3005 caldav_object.href = href;
3006 caldav_object.etag = etag;
3007 caldav_object.cdata = NULL;
3009 if (mod == CALOBJ_MOD_THIS && rid && *rid) {
3010 caldav_object.cdata = pack_cobj (cbdav, cache_comp);
3012 status = caldav_server_put_object (cbdav, &caldav_object, cache_comp);
3014 status = caldav_server_delete_object (cbdav, &caldav_object);
3016 caldav_object_free (&caldav_object, FALSE);
3020 /* mark component as out of synch */
3021 /*if (mod == CALOBJ_MOD_THIS && rid && *rid)
3022 ecalcomp_set_synch_state (cache_comp_master, ECALCOMP_LOCALLY_MODIFIED);
3024 ecalcomp_set_synch_state (cache_comp_master, ECALCOMP_LOCALLY_DELETED);*/
3027 icalcomponent_free (cache_comp);
3034 static ECalBackendSyncStatus
3035 caldav_create_object (ECalBackendSync *backend, EDataCal *cal, gchar **calobj, gchar **uid)
3037 ECalBackendCalDAV *cbdav;
3038 ECalBackendCalDAVPrivate *priv;
3039 ECalBackendSyncStatus status;
3041 cbdav = E_CAL_BACKEND_CALDAV (backend);
3042 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3044 g_mutex_lock (priv->lock);
3045 status = do_create_object (cbdav, calobj, uid);
3046 g_mutex_unlock (priv->lock);
3051 static ECalBackendSyncStatus
3052 caldav_modify_object (ECalBackendSync *backend, EDataCal *cal, const gchar *calobj, CalObjModType mod, gchar **old_object, gchar **new_object)
3054 ECalBackendCalDAV *cbdav;
3055 ECalBackendCalDAVPrivate *priv;
3056 ECalBackendSyncStatus status;
3058 cbdav = E_CAL_BACKEND_CALDAV (backend);
3059 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3061 g_mutex_lock (priv->lock);
3062 status = do_modify_object (cbdav, calobj, mod, old_object, new_object);
3063 g_mutex_unlock (priv->lock);
3068 static ECalBackendSyncStatus
3069 caldav_remove_object (ECalBackendSync *backend, EDataCal *cal, const gchar *uid, const gchar *rid, CalObjModType mod, gchar **old_object, gchar **object)
3071 ECalBackendCalDAV *cbdav;
3072 ECalBackendCalDAVPrivate *priv;
3073 ECalBackendSyncStatus status;
3075 cbdav = E_CAL_BACKEND_CALDAV (backend);
3076 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3078 g_mutex_lock (priv->lock);
3079 status = do_remove_object (cbdav, uid, rid, mod, old_object, object);
3080 g_mutex_unlock (priv->lock);
3085 static ECalBackendSyncStatus
3086 caldav_discard_alarm (ECalBackendSync *backend,
3091 return GNOME_Evolution_Calendar_Success;
3094 static ECalBackendSyncStatus
3095 extract_objects (icalcomponent *icomp,
3096 icalcomponent_kind ekind,
3099 icalcomponent *scomp;
3100 icalcomponent_kind kind;
3102 g_return_val_if_fail (icomp, GNOME_Evolution_Calendar_OtherError);
3103 g_return_val_if_fail (objects, GNOME_Evolution_Calendar_OtherError);
3105 kind = icalcomponent_isa (icomp);
3107 if (kind == ekind) {
3108 *objects = g_list_prepend (NULL, icomp);
3109 return GNOME_Evolution_Calendar_Success;
3112 if (kind != ICAL_VCALENDAR_COMPONENT) {
3113 return GNOME_Evolution_Calendar_InvalidObject;
3117 scomp = icalcomponent_get_first_component (icomp,
3121 /* Remove components from toplevel here */
3122 *objects = g_list_prepend (*objects, scomp);
3123 icalcomponent_remove_component (icomp, scomp);
3125 scomp = icalcomponent_get_next_component (icomp, ekind);
3128 return GNOME_Evolution_Calendar_Success;
3132 extract_timezones (ECalBackendCalDAV *cbdav, icalcomponent *icomp)
3134 ECalBackendCalDAVPrivate *priv;
3135 GList *timezones = NULL, *iter;
3138 g_return_val_if_fail (cbdav != NULL, FALSE);
3139 g_return_val_if_fail (icomp != NULL, FALSE);
3141 if (extract_objects (icomp, ICAL_VTIMEZONE_COMPONENT, &timezones) != GNOME_Evolution_Calendar_Success) {
3145 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3147 zone = icaltimezone_new ();
3148 for (iter = timezones; iter; iter = iter->next) {
3149 if (icaltimezone_set_component (zone, iter->data)) {
3150 e_cal_backend_cache_put_timezone (priv->cache, zone);
3152 icalcomponent_free (iter->data);
3156 icaltimezone_free (zone, TRUE);
3157 g_list_free (timezones);
3162 #define is_error(__status) (__status != GNOME_Evolution_Calendar_Success)
3164 static ECalBackendSyncStatus
3165 process_object (ECalBackendCalDAV *cbdav,
3166 ECalComponent *ecomp,
3168 icalproperty_method method)
3170 ECalBackendCalDAVPrivate *priv;
3171 ECalBackendSyncStatus status;
3172 ECalBackend *backend;
3173 struct icaltimetype now;
3175 gboolean is_declined, is_in_cache;
3177 ECalComponentId *id = e_cal_component_get_id (ecomp);
3179 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3180 backend = E_CAL_BACKEND (cbdav);
3182 g_return_val_if_fail (id != NULL, GNOME_Evolution_Calendar_InvalidObject);
3185 now = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
3186 e_cal_component_set_created (ecomp, &now);
3187 e_cal_component_set_last_modified (ecomp, &now);
3189 /* just to check whether component exists in a cache */
3190 is_in_cache = cache_contains (cbdav, id->uid, NULL) || cache_contains (cbdav, id->uid, id->rid);
3192 new_obj_str = e_cal_component_get_as_string (ecomp);
3193 mod = e_cal_component_is_instance (ecomp) ? CALOBJ_MOD_THIS : CALOBJ_MOD_ALL;
3194 status = GNOME_Evolution_Calendar_Success;
3197 case ICAL_METHOD_PUBLISH:
3198 case ICAL_METHOD_REQUEST:
3199 case ICAL_METHOD_REPLY:
3200 is_declined = e_cal_backend_user_declined (e_cal_component_get_icalcomponent (ecomp));
3203 gchar *new_object = NULL, *old_object = NULL;
3205 status = do_modify_object (cbdav, new_obj_str, mod, &old_object, &new_object);
3206 if (status == GNOME_Evolution_Calendar_Success) {
3208 e_cal_backend_notify_object_created (backend, new_object);
3210 e_cal_backend_notify_object_modified (backend, old_object, new_object);
3213 g_free (new_object);
3214 g_free (old_object);
3216 gchar *new_object = NULL, *old_object = NULL;
3218 status = do_remove_object (cbdav, id->uid, id->rid, mod, &old_object, &new_object);
3219 if (status == GNOME_Evolution_Calendar_Success) {
3221 e_cal_backend_notify_object_modified (backend, old_object, new_object);
3223 e_cal_backend_notify_object_removed (backend, id, old_object, NULL);
3227 g_free (new_object);
3228 g_free (old_object);
3230 } else if (!is_declined) {
3231 gchar *new_object = new_obj_str;
3233 status = do_create_object (cbdav, &new_object, NULL);
3234 if (status == GNOME_Evolution_Calendar_Success) {
3235 e_cal_backend_notify_object_created (backend, new_object);
3238 if (new_object != new_obj_str)
3239 g_free (new_object);
3242 case ICAL_METHOD_CANCEL:
3244 gchar *old_object = NULL, *new_object = NULL;
3246 status = do_remove_object (cbdav, id->uid, id->rid, CALOBJ_MOD_THIS, &old_object, &new_object);
3247 if (status == GNOME_Evolution_Calendar_Success) {
3249 e_cal_backend_notify_object_modified (backend, old_object, new_object);
3251 e_cal_backend_notify_object_removed (backend, id, old_object, NULL);
3255 g_free (old_object);
3256 g_free (new_object);
3258 status = GNOME_Evolution_Calendar_ObjectNotFound;
3263 status = GNOME_Evolution_Calendar_UnsupportedMethod;
3267 e_cal_component_free_id (id);
3268 g_free (new_obj_str);
3273 static ECalBackendSyncStatus
3274 caldav_receive_objects (ECalBackendSync *backend,
3276 const gchar *calobj)
3278 ECalBackendCalDAV *cbdav;
3279 ECalBackendCalDAVPrivate *priv;
3280 ECalBackendSyncStatus status;
3281 icalcomponent *icomp;
3282 icalcomponent_kind kind;
3283 icalproperty_method tmethod;
3288 cbdav = E_CAL_BACKEND_CALDAV (backend);
3289 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3291 icomp = icalparser_parse_string (calobj);
3293 /* Try to parse cal object string */
3294 if (icomp == NULL) {
3295 return GNOME_Evolution_Calendar_InvalidObject;
3298 kind = e_cal_backend_get_kind (E_CAL_BACKEND (backend));
3299 status = extract_objects (icomp, kind, &objects);
3301 if (status != GNOME_Evolution_Calendar_Success) {
3302 icalcomponent_free (icomp);
3306 /* Extract optional timezone compnents */
3307 extract_timezones (cbdav, icomp);
3310 g_mutex_lock (priv->lock);
3312 status = check_state (cbdav, &online);
3314 if (status != GNOME_Evolution_Calendar_Success) {
3315 /* FIXME: free components here */
3316 g_mutex_unlock (priv->lock);
3317 icalcomponent_free (icomp);
3321 tmethod = icalcomponent_get_method (icomp);
3323 for (iter = objects; iter && ! is_error (status); iter = iter->next) {
3324 icalcomponent *scomp;
3325 ECalComponent *ecomp;
3326 icalproperty_method method;
3328 scomp = (icalcomponent *) iter->data;
3329 ecomp = e_cal_component_new ();
3331 e_cal_component_set_icalcomponent (ecomp, scomp);
3333 if (icalcomponent_get_first_property (scomp, ICAL_METHOD_PROPERTY)) {
3334 method = icalcomponent_get_method (scomp);
3339 status = process_object (cbdav, ecomp, online, method);
3341 g_object_unref (ecomp);
3344 g_list_free (objects);
3346 g_mutex_unlock (priv->lock);
3347 icalcomponent_free (icomp);
3352 static ECalBackendSyncStatus
3353 caldav_send_objects (ECalBackendSync *backend,
3355 const gchar *calobj,
3357 gchar **modified_calobj)
3360 *modified_calobj = g_strdup (calobj);
3362 return GNOME_Evolution_Calendar_Success;
3365 static ECalBackendSyncStatus
3366 caldav_get_default_object (ECalBackendSync *backend,
3370 ECalComponent *comp;
3372 comp = e_cal_component_new ();
3374 switch (e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
3375 case ICAL_VEVENT_COMPONENT:
3376 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
3378 case ICAL_VTODO_COMPONENT:
3379 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
3381 case ICAL_VJOURNAL_COMPONENT:
3382 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
3385 g_object_unref (comp);
3386 return GNOME_Evolution_Calendar_ObjectNotFound;
3389 *object = e_cal_component_get_as_string (comp);
3390 g_object_unref (comp);
3392 return GNOME_Evolution_Calendar_Success;
3395 static ECalBackendSyncStatus
3396 caldav_get_object (ECalBackendSync *backend,
3402 ECalBackendCalDAV *cbdav;
3403 ECalBackendCalDAVPrivate *priv;
3404 icalcomponent *icalcomp;
3406 cbdav = E_CAL_BACKEND_CALDAV (backend);
3407 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3409 g_mutex_lock (priv->lock);
3412 icalcomp = get_comp_from_cache (cbdav, uid, rid, NULL, NULL);
3414 g_mutex_unlock (priv->lock);
3417 return GNOME_Evolution_Calendar_ObjectNotFound;
3420 *object = icalcomponent_as_ical_string_r (icalcomp);
3421 icalcomponent_free (icalcomp);
3423 return GNOME_Evolution_Calendar_Success;
3426 static ECalBackendSyncStatus
3427 caldav_get_timezone (ECalBackendSync *backend,
3432 ECalBackendCalDAV *cbdav;
3433 ECalBackendCalDAVPrivate *priv;
3434 const icaltimezone *zone;
3435 icalcomponent *icalcomp;
3437 cbdav = E_CAL_BACKEND_CALDAV (backend);
3438 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3440 g_return_val_if_fail (tzid, GNOME_Evolution_Calendar_ObjectNotFound);
3442 /* first try to get the timezone from the cache */
3443 g_mutex_lock (priv->lock);
3444 zone = e_cal_backend_cache_get_timezone (priv->cache, tzid);
3445 g_mutex_unlock (priv->lock);
3448 zone = icaltimezone_get_builtin_timezone_from_tzid (tzid);
3450 return GNOME_Evolution_Calendar_ObjectNotFound;
3454 icalcomp = icaltimezone_get_component ((icaltimezone *) zone);
3457 return GNOME_Evolution_Calendar_InvalidObject;
3460 *object = icalcomponent_as_ical_string_r (icalcomp);
3462 return GNOME_Evolution_Calendar_Success;
3465 static ECalBackendSyncStatus
3466 caldav_add_timezone (ECalBackendSync *backend,
3470 icalcomponent *tz_comp;
3471 ECalBackendCalDAV *cbdav;
3472 ECalBackendCalDAVPrivate *priv;
3474 cbdav = E_CAL_BACKEND_CALDAV (backend);
3476 g_return_val_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav), GNOME_Evolution_Calendar_OtherError);
3477 g_return_val_if_fail (tzobj != NULL, GNOME_Evolution_Calendar_OtherError);
3479 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3481 tz_comp = icalparser_parse_string (tzobj);
3483 return GNOME_Evolution_Calendar_InvalidObject;
3485 if (icalcomponent_isa (tz_comp) == ICAL_VTIMEZONE_COMPONENT) {
3488 zone = icaltimezone_new ();
3489 icaltimezone_set_component (zone, tz_comp);
3491 g_mutex_lock (priv->lock);
3492 e_cal_backend_cache_put_timezone (priv->cache, zone);
3493 g_mutex_unlock (priv->lock);
3495 icaltimezone_free (zone, TRUE);
3497 icalcomponent_free (tz_comp);
3500 return GNOME_Evolution_Calendar_Success;
3503 static ECalBackendSyncStatus
3504 caldav_set_default_zone (ECalBackendSync *backend,
3508 icalcomponent *tz_comp;
3509 ECalBackendCalDAV *cbdav;
3510 ECalBackendCalDAVPrivate *priv;
3513 g_return_val_if_fail (E_IS_CAL_BACKEND_CALDAV (backend), GNOME_Evolution_Calendar_OtherError);
3514 g_return_val_if_fail (tzobj != NULL, GNOME_Evolution_Calendar_OtherError);
3516 cbdav = E_CAL_BACKEND_CALDAV (backend);
3517 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3519 tz_comp = icalparser_parse_string (tzobj);
3521 return GNOME_Evolution_Calendar_InvalidObject;
3523 zone = icaltimezone_new ();
3524 icaltimezone_set_component (zone, tz_comp);
3526 if (priv->default_zone != icaltimezone_get_utc_timezone ())
3527 icaltimezone_free (priv->default_zone, 1);
3529 /* Set the default timezone to it. */
3530 priv->default_zone = zone;
3532 return GNOME_Evolution_Calendar_Success;
3535 static ECalBackendSyncStatus
3536 caldav_get_object_list (ECalBackendSync *backend,
3538 const gchar *sexp_string,
3541 ECalBackendCalDAV *cbdav;
3542 ECalBackendCalDAVPrivate *priv;
3543 ECalBackendSExp *sexp;
3544 ECalBackendCache *bcache;
3549 cbdav = E_CAL_BACKEND_CALDAV (backend);
3550 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3552 sexp = e_cal_backend_sexp_new (sexp_string);
3555 return GNOME_Evolution_Calendar_InvalidQuery;
3558 if (g_str_equal (sexp_string, "#t")) {
3565 bcache = priv->cache;
3567 g_mutex_lock (priv->lock);
3569 list = e_cal_backend_cache_get_components (bcache);
3570 bkend = E_CAL_BACKEND (backend);
3572 for (iter = list; iter; iter = g_list_next (iter)) {
3573 ECalComponent *comp = E_CAL_COMPONENT (iter->data);
3576 e_cal_backend_sexp_match_comp (sexp, comp, bkend)) {
3577 gchar *str = e_cal_component_get_as_string (comp);
3578 *objects = g_list_prepend (*objects, str);
3581 g_object_unref (comp);
3584 g_object_unref (sexp);
3587 g_mutex_unlock (priv->lock);
3589 return GNOME_Evolution_Calendar_Success;
3593 caldav_start_query (ECalBackend *backend,
3594 EDataCalView *query)
3596 ECalBackendCalDAV *cbdav;
3597 ECalBackendCalDAVPrivate *priv;
3598 ECalBackendSExp *sexp;
3602 const gchar *sexp_string;
3604 cbdav = E_CAL_BACKEND_CALDAV (backend);
3605 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3607 sexp_string = e_data_cal_view_get_text (query);
3608 sexp = e_cal_backend_sexp_new (sexp_string);
3610 /* FIXME:check invalid sexp */
3612 if (g_str_equal (sexp_string, "#t")) {
3618 g_mutex_lock (priv->lock);
3620 list = e_cal_backend_cache_get_components (priv->cache);
3621 bkend = E_CAL_BACKEND (backend);
3623 for (iter = list; iter; iter = g_list_next (iter)) {
3624 ECalComponent *comp = E_CAL_COMPONENT (iter->data);
3627 e_cal_backend_sexp_match_comp (sexp, comp, bkend)) {
3628 gchar *str = e_cal_component_get_as_string (comp);
3629 e_data_cal_view_notify_objects_added_1 (query, str);
3633 g_object_unref (comp);
3636 g_object_unref (sexp);
3640 e_data_cal_view_notify_done (query, GNOME_Evolution_Calendar_Success);
3641 g_mutex_unlock (priv->lock);
3645 static ECalBackendSyncStatus
3646 caldav_get_free_busy (ECalBackendSync *backend,
3653 ECalBackendCalDAV *cbdav;
3654 ECalBackendCalDAVPrivate *priv;
3655 ECalBackendSyncStatus status;
3656 icalcomponent *icalcomp;
3657 ECalComponent *comp;
3658 ECalComponentDateTime dt;
3659 struct icaltimetype dtvalue;
3663 GSList *attendees = NULL, *to_free = NULL;
3665 cbdav = E_CAL_BACKEND_CALDAV (backend);
3666 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3668 g_return_val_if_fail (priv != NULL, GNOME_Evolution_Calendar_OtherError);
3669 g_return_val_if_fail (users != NULL, GNOME_Evolution_Calendar_OtherError);
3670 g_return_val_if_fail (freebusy != NULL, GNOME_Evolution_Calendar_OtherError);
3671 g_return_val_if_fail (start < end, GNOME_Evolution_Calendar_OtherError);
3673 if (!priv->calendar_schedule) {
3674 return GNOME_Evolution_Calendar_OtherError;
3677 if (!priv->schedule_outbox_url) {
3678 caldav_receive_schedule_outbox_url (cbdav);
3679 if (!priv->schedule_outbox_url) {
3680 priv->calendar_schedule = FALSE;
3681 return GNOME_Evolution_Calendar_OtherError;
3685 comp = e_cal_component_new ();
3686 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_FREEBUSY);
3688 str = e_cal_component_gen_uid ();
3689 e_cal_component_set_uid (comp, str);
3692 utc = icaltimezone_get_utc_timezone ();
3693 dt.value = &dtvalue;
3694 dt.tzid = icaltimezone_get_tzid (utc);
3696 dtvalue = icaltime_current_time_with_zone (utc);
3697 e_cal_component_set_dtstamp (comp, &dtvalue);
3699 dtvalue = icaltime_from_timet_with_zone (start, FALSE, utc);
3700 e_cal_component_set_dtstart (comp, &dt);
3702 dtvalue = icaltime_from_timet_with_zone (end, FALSE, utc);
3703 e_cal_component_set_dtend (comp, &dt);
3705 if (priv->username) {
3706 ECalComponentOrganizer organizer = {0};
3708 organizer.value = priv->username;
3709 e_cal_component_set_organizer (comp, &organizer);
3712 for (u = users; u; u = u->next) {
3713 ECalComponentAttendee *ca;
3714 gchar *temp = g_strconcat ("mailto:", (const gchar *)u->data, NULL);
3716 ca = g_new0 (ECalComponentAttendee, 1);
3719 ca->cutype = ICAL_CUTYPE_INDIVIDUAL;
3720 ca->status = ICAL_PARTSTAT_NEEDSACTION;
3721 ca->role = ICAL_ROLE_CHAIR;
3723 to_free = g_slist_prepend (to_free, temp);
3724 attendees = g_slist_append (attendees, ca);
3727 e_cal_component_set_attendee_list (comp, attendees);
3729 g_slist_foreach (attendees, (GFunc) g_free, NULL);
3730 g_slist_free (attendees);
3732 g_slist_foreach (to_free, (GFunc) g_free, NULL);
3733 g_slist_free (to_free);
3735 e_cal_component_abort_sequence (comp);
3737 /* put the free/busy request to a VCALENDAR */
3738 icalcomp = e_cal_util_new_top_level ();
3739 icalcomponent_set_method (icalcomp, ICAL_METHOD_REQUEST);
3740 icalcomponent_add_component (icalcomp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
3742 str = icalcomponent_as_ical_string_r (icalcomp);
3744 icalcomponent_free (icalcomp);
3745 g_object_unref (comp);
3747 g_return_val_if_fail (str != NULL, GNOME_Evolution_Calendar_OtherError);
3749 status = caldav_post_freebusy (cbdav, priv->schedule_outbox_url, &str);
3751 if (status == GNOME_Evolution_Calendar_Success) {
3752 /* parse returned xml */
3755 doc = xmlReadMemory (str, strlen (str), "response.xml", NULL, 0);
3757 xmlXPathContextPtr xpctx;
3758 xmlXPathObjectPtr result;
3760 xpctx = xmlXPathNewContext (doc);
3761 xmlXPathRegisterNs (xpctx, (xmlChar *) "D", (xmlChar *) "DAV:");
3762 xmlXPathRegisterNs (xpctx, (xmlChar *) "C", (xmlChar *) "urn:ietf:params:xml:ns:caldav");
3764 result = xpath_eval (xpctx, "/C:schedule-response/C:response");
3766 if (result == NULL || result->type != XPATH_NODESET) {
3767 status = GNOME_Evolution_Calendar_OtherError;
3771 n = xmlXPathNodeSetGetLength (result->nodesetval);
3772 for (i = 0; i < n; i++) {
3775 tmp = xp_object_get_string (xpath_eval (xpctx, "string(/C:schedule-response/C:response[%d]/C:calendar-data)", i + 1));
3777 GList *objects = NULL, *o;
3779 icalcomp = icalparser_parse_string (tmp);
3780 if (icalcomp && extract_objects (icalcomp, ICAL_VFREEBUSY_COMPONENT, &objects) == GNOME_Evolution_Calendar_Success) {
3781 for (o = objects; o; o = o->next) {
3782 gchar *obj_str = icalcomponent_as_ical_string_r (o->data);
3784 if (obj_str && *obj_str)
3785 *freebusy = g_list_append (*freebusy, obj_str);
3791 g_list_foreach (objects, (GFunc)icalcomponent_free, NULL);
3792 g_list_free (objects);
3795 icalcomponent_free (icalcomp);
3804 xmlXPathFreeObject (result);
3805 xmlXPathFreeContext (xpctx);
3815 static ECalBackendSyncStatus
3816 caldav_get_changes (ECalBackendSync *backend,
3818 const gchar *change_id,
3823 /* FIXME: implement me! */
3824 g_warning ("function not implemented %s", G_STRFUNC);
3825 return GNOME_Evolution_Calendar_OtherError;
3829 caldav_is_loaded (ECalBackend *backend)
3831 ECalBackendCalDAV *cbdav;
3832 ECalBackendCalDAVPrivate *priv;
3834 cbdav = E_CAL_BACKEND_CALDAV (backend);
3835 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3837 return priv->loaded;
3841 caldav_get_mode (ECalBackend *backend)
3843 ECalBackendCalDAV *cbdav;
3844 ECalBackendCalDAVPrivate *priv;
3846 cbdav = E_CAL_BACKEND_CALDAV (backend);
3847 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3853 caldav_set_mode (ECalBackend *backend, CalMode mode)
3855 ECalBackendCalDAV *cbdav;
3856 ECalBackendCalDAVPrivate *priv;
3858 cbdav = E_CAL_BACKEND_CALDAV (backend);
3859 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3861 /*g_mutex_lock (priv->lock);*/
3863 /* We only support online and offline */
3864 if (mode != CAL_MODE_REMOTE &&
3865 mode != CAL_MODE_LOCAL) {
3866 e_cal_backend_notify_mode (backend,
3867 GNOME_Evolution_Calendar_CalListener_MODE_NOT_SUPPORTED,
3868 cal_mode_to_corba (mode));
3869 /*g_mutex_unlock (priv->lock);*/
3873 if (priv->mode == mode || !priv->loaded) {
3875 e_cal_backend_notify_mode (backend,
3876 GNOME_Evolution_Calendar_CalListener_MODE_SET,
3877 cal_mode_to_corba (mode));
3878 /*g_mutex_unlock (priv->lock);*/
3884 if (mode == CAL_MODE_REMOTE) {
3885 /* Wake up the slave thread */
3886 priv->slave_cmd = SLAVE_SHOULD_WORK;
3887 g_cond_signal (priv->cond);
3889 soup_session_abort (priv->session);
3890 priv->slave_cmd = SLAVE_SHOULD_SLEEP;
3893 e_cal_backend_notify_mode (backend,
3894 GNOME_Evolution_Calendar_CalListener_MODE_SET,
3895 cal_mode_to_corba (mode));
3897 /*g_mutex_unlock (priv->lock);*/
3900 static icaltimezone *
3901 caldav_internal_get_default_timezone (ECalBackend *backend)
3903 ECalBackendCalDAV *cbdav;
3904 ECalBackendCalDAVPrivate *priv;
3906 g_return_val_if_fail (E_IS_CAL_BACKEND_CALDAV (backend), NULL);
3908 cbdav = E_CAL_BACKEND_CALDAV (backend);
3909 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3911 g_return_val_if_fail (priv->default_zone != NULL, NULL);
3913 return priv->default_zone;
3916 static icaltimezone *
3917 caldav_internal_get_timezone (ECalBackend *backend,
3922 zone = icaltimezone_get_builtin_timezone_from_tzid (tzid);
3924 if (!zone && E_CAL_BACKEND_CLASS (parent_class)->internal_get_timezone)
3925 zone = E_CAL_BACKEND_CLASS (parent_class)->internal_get_timezone (backend, tzid);
3928 zone = icaltimezone_get_utc_timezone ();
3934 /* ************************************************************************* */
3935 /* ***************************** GObject Foo ******************************* */
3937 G_DEFINE_TYPE (ECalBackendCalDAV, e_cal_backend_caldav, E_TYPE_CAL_BACKEND_SYNC)
3940 e_cal_backend_caldav_dispose (GObject *object)
3942 ECalBackendCalDAV *cbdav;
3943 ECalBackendCalDAVPrivate *priv;
3945 cbdav = E_CAL_BACKEND_CALDAV (object);
3946 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3948 g_mutex_lock (priv->lock);
3950 if (priv->disposed) {
3951 g_mutex_unlock (priv->lock);
3955 /* stop the slave */
3956 priv->slave_cmd = SLAVE_SHOULD_DIE;
3957 if (priv->synch_slave) {
3958 g_cond_signal (priv->cond);
3960 /* wait until the slave died */
3961 g_cond_wait (priv->slave_gone_cond, priv->lock);
3964 g_object_unref (priv->session);
3965 g_object_unref (priv->proxy);
3967 g_free (priv->username);
3968 g_free (priv->password);
3970 g_free (priv->schedule_outbox_url);
3972 if (priv->cache != NULL) {
3973 g_object_unref (priv->cache);
3976 priv->disposed = TRUE;
3977 g_mutex_unlock (priv->lock);
3979 if (G_OBJECT_CLASS (parent_class)->dispose)
3980 (* G_OBJECT_CLASS (parent_class)->dispose) (object);
3984 e_cal_backend_caldav_finalize (GObject *object)
3986 ECalBackendCalDAV *cbdav;
3987 ECalBackendCalDAVPrivate *priv;
3989 cbdav = E_CAL_BACKEND_CALDAV (object);
3990 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3992 g_mutex_free (priv->lock);
3993 g_cond_free (priv->cond);
3994 g_cond_free (priv->slave_gone_cond);
3996 if (priv->default_zone && priv->default_zone != icaltimezone_get_utc_timezone ()) {
3997 icaltimezone_free (priv->default_zone, 1);
3999 priv->default_zone = NULL;
4001 if (G_OBJECT_CLASS (parent_class)->finalize)
4002 (* G_OBJECT_CLASS (parent_class)->finalize) (object);
4006 e_cal_backend_caldav_init (ECalBackendCalDAV *cbdav)
4008 ECalBackendCalDAVPrivate *priv;
4009 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
4011 priv->session = soup_session_sync_new ();
4012 priv->proxy = e_proxy_new ();
4013 e_proxy_setup_proxy (priv->proxy);
4014 g_signal_connect (priv->proxy, "changed", G_CALLBACK (proxy_settings_changed), priv);
4016 if (G_UNLIKELY (caldav_debug_show (DEBUG_MESSAGE)))
4017 caldav_debug_setup (priv->session);
4019 priv->default_zone = icaltimezone_get_utc_timezone ();
4021 priv->disposed = FALSE;
4022 priv->do_synch = FALSE;
4023 priv->loaded = FALSE;
4025 /* Thinks the 'getctag' extension is available the first time, but unset it when realizes it isn't. */
4026 priv->ctag_supported = TRUE;
4028 priv->schedule_outbox_url = NULL;
4030 priv->lock = g_mutex_new ();
4031 priv->cond = g_cond_new ();
4032 priv->slave_gone_cond = g_cond_new ();
4034 /* Slave control ... */
4035 priv->slave_cmd = SLAVE_SHOULD_SLEEP;
4036 priv->refresh_time.tv_usec = 0;
4037 priv->refresh_time.tv_sec = DEFAULT_REFRESH_TIME;
4039 g_signal_connect (priv->session, "authenticate",
4040 G_CALLBACK (soup_authenticate), cbdav);
4042 e_cal_backend_sync_set_lock (E_CAL_BACKEND_SYNC (cbdav), FALSE);
4047 e_cal_backend_caldav_class_init (ECalBackendCalDAVClass *class)
4049 GObjectClass *object_class;
4050 ECalBackendClass *backend_class;
4051 ECalBackendSyncClass *sync_class;
4053 object_class = (GObjectClass *) class;
4054 backend_class = (ECalBackendClass *) class;
4055 sync_class = (ECalBackendSyncClass *) class;
4057 caldav_debug_init ();
4059 parent_class = (ECalBackendSyncClass *) g_type_class_peek_parent (class);
4060 g_type_class_add_private (class, sizeof (ECalBackendCalDAVPrivate));
4062 object_class->dispose = e_cal_backend_caldav_dispose;
4063 object_class->finalize = e_cal_backend_caldav_finalize;
4065 sync_class->is_read_only_sync = caldav_is_read_only;
4066 sync_class->get_cal_address_sync = caldav_get_cal_address;
4067 sync_class->get_alarm_email_address_sync = caldav_get_alarm_email_address;
4068 sync_class->get_ldap_attribute_sync = caldav_get_ldap_attribute;
4069 sync_class->get_static_capabilities_sync = caldav_get_static_capabilities;
4071 sync_class->open_sync = caldav_do_open;
4072 sync_class->remove_sync = caldav_remove;
4074 sync_class->create_object_sync = caldav_create_object;
4075 sync_class->modify_object_sync = caldav_modify_object;
4076 sync_class->remove_object_sync = caldav_remove_object;
4078 sync_class->discard_alarm_sync = caldav_discard_alarm;
4079 sync_class->receive_objects_sync = caldav_receive_objects;
4080 sync_class->send_objects_sync = caldav_send_objects;
4081 sync_class->get_default_object_sync = caldav_get_default_object;
4082 sync_class->get_object_sync = caldav_get_object;
4083 sync_class->get_object_list_sync = caldav_get_object_list;
4084 sync_class->get_timezone_sync = caldav_get_timezone;
4085 sync_class->add_timezone_sync = caldav_add_timezone;
4086 sync_class->set_default_zone_sync = caldav_set_default_zone;
4087 sync_class->get_freebusy_sync = caldav_get_free_busy;
4088 sync_class->get_changes_sync = caldav_get_changes;
4090 backend_class->is_loaded = caldav_is_loaded;
4091 backend_class->start_query = caldav_start_query;
4092 backend_class->get_mode = caldav_get_mode;
4093 backend_class->set_mode = caldav_set_mode;
4095 backend_class->internal_get_default_timezone = caldav_internal_get_default_timezone;
4096 backend_class->internal_get_timezone = caldav_internal_get_timezone;