1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
3 /* Evolution calendar - caldav backend
5 * Copyright (C) 2005 Novell, Inc.
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of version 2 of the GNU General Public
9 * License as published by the Free Software Foundation.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * Author: Christian Kellner <gicmo@gnome.org>
26 #include <gconf/gconf-client.h>
27 #include <bonobo/bonobo-exception.h>
28 #include <bonobo/bonobo-moniker-util.h>
29 #include <glib/gi18n-lib.h>
30 #include "libedataserver/e-xml-hash-utils.h"
31 #include <libecal/e-cal-recur.h>
32 #include <libecal/e-cal-util.h>
33 #include <libecal/e-cal-time-util.h>
34 #include <libedata-cal/e-cal-backend-cache.h>
35 #include <libedata-cal/e-cal-backend-util.h>
36 #include <libedata-cal/e-cal-backend-sexp.h>
38 /* LibXML2 includes */
39 #include <libxml/parser.h>
40 #include <libxml/tree.h>
41 #include <libxml/xpath.h>
42 #include <libxml/xpathInternals.h>
44 /* LibSoup includes */
45 #include <libsoup/soup.h>
46 #include <libsoup/soup-headers.h>
47 #include <libsoup/soup-uri.h>
48 #include <libsoup/soup-soap-message.h>
50 #include "e-cal-backend-caldav.h"
53 #define DEFAULT_REFRESH_TIME 60
63 /* Private part of the ECalBackendHttp structure */
64 struct _ECalBackendCalDAVPrivate {
69 /* The local disk cache */
70 ECalBackendCache *cache;
72 /* should we sync for offline mode? */
75 /* TRUE after caldav_open */
79 ECalBackendSyncStatus ostatus;
81 /* lock to protect cache */
84 /* cond to synch threads */
89 SlaveCommand slave_cmd;
90 GTimeVal refresh_time;
93 /* The main soup session */
96 /* well, guess what */
99 /* whehter the synch function
100 * should report changes to the
102 gboolean report_changes;
107 /* Authentication info */
116 /* ************************************************************************* */
119 #define DEBUG_MESSAGE "message"
120 #define DEBUG_MESSAGE_HEADER "message:header"
121 #define DEBUG_MESSAGE_BODY "message:body"
123 static gboolean caldav_debug_all = FALSE;
124 static GHashTable *caldav_debug_table = NULL;
128 add_debug_key (const char *start, const char *end)
137 debug_key = debug_value = g_strndup (start, end - start);
139 debug_key = g_strchug (debug_key);
140 debug_key = g_strchomp (debug_key);
142 if (strlen (debug_key) == 0) {
143 g_free (debug_value);
147 g_hash_table_insert (caldav_debug_table,
151 g_debug ("Adding %s to enabled debugging keys", debug_key);
155 caldav_debug_init_once (gpointer data)
159 dbg = g_getenv ("CALDAV_DEBUG");
164 g_debug ("Got debug env variable: [%s]", dbg);
166 caldav_debug_table = g_hash_table_new (g_str_hash,
171 while (*ptr != '\0') {
172 if (*ptr == ',' || *ptr == ':') {
174 add_debug_key (dbg, ptr);
185 add_debug_key (dbg, ptr);
188 if (g_hash_table_lookup (caldav_debug_table, "all")) {
189 caldav_debug_all = TRUE;
190 g_hash_table_destroy (caldav_debug_table);
191 caldav_debug_table = NULL;
201 static GOnce debug_once = G_ONCE_INIT;
204 caldav_debug_init_once,
209 caldav_debug_show (const char *component)
211 if (G_UNLIKELY (caldav_debug_all)) {
213 } else if (G_UNLIKELY (caldav_debug_table != NULL) &&
214 g_hash_table_lookup (caldav_debug_table, component)) {
222 message_debug_print_header (gpointer name, gpointer value, gpointer data)
224 g_debug ("%s: %s", (char *) name, (char *) value);
227 #define DEBUG_MAX_BODY_SIZE (100 * 1024 * 1024)
230 message_response_debug_handler (SoupMessage *msg, gpointer user_data)
233 g_debug ("%d %s\nMessage-Debug: %p @ %lu",
239 if (caldav_debug_show (DEBUG_MESSAGE_HEADER)) {
241 soup_message_foreach_header (msg->response_headers,
242 message_debug_print_header,
247 if (caldav_debug_show (DEBUG_MESSAGE_BODY)) {
250 if (msg->response.length) {
253 //needed for null terminal and truncation
254 body = g_strndup (msg->response.body,
255 MIN (msg->response.length,
256 DEBUG_MAX_BODY_SIZE));
258 g_debug ("Response: \n[%s%s]%s", body,
259 msg->response.length > DEBUG_MAX_BODY_SIZE ?
261 msg->response.length > DEBUG_MAX_BODY_SIZE ?
262 " (trunkated)" : "");
270 message_setup_debug (SoupMessage *msg)
274 if (G_LIKELY (! caldav_debug_show (DEBUG_MESSAGE))) {
278 suri = soup_message_get_uri (msg);
280 g_debug ("%s %s%s%s HTTP/1.1\nMessage-ID: %p @ %lu",
281 SOUP_MESSAGE (msg)->method,
283 suri->query ? "?" : "",
284 suri->query ? suri->query : "",
286 (unsigned long) time (0));
288 soup_message_add_handler (SOUP_MESSAGE (msg),
289 SOUP_HANDLER_POST_BODY,
290 message_response_debug_handler,
293 if (G_LIKELY (! caldav_debug_show (DEBUG_MESSAGE_HEADER))) {
297 /* print message headers */
298 message_debug_print_header ("Host", suri->host, NULL);
300 soup_message_foreach_header (SOUP_MESSAGE (msg)->request_headers,
301 message_debug_print_header,
304 if (caldav_debug_show (DEBUG_MESSAGE_BODY)) {
307 if (msg->request.length) {
310 //needed for null terminal and truncation
311 body = g_strndup (msg->request.body,
312 MIN (msg->request.length,
313 DEBUG_MAX_BODY_SIZE));
315 g_debug ("Request: \n[%s%s]%s", body,
316 msg->request.length > DEBUG_MAX_BODY_SIZE ?
318 msg->request.length > DEBUG_MAX_BODY_SIZE ?
319 " (trunkated)" : "");
327 static ECalBackendSyncClass *parent_class = NULL;
329 /* ************************************************************************* */
330 /* Misc. utility functions */
331 #define X_E_CALDAV "X-EVOLUTION-CALDAV-"
334 icomp_x_prop_set (icalcomponent *comp, const char *key, const char *value)
338 /* Find the old one first */
339 xprop = icalcomponent_get_first_property (comp, ICAL_X_PROPERTY);
342 const char *str = icalproperty_get_x_name (xprop);
344 if (!strcmp (str, key)) {
345 icalcomponent_remove_property (comp, xprop);
346 icalproperty_free (xprop);
350 xprop = icalcomponent_get_next_property (comp, ICAL_X_PROPERTY);
353 /* couldnt we be a bit smarter here and reuse the property? */
355 xprop = icalproperty_new_x (value);
356 icalproperty_set_x_name (xprop, key);
357 icalcomponent_add_property (comp, xprop);
362 icomp_x_prop_get (icalcomponent *comp, const char *key)
366 /* Find the old one first */
367 xprop = icalcomponent_get_first_property (comp, ICAL_X_PROPERTY);
370 const char *str = icalproperty_get_x_name (xprop);
372 if (!strcmp (str, key)) {
376 xprop = icalcomponent_get_next_property (comp, ICAL_X_PROPERTY);
380 return icalproperty_get_value_as_string (xprop);
388 e_cal_component_set_href (ECalComponent *comp, const char *href)
390 icalcomponent *icomp;
392 icomp = e_cal_component_get_icalcomponent (comp);
394 icomp_x_prop_set (icomp, X_E_CALDAV "HREF", href);
398 e_cal_component_get_href (ECalComponent *comp)
400 icalcomponent *icomp;
404 icomp = e_cal_component_get_icalcomponent (comp);
406 str = (char *) icomp_x_prop_get (icomp, X_E_CALDAV "HREF");
413 e_cal_component_set_etag (ECalComponent *comp, const char *etag)
415 icalcomponent *icomp;
417 icomp = e_cal_component_get_icalcomponent (comp);
419 icomp_x_prop_set (icomp, X_E_CALDAV "ETAG", etag);
425 e_cal_component_get_etag (ECalComponent *comp)
427 icalcomponent *icomp;
431 icomp = e_cal_component_get_icalcomponent (comp);
433 str = (char *) icomp_x_prop_get (icomp, X_E_CALDAV "ETAG");
440 /* object is in synch,
441 * now isnt that ironic? :) */
442 E_CAL_COMPONENT_IN_SYNCH = 0,
445 E_CAL_COMPONENT_LOCALLY_CREATED,
446 E_CAL_COMPONENT_LOCALLY_DELETED,
447 E_CAL_COMPONENT_LOCALLY_MODIFIED,
449 } ECalComponentSyncState;
451 /* oos = out of synch */
453 e_cal_component_set_synch_state (ECalComponent *comp,
454 ECalComponentSyncState state)
456 icalcomponent *icomp;
459 icomp = e_cal_component_get_icalcomponent (comp);
461 state_string = g_strdup_printf ("%d", state);
463 icomp_x_prop_set (icomp, X_E_CALDAV "ETAG", state_string);
465 g_free (state_string);
469 /* gen uid, set it internally and report it back so we can instantly
471 * and btw FIXME!!! */
473 e_cal_component_gen_href (ECalComponent *comp)
477 icalcomponent *icomp;
479 iso = isodate_from_time_t (time (NULL));
481 href = g_strconcat (iso, ".ics", NULL);
485 icomp = e_cal_component_get_icalcomponent (comp);
486 icomp_x_prop_set (icomp, X_E_CALDAV "HREF", href);
491 /* ensure etag is quoted (to workaround potential server bugs) */
493 quote_etag (const char *etag)
497 if (etag && (strlen (etag) < 2 || etag[strlen (etag) - 1] != '\"')) {
498 ret = g_strdup_printf ("\"%s\"", etag);
500 ret = g_strdup (etag);
506 /* ************************************************************************* */
508 sm_join_and_split_header (SoupMessage *message, const char *header)
516 list = soup_message_get_header_list (message->response_headers, header);
518 if (list == NULL || list->data == NULL) {
522 /* Only do string manipulation if really necessary */
525 stmp = g_string_new ((gchar *) list->data);
527 while ((list = list->next)) {
528 g_string_append_printf (stmp, ",%s", (gchar *) list->data);
531 str = tofree = g_string_free (stmp, FALSE);
533 str = (char *) list->data;
537 g_assert (str != NULL);
538 sa = g_strsplit (str, ",", 20);
544 static ECalBackendSyncStatus
545 status_code_to_result (guint status_code)
547 ECalBackendSyncStatus result;
549 if (SOUP_STATUS_IS_SUCCESSFUL (status_code)) {
550 return GNOME_Evolution_Calendar_Success;
553 switch (status_code) {
556 result = GNOME_Evolution_Calendar_NoSuchCal;
560 result = GNOME_Evolution_Calendar_AuthenticationFailed;
564 result = GNOME_Evolution_Calendar_AuthenticationRequired;
568 result = GNOME_Evolution_Calendar_OtherError;
575 match_header (const char *header, const char *string)
577 g_assert (string != NULL);
579 if (header == NULL || header[0] == '\0') {
583 /* skip leading whitespaces */
584 while (g_ascii_isspace (header[0])) {
588 return !g_ascii_strncasecmp (header, string, strlen (string));
591 /* !TS, call with lock held */
592 static ECalBackendSyncStatus
593 check_state (ECalBackendCalDAV *cbdav, gboolean *online)
595 ECalBackendCalDAVPrivate *priv;
597 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
601 if (priv->loaded != TRUE) {
602 return GNOME_Evolution_Calendar_OtherError;
605 if (priv->mode == CAL_MODE_LOCAL) {
607 if (! priv->do_offline) {
608 return GNOME_Evolution_Calendar_RepositoryOffline;
615 return GNOME_Evolution_Calendar_Success;
618 /* ************************************************************************* */
619 /* XML Parsing code */
621 static xmlXPathObjectPtr
622 xpath_eval (xmlXPathContextPtr ctx, char *format, ...)
624 xmlXPathObjectPtr result;
632 va_start (args, format);
633 expr = g_strdup_vprintf (format, args);
636 result = xmlXPathEvalExpression ((xmlChar *) expr, ctx);
639 if (result == NULL) {
643 if (result->type == XPATH_NODESET &&
644 xmlXPathNodeSetIsEmpty (result->nodesetval)) {
645 xmlXPathFreeObject (result);
647 g_print ("No result\n");
657 parse_status_node (xmlNodePtr node, guint *status_code)
662 content = xmlNodeGetContent (node);
664 res = soup_headers_parse_status_line ((char *) content,
675 xp_object_get_string (xmlXPathObjectPtr result)
679 if (result == NULL || result->type != XPATH_STRING) {
683 ret = g_strdup ((char *) result->stringval);
685 xmlXPathFreeObject (result);
689 /* as get_string but will normailze it (i.e. only take
690 * the last part of the href) */
692 xp_object_get_href (xmlXPathObjectPtr result)
697 if (result == NULL || result->type != XPATH_STRING) {
701 val = (char *) result->stringval;
703 if ((ret = g_strrstr (val, "/")) == NULL) {
706 ret++; /* skip the unwanted "/" */
709 ret = g_strdup (ret);
710 g_debug ("found href: %s", ret);
712 xmlXPathFreeObject (result);
716 /* like get_string but will quote the etag if necessary */
718 xp_object_get_etag (xmlXPathObjectPtr result)
723 if (result == NULL || result->type != XPATH_STRING) {
727 str = (char *) result->stringval;
729 ret = quote_etag (str);
731 xmlXPathFreeObject (result);
736 xp_object_get_status (xmlXPathObjectPtr result)
742 if (result == NULL || result->type != XPATH_STRING) {
746 res = soup_headers_parse_status_line ((char *) result->stringval,
755 xmlXPathFreeObject (result);
761 xp_object_get_number (xmlXPathObjectPtr result)
765 if (result == NULL || result->type != XPATH_STRING) {
769 ret = result->boolval;
771 xmlXPathFreeObject (result);
776 /*** *** *** *** *** *** */
777 #define XPATH_HREF "string(/D:multistatus/D:response[%d]/D:href)"
778 #define XPATH_STATUS "string(/D:multistatus/D:response[%d]/D:propstat/D:status)"
779 #define XPATH_GETETAG_STATUS "string(/D:multistatus/D:response[%d]/D:propstat/D:prop/D:getetag/../../D:status)"
780 #define XPATH_GETETAG "string(/D:multistatus/D:response[%d]/D:propstat/D:prop/D:getetag)"
781 #define XPATH_CALENDAR_DATA "string(/D:multistatus/D:response[%d]/C:calendar-data)"
784 typedef struct _CalDAVObject CalDAVObject;
786 struct _CalDAVObject {
797 caldav_object_free (CalDAVObject *object, gboolean free_object_itself)
799 g_free (object->href);
800 g_free (object->etag);
801 g_free (object->cdata);
803 if (free_object_itself) {
809 parse_report_response (SoupMessage *soup_message, CalDAVObject **objs, int *len)
811 xmlXPathContextPtr xpctx;
812 xmlXPathObjectPtr result;
817 g_return_val_if_fail (soup_message != NULL, FALSE);
818 g_return_val_if_fail (objs != NULL || len != NULL, FALSE);
821 doc = xmlReadMemory (soup_message->response.body,
822 soup_message->response.length,
831 xpctx = xmlXPathNewContext (doc);
833 xmlXPathRegisterNs (xpctx, (xmlChar *) "D",
836 xmlXPathRegisterNs (xpctx, (xmlChar *) "C",
837 (xmlChar *) "urn:ietf:params:xml:ns:caldav");
839 result = xpath_eval (xpctx, "/D:multistatus/D:response");
841 if (result == NULL || result->type != XPATH_NODESET) {
847 n = xmlXPathNodeSetGetLength (result->nodesetval);
850 *objs = g_new0 (CalDAVObject, n);
852 for (i = 0; i < n; i++) {
853 CalDAVObject *object;
854 xmlXPathObjectPtr xpres;
857 /* see if we got a status child in the response element */
859 xpres = xpath_eval (xpctx, XPATH_HREF, i + 1);
860 object->href = xp_object_get_href (xpres);
862 xpres = xpath_eval (xpctx,XPATH_STATUS , i + 1);
863 object->status = xp_object_get_status (xpres);
865 //dump_xp_object (xpres);
866 if (object->status && object->status != 200) {
870 xpres = xpath_eval (xpctx, XPATH_GETETAG_STATUS, i + 1);
871 object->status = xp_object_get_status (xpres);
873 if (object->status != 200) {
877 xpres = xpath_eval (xpctx, XPATH_GETETAG, i + 1);
878 object->etag = xp_object_get_etag (xpres);
880 xpres = xpath_eval (xpctx, XPATH_CALENDAR_DATA, i + 1);
881 object->cdata = xp_object_get_string (xpres);
885 xmlXPathFreeContext (xpctx);
890 /* ************************************************************************* */
891 /* Authentication helpers for libsoup */
894 soup_authenticate (SoupSession *session,
896 const char *auth_type,
897 const char *auth_realm,
902 ECalBackendCalDAVPrivate *priv;
903 ECalBackendCalDAV *cbdav;
905 cbdav = E_CAL_BACKEND_CALDAV (data);
906 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
908 *username = priv->username;
909 *password = priv->password;
911 priv->username = NULL;
912 priv->password = NULL;
917 soup_reauthenticate (SoupSession *session,
919 const char *auth_type,
920 const char *auth_realm,
925 ECalBackendCalDAVPrivate *priv;
926 ECalBackendCalDAV *cbdav;
928 cbdav = E_CAL_BACKEND_CALDAV (data);
929 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
931 *username = priv->username;
932 *password = priv->password;
934 priv->username = NULL;
935 priv->password = NULL;
939 caldav_ignore_host(gconstpointer a, gconstpointer b)
941 gchar *hostname = (gchar*)a,
944 if (hostname && ignore)
945 return strcmp(hostname, ignore);
950 caldav_set_session_proxy(ECalBackendCalDAVPrivate *priv)
952 GConfClient *conf_client;
955 if (priv->session == NULL)
958 uri_base = soup_uri_new (priv->uri);
959 if (uri_base == NULL)
962 /* set the outbound HTTP proxy, if configuration is set to do so */
963 conf_client = gconf_client_get_default ();
964 if (gconf_client_get_bool (conf_client, "/system/http_proxy/use_http_proxy", NULL)) {
965 char *server, *proxy_uri;
967 GSList *ignore = gconf_client_get_list (conf_client,
968 "/system/http_proxy/ignore_hosts",
969 GCONF_VALUE_STRING, NULL);
970 if (ignore == NULL ||
971 g_slist_find_custom(ignore, uri_base->host, caldav_ignore_host) == NULL) {
972 server = gconf_client_get_string (conf_client, "/system/http_proxy/host", NULL);
973 port = gconf_client_get_int (conf_client, "/system/http_proxy/port", NULL);
975 if (server && server[0]) {
977 if (gconf_client_get_bool (conf_client, "/system/http_proxy/use_authentication", NULL)) {
978 char *user, *password;
979 user = gconf_client_get_string (conf_client,
980 "/system/http_proxy/authentication_user",
982 password = gconf_client_get_string (conf_client,
983 "/system/http_proxy/authentication_password",
986 proxy_uri = g_strdup_printf("http://%s:%s@%s:%d", user, password, server, port);
990 proxy_uri = g_strdup_printf ("http://%s:%d", server, port);
992 suri = soup_uri_new (proxy_uri);
993 g_object_set (G_OBJECT (priv->session), SOUP_SESSION_PROXY_URI, suri, NULL);
995 soup_uri_free (suri);
1000 g_slist_foreach(ignore, (GFunc) g_free, NULL);
1001 g_slist_free(ignore);
1003 soup_uri_free (uri_base);
1007 /* ************************************************************************* */
1008 /* direct CalDAV server access functions */
1011 caldav_generate_uri (ECalBackendCalDAV *cbdav, const char *target)
1013 ECalBackendCalDAVPrivate *priv;
1016 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1018 /* priv->uri must NOT have trailing slash */
1019 uri = g_strconcat (priv->uri, "/" , target, NULL);
1024 static ECalBackendSyncStatus
1025 caldav_server_open_calendar (ECalBackendCalDAV *cbdav)
1027 ECalBackendCalDAVPrivate *priv;
1028 SoupMessage *message;
1031 gboolean calendar_access;
1032 gboolean put_allowed;
1033 gboolean delete_allowed;
1035 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1037 /* FIXME: setup text_uri */
1039 message = soup_message_new (SOUP_METHOD_OPTIONS, priv->uri);
1040 soup_message_add_header (message->request_headers,
1041 "User-Agent", "Evolution/" VERSION);
1043 message_setup_debug (message);
1044 soup_session_send_message (priv->session, message);
1046 if (! SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1047 g_object_unref (message);
1049 return status_code_to_result (message->status_code);
1052 /* parse the dav header, we are intreseted in the
1053 * calendar-access bit only at the moment */
1054 sa = sm_join_and_split_header (message, "DAV");
1056 calendar_access = FALSE;
1057 for (siter = sa; siter && *siter; siter++) {
1059 if (match_header (*siter, "calendar-access")) {
1060 calendar_access = TRUE;
1068 sa = sm_join_and_split_header (message, "Allow");
1070 /* parse the Allow header and look for PUT, DELETE at the
1071 * moment (maybe we should check more here, for REPORT eg) */
1072 put_allowed = delete_allowed = FALSE;
1073 for (siter = sa; siter && *siter; siter++) {
1074 if (match_header (*siter, "DELETE")) {
1075 delete_allowed = TRUE;
1076 } else if (match_header (*siter, "PUT")) {
1080 if (put_allowed && delete_allowed) {
1087 g_object_unref (message);
1089 if (calendar_access) {
1090 priv->read_only = ! (put_allowed && delete_allowed);
1091 priv->do_synch = TRUE;
1092 return GNOME_Evolution_Calendar_Success;
1095 return GNOME_Evolution_Calendar_NoSuchCal;
1100 caldav_server_list_objects (ECalBackendCalDAV *cbdav, CalDAVObject **objs, int *len)
1102 ECalBackendCalDAVPrivate *priv;
1103 xmlOutputBufferPtr buf;
1104 SoupMessage *message;
1113 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1115 /* Maybe we should just do a g_strdup_printf here? */
1116 /* Prepare request body */
1117 doc = xmlNewDoc ((xmlChar *) "1.0");
1118 root = xmlNewNode (NULL, (xmlChar *) "calendar-query");
1119 nscd = xmlNewNs (root, (xmlChar *) "urn:ietf:params:xml:ns:caldav",
1121 xmlSetNs (root, nscd);
1123 /* Add webdav tags */
1124 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", (xmlChar *) "D");
1125 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1126 xmlNewTextChild (node, nsdav, (xmlChar *) "getetag", NULL);
1128 node = xmlNewTextChild (root, nscd, (xmlChar *) "filter", NULL);
1129 node = xmlNewTextChild (node, nscd, (xmlChar *) "comp-filter", NULL);
1130 xmlSetProp (node, (xmlChar *) "name", (xmlChar *) "VCALENDAR");
1132 sn = xmlNewTextChild (node, nscd, (xmlChar *) "comp-filter", NULL);
1133 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VEVENT");
1134 /* ^^^ add timerange for performance? */
1137 buf = xmlAllocOutputBuffer (NULL);
1138 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1139 xmlOutputBufferFlush (buf);
1141 /* Prepare the soup message */
1142 message = soup_message_new ("REPORT", priv->uri);
1143 soup_message_add_header (message->request_headers,
1144 "User-Agent", "Evolution/" VERSION);
1145 soup_message_add_header (message->request_headers,
1148 soup_message_set_request (message,
1150 SOUP_BUFFER_USER_OWNED,
1151 (char *) buf->buffer->content,
1154 message_setup_debug (message);
1156 /* Send the request now */
1157 soup_session_send_message (priv->session, message);
1159 /* Clean up the memory */
1160 xmlOutputBufferClose (buf);
1163 /* Check the result */
1164 if (message->status_code != 207) {
1165 g_warning ("Sever did not response with 207\n");
1169 /* Parse the response body */
1170 result = parse_report_response (message, objs, len);
1172 g_object_unref (message);
1177 static ECalBackendSyncStatus
1178 caldav_server_get_object (ECalBackendCalDAV *cbdav, CalDAVObject *object)
1180 ECalBackendCalDAVPrivate *priv;
1181 ECalBackendSyncStatus result;
1182 SoupMessage *message;
1186 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1187 result = GNOME_Evolution_Calendar_Success;
1189 g_assert (object != NULL && object->href != NULL);
1191 uri = caldav_generate_uri (cbdav, object->href);
1192 message = soup_message_new (SOUP_METHOD_GET, uri);
1195 soup_message_add_header (message->request_headers,
1196 "User-Agent", "Evolution/" VERSION);
1198 message_setup_debug (message);
1200 soup_session_send_message (priv->session, message);
1202 if (! SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1203 result = status_code_to_result (message->status_code);
1204 g_object_unref (message);
1205 g_warning ("Could not fetch object from server\n");
1209 hdr = soup_message_get_header (message->response_headers, "Content-Type");
1211 if (hdr == NULL || g_ascii_strcasecmp (hdr, "text/calendar")) {
1212 result = GNOME_Evolution_Calendar_InvalidObject;
1213 g_object_unref (message);
1214 g_warning ("Object to fetch not of type text/calendar");
1218 hdr = soup_message_get_header (message->response_headers, "ETag");
1221 g_warning ("UUHH no ETag, now that's bad!");
1222 object->etag = NULL;
1224 object->etag = quote_etag (hdr);
1227 /* Need to NULL terminate the string, do we? */
1228 object->cdata = g_malloc0 (message->response.length + 1);
1229 memcpy (object->cdata, message->response.body, message->response.length);
1230 g_object_unref (message);
1235 static ECalBackendSyncStatus
1236 caldav_server_put_object (ECalBackendCalDAV *cbdav, CalDAVObject *object)
1238 ECalBackendCalDAVPrivate *priv;
1239 ECalBackendSyncStatus result;
1240 SoupMessage *message;
1244 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1245 result = GNOME_Evolution_Calendar_Success;
1248 g_assert (object != NULL && object->cdata != NULL);
1250 uri = caldav_generate_uri (cbdav, object->href);
1251 message = soup_message_new (SOUP_METHOD_PUT, uri);
1254 soup_message_add_header (message->request_headers,
1255 "User-Agent", "Evolution/" VERSION);
1257 /* For new items we use the If-None-Match so we don't
1258 * acidently override resources, for item updates we
1259 * use the If-Match header to avoid the Lost-update
1261 if (object->etag == NULL) {
1262 soup_message_add_header (message->request_headers,
1263 "If-None-Match", "*");
1265 soup_message_add_header (message->request_headers,
1266 "If-Match", object->etag);
1269 soup_message_set_request (message,
1271 SOUP_BUFFER_USER_OWNED,
1273 strlen (object->cdata));
1276 message_setup_debug (message);
1278 soup_session_send_message (priv->session, message);
1280 /* FIXME: sepcial case precondition errors ?*/
1281 result = status_code_to_result (message->status_code);
1283 if (result == GNOME_Evolution_Calendar_Success) {
1284 hdr = soup_message_get_header (message->response_headers,
1289 g_free (object->etag);
1290 object->etag = quote_etag (hdr);
1292 g_warning ("Ups no Etag in put response");
1296 g_object_unref (message);
1300 static ECalBackendSyncStatus
1301 caldav_server_delete_object (ECalBackendCalDAV *cbdav, CalDAVObject *object)
1303 ECalBackendCalDAVPrivate *priv;
1304 ECalBackendSyncStatus result;
1305 SoupMessage *message;
1308 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1309 result = GNOME_Evolution_Calendar_Success;
1311 g_assert (object != NULL && object->href != NULL);
1313 uri = caldav_generate_uri (cbdav, object->href);
1314 message = soup_message_new (SOUP_METHOD_DELETE, uri);
1317 soup_message_add_header (message->request_headers,
1318 "User-Agent", "Evolution/" VERSION);
1320 if (object->etag != NULL) {
1321 soup_message_add_header (message->request_headers,
1322 "If-Match", object->etag);
1325 message_setup_debug (message);
1327 soup_session_send_message (priv->session, message);
1329 result = status_code_to_result (message->status_code);
1331 g_object_unref (message);
1336 /* ************************************************************************* */
1337 /* Synchronization foo */
1340 synchronize_object (ECalBackendCalDAV *cbdav,
1341 CalDAVObject *object,
1342 ECalComponent *old_comp)
1344 ECalBackendCalDAVPrivate *priv;
1345 ECalBackendCache *bcache;
1346 ECalBackendSyncStatus result;
1348 ECalComponent *comp;
1349 icalcomponent *icomp, *subcomp;
1350 icalcomponent_kind kind;
1356 result = caldav_server_get_object (cbdav, object);
1358 if (result != GNOME_Evolution_Calendar_Success) {
1359 g_warning ("Could not fetch object from server");
1363 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1365 icomp = icalparser_parse_string (object->cdata);
1366 kind = icalcomponent_isa (icomp);
1367 bkend = E_CAL_BACKEND (cbdav);
1369 if (kind == ICAL_VCALENDAR_COMPONENT) {
1371 kind = e_cal_backend_get_kind (bkend);
1372 subcomp = icalcomponent_get_first_component (icomp, kind);
1374 comp = e_cal_component_new ();
1375 res = e_cal_component_set_icalcomponent (comp,
1376 icalcomponent_new_clone (subcomp));
1378 e_cal_component_set_href (comp, object->href);
1379 e_cal_component_set_etag (comp, object->etag);
1381 g_object_unref (comp);
1389 icalcomponent_free (icomp);
1395 bcache = priv->cache;
1396 do_report = priv->report_changes;
1398 if ((res = e_cal_backend_cache_put_component (bcache, comp))
1400 char *new_cs = NULL;
1401 char *old_cs = NULL;
1403 new_cs = e_cal_component_get_as_string (comp);
1405 if (old_comp == NULL) {
1406 e_cal_backend_notify_object_created (bkend, new_cs);
1408 old_cs = e_cal_component_get_as_string (old_comp);
1409 e_cal_backend_notify_object_modified (bkend, old_cs, new_cs);
1416 g_object_unref (comp);
1421 #define etags_match(_tag1, _tag2) ((_tag1 == _tag2) ? TRUE : \
1422 g_str_equal (_tag1 != NULL ? _tag1 : "", \
1423 _tag2 != NULL ? _tag2 : ""))
1426 synchronize_cache (ECalBackendCalDAV *cbdav)
1428 ECalBackendCalDAVPrivate *priv;
1429 ECalBackendCache *bcache;
1430 CalDAVObject *sobjs;
1431 CalDAVObject *object;
1439 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1440 bcache = priv->cache;
1444 res = caldav_server_list_objects (cbdav, &sobjs, &len);
1448 g_warning ("Could not synch server BLehh!");
1452 hindex = g_hash_table_new (g_str_hash, g_str_equal);
1453 cobjs = e_cal_backend_cache_get_components (bcache);
1455 /* build up a index for the href entry */
1456 for (citer = cobjs; citer; citer = g_list_next (citer)) {
1457 ECalComponent *ccomp = E_CAL_COMPONENT (citer->data);
1460 href = e_cal_component_get_href (ccomp);
1463 g_warning ("href of object NULL :(");
1467 g_hash_table_insert (hindex, (gpointer) href, ccomp);
1470 /* see if we have to upate or add some objects */
1471 for (i = 0, object = sobjs; i < len; i++, object++) {
1472 ECalComponent *ccomp;
1473 const char *etag = NULL;
1475 if (object->status != 200) {
1476 /* just continue here, so that the object
1477 * doesnt get removed from the cobjs list
1478 * - therefore it will be removed */
1483 ccomp = g_hash_table_lookup (hindex, object->href);
1485 if (ccomp != NULL) {
1486 etag = e_cal_component_get_etag (ccomp);
1489 if (!etag || !etags_match (etag, object->etag)) {
1490 res = synchronize_object (cbdav, object, ccomp);
1494 cobjs = g_list_remove (cobjs, ccomp);
1497 caldav_object_free (object, FALSE);
1500 /* remove old (not on server anymore) items from cache */
1501 for (citer = cobjs; citer; citer = g_list_next (citer)) {
1502 ECalComponent *comp;
1505 comp = E_CAL_COMPONENT (citer->data);
1506 e_cal_component_get_uid (comp, &uid);
1508 if (e_cal_backend_cache_remove_component (bcache, uid, NULL) &&
1509 priv->report_changes) {
1510 char *str = e_cal_component_get_as_string (comp);
1511 ECalComponentId *id = e_cal_component_get_id (comp);
1513 e_cal_backend_notify_object_removed (E_CAL_BACKEND (cbdav),
1515 e_cal_component_free_id (id);
1519 g_object_unref (comp);
1522 g_hash_table_destroy (hindex);
1523 g_list_free (cobjs);
1527 /* ************************************************************************* */
1529 synch_slave_loop (gpointer data)
1531 ECalBackendCalDAVPrivate *priv;
1532 ECalBackendCalDAV *cbdav;
1534 cbdav = E_CAL_BACKEND_CALDAV (data);
1535 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1537 g_mutex_lock (priv->lock);
1539 while (priv->slave_cmd != SLAVE_SHOULD_DIE) {
1540 GTimeVal alarm_clock;
1541 if (priv->slave_cmd == SLAVE_SHOULD_SLEEP) {
1542 /* just sleep until we get woken up again */
1543 g_cond_wait (priv->cond, priv->lock);
1545 /* check if we should die, work or sleep again */
1549 /* Ok here we go, do some real work
1550 * Synch it baby one more time ...
1552 //d(g_print ("Synch-Slave: Goint to work ...\n")); XXX re-enable output please
1553 synchronize_cache (cbdav);
1555 /* puhh that was hard, get some rest :) */
1556 g_get_current_time (&alarm_clock);
1557 alarm_clock.tv_sec += priv->refresh_time.tv_sec;
1558 g_cond_timed_wait (priv->cond,
1564 /* we got killed ... */
1565 g_mutex_unlock (priv->lock);
1569 /* ************************************************************************* */
1570 /* ********** ECalBackendSync virtual function implementation ************* */
1572 static ECalBackendSyncStatus
1573 caldav_is_read_only (ECalBackendSync *backend,
1575 gboolean *read_only)
1577 ECalBackendCalDAV *cbdav;
1578 ECalBackendCalDAVPrivate *priv;
1580 cbdav = E_CAL_BACKEND_CALDAV (backend);
1581 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1583 /* no write support in offline mode yet! */
1584 if (priv->mode == CAL_MODE_LOCAL) {
1587 *read_only = priv->read_only;
1590 return GNOME_Evolution_Calendar_Success;
1594 static ECalBackendSyncStatus
1595 caldav_get_cal_address (ECalBackendSync *backend,
1600 return GNOME_Evolution_Calendar_Success;
1605 static ECalBackendSyncStatus
1606 caldav_get_ldap_attribute (ECalBackendSync *backend,
1611 return GNOME_Evolution_Calendar_Success;
1614 static ECalBackendSyncStatus
1615 caldav_get_alarm_email_address (ECalBackendSync *backend,
1620 return GNOME_Evolution_Calendar_Success;
1623 static ECalBackendSyncStatus
1624 caldav_get_static_capabilities (ECalBackendSync *backend,
1626 char **capabilities)
1628 *capabilities = g_strdup (CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS ","
1629 CAL_STATIC_CAPABILITY_NO_THISANDFUTURE ","
1630 CAL_STATIC_CAPABILITY_NO_THISANDPRIOR);
1632 return GNOME_Evolution_Calendar_Success;
1635 static ECalBackendSyncStatus
1636 initialize_backend (ECalBackendCalDAV *cbdav)
1638 ECalBackendSyncStatus result;
1639 ECalBackendCalDAVPrivate *priv;
1646 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1648 result = GNOME_Evolution_Calendar_Success;
1649 source = e_cal_backend_get_source (E_CAL_BACKEND (cbdav));
1651 os_val = e_source_get_property (source, "offline_sync");
1653 if (!os_val || !g_str_equal (os_val, "1")) {
1654 priv->do_offline = FALSE;
1657 os_val = e_source_get_property (source, "auth");
1660 priv->need_auth = TRUE;
1663 os_val = e_source_get_property(source, "ssl");
1664 uri = e_cal_backend_get_uri (E_CAL_BACKEND (cbdav));
1668 if (g_str_has_prefix (uri, "caldav://")) {
1671 if (os_val && os_val[0] == '1') {
1677 priv->uri = g_strconcat (proto, uri + 9, NULL);
1681 priv->uri = g_strdup (uri);
1684 /* remove trailing slashes */
1685 len = strlen (priv->uri);
1687 if (priv->uri[len] == '/') {
1688 priv->uri[len] = '\0';
1694 if (priv->cache == NULL) {
1695 priv->cache = e_cal_backend_cache_new (priv->uri, E_CAL_SOURCE_TYPE_EVENT);
1697 if (priv->cache == NULL) {
1698 result = GNOME_Evolution_Calendar_OtherError;
1704 priv->slave_cmd = SLAVE_SHOULD_SLEEP;
1705 slave = g_thread_create (synch_slave_loop, cbdav, FALSE, NULL);
1707 if (slave == NULL) {
1708 g_warning ("Could not create synch slave");
1709 result = GNOME_Evolution_Calendar_OtherError;
1712 priv->report_changes = TRUE;
1713 priv->synch_slave = slave;
1714 priv->loaded = TRUE;
1720 static ECalBackendSyncStatus
1721 caldav_do_open (ECalBackendSync *backend,
1723 gboolean only_if_exists,
1724 const char *username,
1725 const char *password)
1727 ECalBackendCalDAV *cbdav;
1728 ECalBackendCalDAVPrivate *priv;
1729 ECalBackendSyncStatus status;
1731 cbdav = E_CAL_BACKEND_CALDAV (backend);
1732 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1734 status = GNOME_Evolution_Calendar_Success;
1736 g_mutex_lock (priv->lock);
1738 if (priv->loaded != TRUE) {
1739 priv->ostatus = initialize_backend (cbdav);
1742 if (priv->ostatus != GNOME_Evolution_Calendar_Success) {
1743 g_mutex_unlock (priv->lock);
1748 if (priv->need_auth == TRUE) {
1749 if ((username == NULL || password == NULL)) {
1750 g_mutex_unlock (priv->lock);
1751 return GNOME_Evolution_Calendar_AuthenticationRequired;
1754 priv->username = g_strdup (username);
1755 priv->password = g_strdup (password);
1756 priv->need_auth = FALSE;
1759 if (! priv->do_offline && priv->mode == CAL_MODE_LOCAL) {
1760 g_mutex_unlock (priv->lock);
1761 return GNOME_Evolution_Calendar_RepositoryOffline;
1764 if (priv->mode == CAL_MODE_REMOTE) {
1765 /* set forward proxy */
1766 caldav_set_session_proxy (priv);
1768 status = caldav_server_open_calendar (cbdav);
1770 if (status == GNOME_Evolution_Calendar_Success) {
1771 priv->slave_cmd = SLAVE_SHOULD_WORK;
1772 g_cond_signal (priv->cond);
1775 priv->read_only = TRUE;
1778 g_mutex_unlock (priv->lock);
1783 static ECalBackendSyncStatus
1784 caldav_remove (ECalBackendSync *backend,
1787 ECalBackendCalDAV *cbdav;
1788 ECalBackendCalDAVPrivate *priv;
1789 ECalBackendSyncStatus status;
1792 cbdav = E_CAL_BACKEND_CALDAV (backend);
1793 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1795 g_mutex_lock (priv->lock);
1797 if (priv->loaded != TRUE) {
1798 g_mutex_unlock (priv->lock);
1799 return GNOME_Evolution_Calendar_Success;
1802 status = check_state (cbdav, &online);
1804 if (status != GNOME_Evolution_Calendar_Success) {
1805 g_mutex_unlock (priv->lock);
1809 e_file_cache_remove (E_FILE_CACHE (priv->cache));
1811 priv->loaded = FALSE;
1812 priv->slave_cmd = SLAVE_SHOULD_DIE;
1813 g_cond_signal (priv->cond);
1814 g_mutex_unlock (priv->lock);
1816 return GNOME_Evolution_Calendar_Success;
1821 pack_cobj (ECalBackendCalDAV *cbdav, ECalComponent *ecomp)
1823 ECalBackendCalDAVPrivate *priv;
1824 icalcomponent *calcomp;
1825 icalcomponent *icomp;
1828 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1830 icomp = e_cal_component_get_icalcomponent (ecomp);
1832 if (icalcomponent_isa (icomp) != ICAL_VCALENDAR_COMPONENT) {
1833 icalcomponent *cclone;
1835 calcomp = e_cal_util_new_top_level ();
1836 cclone = icalcomponent_new_clone (icomp);
1837 icalcomponent_add_component (calcomp, cclone);
1838 e_cal_util_add_timezones_from_component(calcomp,
1841 calcomp = icalcomponent_new_clone (icomp);
1844 objstr = icalcomponent_as_ical_string (calcomp);
1848 return g_strdup (objstr);
1853 static ECalBackendSyncStatus
1854 caldav_create_object (ECalBackendSync *backend,
1859 ECalBackendCalDAV *cbdav;
1860 ECalBackendCalDAVPrivate *priv;
1861 ECalBackendSyncStatus status;
1862 ECalComponent *comp;
1866 cbdav = E_CAL_BACKEND_CALDAV (backend);
1867 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1869 g_mutex_lock (priv->lock);
1871 status = check_state (cbdav, &online);
1873 if (status != GNOME_Evolution_Calendar_Success) {
1874 g_mutex_unlock (priv->lock);
1878 comp = e_cal_component_new_from_string (*calobj);
1881 g_mutex_unlock (priv->lock);
1882 return GNOME_Evolution_Calendar_InvalidObject;
1886 CalDAVObject object;
1888 href = e_cal_component_gen_href (comp);
1892 object.cdata = pack_cobj (cbdav, comp);
1894 status = caldav_server_put_object (cbdav, &object);
1896 e_cal_component_set_etag (comp, object.etag);
1897 caldav_object_free (&object, FALSE);
1900 /* mark component as out of synch */
1901 e_cal_component_set_synch_state (comp,
1902 E_CAL_COMPONENT_LOCALLY_CREATED);
1905 if (status != GNOME_Evolution_Calendar_Success) {
1906 g_object_unref (comp);
1907 g_mutex_unlock (priv->lock);
1911 /* We should prolly check for cache errors
1912 * but when that happens we are kinda hosed anyway */
1913 e_cal_backend_cache_put_component (priv->cache, comp);
1914 *calobj = e_cal_component_get_as_string (comp);
1916 g_mutex_unlock (priv->lock);
1921 static ECalBackendSyncStatus
1922 caldav_modify_object (ECalBackendSync *backend,
1929 ECalBackendCalDAV *cbdav;
1930 ECalBackendCalDAVPrivate *priv;
1931 ECalBackendSyncStatus status;
1932 ECalComponent *comp;
1933 ECalComponent *cache_comp;
1935 const char *uid = NULL;
1937 cbdav = E_CAL_BACKEND_CALDAV (backend);
1938 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1940 g_mutex_lock (priv->lock);
1942 status = check_state (cbdav, &online);
1944 if (status != GNOME_Evolution_Calendar_Success) {
1945 g_mutex_unlock (priv->lock);
1949 comp = e_cal_component_new_from_string (calobj);
1952 g_mutex_unlock (priv->lock);
1953 return GNOME_Evolution_Calendar_InvalidObject;
1956 e_cal_component_get_uid (comp, &uid);
1958 cache_comp = e_cal_backend_cache_get_component (priv->cache, uid, NULL);
1959 if (cache_comp == NULL) {
1960 g_mutex_unlock (priv->lock);
1961 return GNOME_Evolution_Calendar_ObjectNotFound;
1965 CalDAVObject object;
1967 object.href = g_strdup (e_cal_component_get_href (cache_comp));
1968 object.etag = g_strdup (e_cal_component_get_etag (cache_comp));
1969 object.cdata = pack_cobj (cbdav, comp);
1971 status = caldav_server_put_object (cbdav, &object);
1973 e_cal_component_set_etag (comp, object.etag);
1974 caldav_object_free (&object, FALSE);
1977 /* mark component as out of synch */
1978 e_cal_component_set_synch_state (comp,
1979 E_CAL_COMPONENT_LOCALLY_MODIFIED);
1982 if (status != GNOME_Evolution_Calendar_Success) {
1983 g_object_unref (comp);
1984 g_mutex_unlock (priv->lock);
1988 /* We should prolly check for cache errors
1989 * but when that happens we are kinda hosed anyway */
1990 e_cal_backend_cache_put_component (priv->cache, comp);
1991 *old_object = e_cal_component_get_as_string (cache_comp);
1992 *new_object = e_cal_component_get_as_string (comp);
1994 g_mutex_unlock (priv->lock);
1999 static ECalBackendSyncStatus
2000 caldav_remove_object (ECalBackendSync *backend,
2008 ECalBackendCalDAV *cbdav;
2009 ECalBackendCalDAVPrivate *priv;
2010 ECalBackendSyncStatus status;
2011 ECalComponent *cache_comp;
2014 cbdav = E_CAL_BACKEND_CALDAV (backend);
2015 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2017 g_mutex_lock (priv->lock);
2019 status = check_state (cbdav, &online);
2021 if (status != GNOME_Evolution_Calendar_Success) {
2022 g_mutex_unlock (priv->lock);
2026 cache_comp = e_cal_backend_cache_get_component (priv->cache, uid, rid);
2027 if (cache_comp == NULL) {
2028 g_mutex_unlock (priv->lock);
2029 return GNOME_Evolution_Calendar_ObjectNotFound;
2033 CalDAVObject caldav_object;
2035 caldav_object.href = g_strdup (e_cal_component_get_href (cache_comp));
2036 caldav_object.etag = g_strdup (e_cal_component_get_etag (cache_comp));
2037 caldav_object.cdata = NULL;
2039 status = caldav_server_delete_object (cbdav, &caldav_object);
2041 caldav_object_free (&caldav_object, FALSE);
2044 /* mark component as out of synch */
2045 e_cal_component_set_synch_state (cache_comp,
2046 E_CAL_COMPONENT_LOCALLY_DELETED);
2049 if (status != GNOME_Evolution_Calendar_Success) {
2050 g_mutex_unlock (priv->lock);
2054 *old_object = e_cal_component_get_as_string (cache_comp);
2056 /* We should prolly check for cache errors
2057 * but when that happens we are kinda hosed anyway */
2058 e_cal_backend_cache_remove_component (priv->cache, uid, rid);
2060 /* FIXME: set new_object when removing instances of a recurring appointment */
2062 g_mutex_unlock (priv->lock);
2067 static ECalBackendSyncStatus
2068 caldav_discard_alarm (ECalBackendSync *backend,
2073 return GNOME_Evolution_Calendar_Success;
2076 /* FIXME: use list here? */
2077 static ECalBackendSyncStatus
2078 extract_objects (icalcomponent *icomp,
2079 icalcomponent_kind ekind,
2082 icalcomponent *scomp;
2083 icalcomponent_kind kind;
2085 g_return_val_if_fail (icomp, GNOME_Evolution_Calendar_OtherError);
2086 g_return_val_if_fail (objects, GNOME_Evolution_Calendar_OtherError);
2088 kind = icalcomponent_isa (icomp);
2090 if (kind == ekind) {
2091 *objects = g_list_prepend (NULL, icomp);
2092 return GNOME_Evolution_Calendar_Success;
2095 if (kind != ICAL_VCALENDAR_COMPONENT) {
2096 return GNOME_Evolution_Calendar_InvalidObject;
2100 scomp = icalcomponent_get_first_component (icomp,
2105 /* Remove components from toplevel here */
2106 *objects = g_list_prepend (*objects, scomp);
2107 icalcomponent_remove_component (icomp, scomp);
2109 scomp = icalcomponent_get_next_component (icomp, ekind);
2112 return GNOME_Evolution_Calendar_Success;
2115 #define is_error(__status) (__status != GNOME_Evolution_Calendar_Success)
2117 static ECalBackendSyncStatus
2118 process_object (ECalBackendCalDAV *cbdav,
2119 ECalComponent *ecomp,
2121 icalproperty_method method)
2123 ECalBackendCalDAVPrivate *priv;
2124 ECalBackendSyncStatus status;
2125 ECalBackend *backend;
2126 ECalComponent *ccomp;
2127 struct icaltimetype now;
2128 ECalComponentId *id;
2134 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2135 backend = E_CAL_BACKEND (cbdav);
2138 now = icaltime_from_timet (time (NULL), 0);
2139 e_cal_component_set_created (ecomp, &now);
2140 e_cal_component_set_last_modified (ecomp, &now);
2142 e_cal_component_get_uid (ecomp, &uid);
2143 rid = e_cal_component_get_recurid_as_string (ecomp);
2145 ccomp = e_cal_backend_cache_get_component (priv->cache, uid, NULL);
2147 if (ccomp != NULL) {
2148 oostr = e_cal_component_get_as_string (ccomp);
2153 ostr = e_cal_component_get_as_string (ecomp);
2155 status = GNOME_Evolution_Calendar_Success;
2159 case ICAL_METHOD_PUBLISH:
2160 case ICAL_METHOD_REQUEST:
2161 case ICAL_METHOD_REPLY:
2164 CalDAVObject object = { NULL, };
2170 href = e_cal_component_get_href (ccomp);
2171 etag = e_cal_component_get_etag (ccomp);
2173 object.href = g_strdup (href);
2174 object.etag = g_strdup (etag);
2177 object.href = e_cal_component_gen_href (ecomp);
2180 object.cdata = pack_cobj (cbdav, ecomp);
2181 status = caldav_server_put_object (cbdav, &object);
2182 e_cal_component_set_etag (ecomp, object.etag);
2183 caldav_object_free (&object, FALSE);
2185 ECalComponentSyncState sstate;
2188 sstate = E_CAL_COMPONENT_LOCALLY_MODIFIED;
2190 sstate = E_CAL_COMPONENT_LOCALLY_CREATED;
2193 e_cal_component_set_synch_state (ecomp, sstate);
2197 if (status != GNOME_Evolution_Calendar_Success) {
2201 e_cal_backend_cache_put_component (priv->cache, ecomp);
2205 e_cal_backend_notify_object_modified (backend,
2211 e_cal_backend_notify_object_created (backend,
2218 case ICAL_METHOD_CANCEL:
2220 if (ccomp == NULL) {
2221 status = GNOME_Evolution_Calendar_ObjectNotFound;
2225 /* FIXME: this is not working for instances
2226 * of recurring appointments - yet - */
2228 CalDAVObject object;
2232 href = e_cal_component_get_href (ccomp);
2233 etag = e_cal_component_get_etag (ccomp);
2235 object.href = g_strdup (href);
2236 object.etag = g_strdup (etag);
2237 object.cdata = NULL;
2239 status = caldav_server_delete_object (cbdav,
2242 caldav_object_free (&object, FALSE);
2245 /* mark component as out of synch */
2246 e_cal_component_set_synch_state (ecomp,
2247 E_CAL_COMPONENT_LOCALLY_DELETED);
2251 if (status != GNOME_Evolution_Calendar_Success) {
2255 e_cal_backend_cache_remove_component (priv->cache,
2259 id = e_cal_component_get_id (ccomp);
2261 e_cal_backend_notify_object_removed (E_CAL_BACKEND (backend),
2269 status = GNOME_Evolution_Calendar_UnsupportedMethod;
2273 g_object_unref (ecomp);
2278 g_object_unref (ccomp);
2284 static ECalBackendSyncStatus
2285 caldav_receive_objects (ECalBackendSync *backend,
2289 ECalBackendCalDAV *cbdav;
2290 ECalBackendCalDAVPrivate *priv;
2291 ECalBackendSyncStatus status;
2292 icalcomponent *icomp;
2293 icalcomponent_kind kind;
2294 icalproperty_method tmethod;
2300 cbdav = E_CAL_BACKEND_CALDAV (backend);
2301 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2303 icomp = icalparser_parse_string (calobj);
2305 /* Try to parse cal object string */
2306 if (icomp == NULL) {
2307 return GNOME_Evolution_Calendar_InvalidObject;
2310 /* FIXME: use the e_cal_backend_xxx_kind call here */
2311 kind = ICAL_VEVENT_COMPONENT;
2312 status = extract_objects (icomp, kind, &objects);
2314 if (status != GNOME_Evolution_Calendar_Success) {
2318 /* Extract optional timezone compnents */
2319 kind = ICAL_VTIMEZONE_COMPONENT;
2320 status = extract_objects (icomp, kind, &timezones);
2322 if (status == GNOME_Evolution_Calendar_Success) {
2324 /* Do something usefull with the timezone */
2328 g_mutex_lock (priv->lock);
2330 status = check_state (cbdav, &online);
2332 if (status != GNOME_Evolution_Calendar_Success) {
2333 /* FIXME: free components here */
2334 g_mutex_unlock (priv->lock);
2338 tmethod = icalcomponent_get_method (icomp);
2340 for (iter = objects; iter && ! is_error (status); iter = iter->next) {
2341 icalcomponent *scomp;
2342 ECalComponent *ecomp;
2343 icalproperty_method method;
2345 scomp = (icalcomponent *) iter->data;
2346 ecomp = e_cal_component_new ();
2348 e_cal_component_set_icalcomponent (ecomp, scomp);
2350 if (icalcomponent_get_first_property (scomp,
2351 ICAL_METHOD_PROPERTY)) {
2353 method = icalcomponent_get_method (scomp);
2358 status = process_object (cbdav, ecomp, online, method);
2360 g_object_unref (ecomp);
2363 g_list_free (objects);
2364 g_list_free (timezones);
2366 g_mutex_unlock (priv->lock);
2371 static ECalBackendSyncStatus
2372 caldav_send_objects (ECalBackendSync *backend,
2376 char **modified_calobj)
2379 *modified_calobj = g_strdup (calobj);
2381 return GNOME_Evolution_Calendar_Success;
2384 static ECalBackendSyncStatus
2385 caldav_get_default_object (ECalBackendSync *backend,
2389 ECalComponent *comp;
2391 comp = e_cal_component_new ();
2392 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
2393 *object = e_cal_component_get_as_string (comp);
2394 g_object_unref (comp);
2396 return GNOME_Evolution_Calendar_Success;
2399 static ECalBackendSyncStatus
2400 caldav_get_object (ECalBackendSync *backend,
2406 ECalBackendCalDAV *cbdav;
2407 ECalBackendCalDAVPrivate *priv;
2408 ECalComponent *comp;
2410 cbdav = E_CAL_BACKEND_CALDAV (backend);
2411 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2413 g_mutex_lock (priv->lock);
2414 comp = e_cal_backend_cache_get_component (priv->cache, uid, rid);
2415 g_mutex_unlock (priv->lock);
2419 return GNOME_Evolution_Calendar_ObjectNotFound;
2422 *object = e_cal_component_get_as_string (comp);
2423 g_object_unref (comp);
2425 return GNOME_Evolution_Calendar_Success;
2428 static ECalBackendSyncStatus
2429 caldav_get_timezone (ECalBackendSync *backend,
2434 ECalBackendCalDAV *cbdav;
2435 ECalBackendCalDAVPrivate *priv;
2436 const icaltimezone *zone;
2437 icalcomponent *icalcomp;
2439 cbdav = E_CAL_BACKEND_CALDAV (backend);
2440 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2442 g_return_val_if_fail (tzid, GNOME_Evolution_Calendar_ObjectNotFound);
2444 /* first try to get the timezone from the cache */
2445 g_mutex_lock (priv->lock);
2446 zone = e_cal_backend_cache_get_timezone (priv->cache, tzid);
2447 g_mutex_unlock (priv->lock);
2450 zone = icaltimezone_get_builtin_timezone_from_tzid (tzid);
2452 return GNOME_Evolution_Calendar_ObjectNotFound;
2456 icalcomp = icaltimezone_get_component ((icaltimezone *) zone);
2459 return GNOME_Evolution_Calendar_InvalidObject;
2462 *object = g_strdup (icalcomponent_as_ical_string (icalcomp));
2464 return GNOME_Evolution_Calendar_Success;
2467 static ECalBackendSyncStatus
2468 caldav_add_timezone (ECalBackendSync *backend,
2472 /* FIXME: implement me! */
2473 g_warning ("function not implemented %s", G_STRFUNC);
2474 return GNOME_Evolution_Calendar_Success;
2477 static ECalBackendSyncStatus
2478 caldav_set_default_zone (ECalBackendSync *backend,
2482 /* FIXME: implement me! */
2483 g_warning ("function not implemented %s", G_STRFUNC);
2484 return GNOME_Evolution_Calendar_Success;
2487 static ECalBackendSyncStatus
2488 caldav_get_object_list (ECalBackendSync *backend,
2490 const char *sexp_string,
2493 ECalBackendCalDAV *cbdav;
2494 ECalBackendCalDAVPrivate *priv;
2495 ECalBackendSExp *sexp;
2496 ECalBackendCache *bcache;
2501 cbdav = E_CAL_BACKEND_CALDAV (backend);
2502 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2504 sexp = e_cal_backend_sexp_new (sexp_string);
2507 return GNOME_Evolution_Calendar_InvalidQuery;
2510 if (g_str_equal (sexp, "#t")) {
2517 bcache = priv->cache;
2519 g_mutex_lock (priv->lock);
2521 list = e_cal_backend_cache_get_components (bcache);
2522 bkend = E_CAL_BACKEND (backend);
2524 for (iter = list; iter; iter = g_list_next (iter)) {
2525 ECalComponent *comp = E_CAL_COMPONENT (iter->data);
2527 if (do_search == FALSE ||
2528 e_cal_backend_sexp_match_comp (sexp, comp, bkend)) {
2529 char *str = e_cal_component_get_as_string (comp);
2530 *objects = g_list_prepend (*objects, str);
2533 g_object_unref (comp);
2536 g_object_unref (sexp);
2539 g_mutex_unlock (priv->lock);
2541 return GNOME_Evolution_Calendar_Success;
2545 caldav_start_query (ECalBackend *backend,
2546 EDataCalView *query)
2548 ECalBackendCalDAV *cbdav;
2549 ECalBackendCalDAVPrivate *priv;
2550 ECalBackendSExp *sexp;
2554 const char *sexp_string;
2556 cbdav = E_CAL_BACKEND_CALDAV (backend);
2557 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2559 sexp_string = e_data_cal_view_get_text (query);
2560 sexp = e_cal_backend_sexp_new (sexp_string);
2562 /* FIXME:check invalid sexp */
2564 if (g_str_equal (sexp, "#t")) {
2570 g_mutex_lock (priv->lock);
2572 list = e_cal_backend_cache_get_components (priv->cache);
2573 bkend = E_CAL_BACKEND (backend);
2575 for (iter = list; iter; iter = g_list_next (iter)) {
2576 ECalComponent *comp = E_CAL_COMPONENT (iter->data);
2578 if (do_search == FALSE ||
2579 e_cal_backend_sexp_match_comp (sexp, comp, bkend)) {
2580 char *str = e_cal_component_get_as_string (comp);
2581 e_data_cal_view_notify_objects_added_1 (query, str);
2585 g_object_unref (comp);
2588 g_object_unref (sexp);
2592 e_data_cal_view_notify_done (query, GNOME_Evolution_Calendar_Success);
2593 g_mutex_unlock (priv->lock);
2597 static ECalBackendSyncStatus
2598 caldav_get_free_busy (ECalBackendSync *backend,
2605 /* FIXME: implement me! */
2606 g_warning ("function not implemented %s", G_STRFUNC);
2607 return GNOME_Evolution_Calendar_OtherError;
2610 static ECalBackendSyncStatus
2611 caldav_get_changes (ECalBackendSync *backend,
2613 const char *change_id,
2618 /* FIXME: implement me! */
2619 g_warning ("function not implemented %s", G_STRFUNC);
2620 return GNOME_Evolution_Calendar_OtherError;
2624 caldav_is_loaded (ECalBackend *backend)
2626 { ECalBackendCalDAV *cbdav;
2627 ECalBackendCalDAVPrivate *priv;
2629 cbdav = E_CAL_BACKEND_CALDAV (backend);
2630 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2632 return priv->loaded;
2636 caldav_get_mode (ECalBackend *backend)
2638 ECalBackendCalDAV *cbdav;
2639 ECalBackendCalDAVPrivate *priv;
2641 cbdav = E_CAL_BACKEND_CALDAV (backend);
2642 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2648 caldav_set_mode (ECalBackend *backend, CalMode mode)
2650 ECalBackendCalDAV *cbdav;
2651 ECalBackendCalDAVPrivate *priv;
2653 cbdav = E_CAL_BACKEND_CALDAV (backend);
2654 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2656 g_mutex_lock (priv->lock);
2658 /* We only support online and offline
2659 * (is there something else?) */
2660 if (mode != CAL_MODE_REMOTE &&
2661 mode != CAL_MODE_LOCAL) {
2662 e_cal_backend_notify_mode (backend,
2663 GNOME_Evolution_Calendar_CalListener_MODE_NOT_SUPPORTED,
2664 cal_mode_to_corba (mode));
2667 if (priv->mode == mode || priv->loaded == FALSE) {
2669 e_cal_backend_notify_mode (backend,
2670 GNOME_Evolution_Calendar_CalListener_MODE_SET,
2671 cal_mode_to_corba (mode));
2672 g_mutex_unlock (priv->lock);
2676 if (mode == CAL_MODE_REMOTE) {
2677 /* Wake up the slave thread */
2678 priv->slave_cmd = SLAVE_SHOULD_WORK;
2679 g_cond_signal (priv->cond);
2681 soup_session_abort (priv->session);
2682 priv->slave_cmd = SLAVE_SHOULD_SLEEP;
2685 e_cal_backend_notify_mode (backend,
2686 GNOME_Evolution_Calendar_CalListener_MODE_SET,
2687 cal_mode_to_corba (mode));
2689 g_mutex_unlock (priv->lock);
2692 static icaltimezone *
2693 caldav_internal_get_default_timezone (ECalBackend *backend)
2695 return icaltimezone_get_utc_timezone ();
2698 static icaltimezone *
2699 caldav_internal_get_timezone (ECalBackend *backend,
2704 zone = icaltimezone_get_builtin_timezone_from_tzid (tzid);
2707 zone = icaltimezone_get_utc_timezone ();
2713 /* ************************************************************************* */
2714 /* ***************************** GObject Foo ******************************* */
2716 G_DEFINE_TYPE (ECalBackendCalDAV, e_cal_backend_caldav, E_TYPE_CAL_BACKEND_SYNC);
2719 e_cal_backend_caldav_dispose (GObject *object)
2721 ECalBackendCalDAV *cbdav;
2722 ECalBackendCalDAVPrivate *priv;
2724 cbdav = E_CAL_BACKEND_CALDAV (object);
2725 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2727 g_mutex_lock (priv->lock);
2729 if (priv->disposed == TRUE) {
2730 g_mutex_unlock (priv->lock);
2734 /* stop the slave */
2735 priv->slave_cmd = SLAVE_SHOULD_DIE;
2736 g_cond_signal (priv->cond);
2737 g_mutex_unlock (priv->lock);
2739 /* wait until the slave died */
2740 g_mutex_lock (priv->lock);
2742 g_object_unref (priv->session);
2744 g_free (priv->username);
2745 g_free (priv->password);
2748 if (priv->cache != NULL) {
2749 g_object_unref (priv->cache);
2752 priv->disposed = TRUE;
2753 g_mutex_unlock (priv->lock);
2755 if (G_OBJECT_CLASS (parent_class)->dispose)
2756 (* G_OBJECT_CLASS (parent_class)->dispose) (object);
2760 e_cal_backend_caldav_finalize (GObject *object)
2762 ECalBackendCalDAV *cbdav;
2763 ECalBackendCalDAVPrivate *priv;
2765 cbdav = E_CAL_BACKEND_CALDAV (object);
2766 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2768 g_mutex_free (priv->lock);
2769 g_cond_free (priv->cond);
2771 if (G_OBJECT_CLASS (parent_class)->finalize)
2772 (* G_OBJECT_CLASS (parent_class)->finalize) (object);
2777 e_cal_backend_caldav_init (ECalBackendCalDAV *cbdav)
2779 ECalBackendCalDAVPrivate *priv;
2780 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2782 priv->session = soup_session_sync_new ();
2784 priv->disposed = FALSE;
2785 priv->do_synch = FALSE;
2786 priv->loaded = FALSE;
2788 priv->cond = g_cond_new ();
2789 priv->lock = g_mutex_new ();
2791 /* Slave control ... */
2792 priv->slave_cmd = SLAVE_SHOULD_SLEEP;
2793 priv->refresh_time.tv_usec = 0;
2794 priv->refresh_time.tv_sec = DEFAULT_REFRESH_TIME;
2796 g_signal_connect (priv->session, "authenticate",
2797 G_CALLBACK (soup_authenticate), cbdav);
2798 g_signal_connect (priv->session, "reauthenticate",
2799 G_CALLBACK (soup_reauthenticate), cbdav);
2801 e_cal_backend_sync_set_lock (E_CAL_BACKEND_SYNC (cbdav), FALSE);
2806 e_cal_backend_caldav_class_init (ECalBackendCalDAVClass *class)
2808 GObjectClass *object_class;
2809 ECalBackendClass *backend_class;
2810 ECalBackendSyncClass *sync_class;
2812 object_class = (GObjectClass *) class;
2813 backend_class = (ECalBackendClass *) class;
2814 sync_class = (ECalBackendSyncClass *) class;
2816 caldav_debug_init ();
2818 parent_class = (ECalBackendSyncClass *) g_type_class_peek_parent (class);
2819 g_type_class_add_private (class, sizeof (ECalBackendCalDAVPrivate));
2821 object_class->dispose = e_cal_backend_caldav_dispose;
2822 object_class->finalize = e_cal_backend_caldav_finalize;
2824 sync_class->is_read_only_sync = caldav_is_read_only;
2825 sync_class->get_cal_address_sync = caldav_get_cal_address;
2826 sync_class->get_alarm_email_address_sync = caldav_get_alarm_email_address;
2827 sync_class->get_ldap_attribute_sync = caldav_get_ldap_attribute;
2828 sync_class->get_static_capabilities_sync = caldav_get_static_capabilities;
2830 sync_class->open_sync = caldav_do_open;
2831 sync_class->remove_sync = caldav_remove;
2833 sync_class->create_object_sync = caldav_create_object;
2834 sync_class->modify_object_sync = caldav_modify_object;
2835 sync_class->remove_object_sync = caldav_remove_object;
2837 sync_class->discard_alarm_sync = caldav_discard_alarm;
2838 sync_class->receive_objects_sync = caldav_receive_objects;
2839 sync_class->send_objects_sync = caldav_send_objects;
2840 sync_class->get_default_object_sync = caldav_get_default_object;
2841 sync_class->get_object_sync = caldav_get_object;
2842 sync_class->get_object_list_sync = caldav_get_object_list;
2843 sync_class->get_timezone_sync = caldav_get_timezone;
2844 sync_class->add_timezone_sync = caldav_add_timezone;
2845 sync_class->set_default_zone_sync = caldav_set_default_zone;
2846 sync_class->get_freebusy_sync = caldav_get_free_busy;
2847 sync_class->get_changes_sync = caldav_get_changes;
2849 backend_class->is_loaded = caldav_is_loaded;
2850 backend_class->start_query = caldav_start_query;
2851 backend_class->get_mode = caldav_get_mode;
2852 backend_class->set_mode = caldav_set_mode;
2854 backend_class->internal_get_default_timezone = caldav_internal_get_default_timezone;
2855 backend_class->internal_get_timezone = caldav_internal_get_timezone;