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/gstdio.h>
30 #include <glib/gi18n-lib.h>
31 #include <libedataserver/e-data-server-util.h>
32 #include <libedataserver/e-xml-hash-utils.h>
33 #include <libedataserver/e-proxy.h>
34 #include <libecal/e-cal-recur.h>
35 #include <libecal/e-cal-util.h>
36 #include <libecal/e-cal-time-util.h>
37 #include <libedata-cal/e-cal-backend-cache.h>
38 #include <libedata-cal/e-cal-backend-file-store.h>
39 #include <libedata-cal/e-cal-backend-util.h>
40 #include <libedata-cal/e-cal-backend-sexp.h>
42 /* LibXML2 includes */
43 #include <libxml/parser.h>
44 #include <libxml/tree.h>
45 #include <libxml/xpath.h>
46 #include <libxml/xpathInternals.h>
48 /* LibSoup includes */
49 #include <libsoup/soup.h>
51 #include "e-cal-backend-caldav.h"
55 #define CALDAV_CTAG_KEY "CALDAV_CTAG"
56 #define CALDAV_MAX_MULTIGET_AMOUNT 100 /* what's the maximum count of items to fetch within a multiget request */
57 #define LOCAL_PREFIX "file://"
60 #define DEFAULT_REFRESH_TIME 60
62 #define EDC_ERROR(_code) e_data_cal_create_error (_code, NULL)
63 #define EDC_ERROR_EX(_code, _msg) e_data_cal_create_error (_code, _msg)
73 /* Private part of the ECalBackendHttp structure */
74 struct _ECalBackendCalDAVPrivate {
76 /* The local disk cache */
77 ECalBackendStore *store;
79 /* should we sync for offline mode? */
82 /* TRUE after caldav_open */
84 /* TRUE when server reachable */
87 /* lock to indicate a busy state */
90 /* cond to synch threads */
93 /* cond to know the slave gone */
94 GCond *slave_gone_cond;
97 const GThread *synch_slave; /* just for a reference, whether thread exists */
98 SlaveCommand slave_cmd;
99 gboolean slave_busy; /* whether is slave working */
100 GTimeVal refresh_time;
102 /* The main soup session */
103 SoupSession *session;
106 /* well, guess what */
112 /* Authentication info */
113 ECredentials *credentials;
114 gboolean auth_required;
119 /* support for 'getctag' extension */
120 gboolean ctag_supported;
121 gchar *ctag_to_store;
123 /* TRUE when 'calendar-schedule' supported on the server */
124 gboolean calendar_schedule;
125 /* with 'calendar-schedule' supported, here's an outbox url
126 * for queries of free/busy information */
127 gchar *schedule_outbox_url;
129 /* "Temporary hack" to indicate it's talking to a google calendar.
130 * The proper solution should be to subclass whole backend and change only
131 * necessary parts in it, but this will give us more freedom, as also direct
132 * caldav calendars can profit from this. */
136 /* ************************************************************************* */
139 #define DEBUG_MESSAGE "message"
140 #define DEBUG_MESSAGE_HEADER "message:header"
141 #define DEBUG_MESSAGE_BODY "message:body"
142 #define DEBUG_SERVER_ITEMS "items"
143 #define DEBUG_ATTACHMENTS "attachments"
145 static void convert_to_inline_attachment (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp);
146 static void convert_to_url_attachment (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp);
147 static void remove_cached_attachment (ECalBackendCalDAV *cbdav, const gchar *uid);
149 static gboolean caldav_debug_all = FALSE;
150 static GHashTable *caldav_debug_table = NULL;
153 add_debug_key (const gchar *start,
163 debug_key = debug_value = g_strndup (start, end - start);
165 debug_key = g_strchug (debug_key);
166 debug_key = g_strchomp (debug_key);
168 if (strlen (debug_key) == 0) {
169 g_free (debug_value);
173 g_hash_table_insert (caldav_debug_table,
177 d(g_debug ("Adding %s to enabled debugging keys", debug_key));
181 caldav_debug_init_once (gpointer data)
185 dbg = g_getenv ("CALDAV_DEBUG");
190 d(g_debug ("Got debug env variable: [%s]", dbg));
192 caldav_debug_table = g_hash_table_new (g_str_hash,
197 while (*ptr != '\0') {
198 if (*ptr == ',' || *ptr == ':') {
200 add_debug_key (dbg, ptr);
211 add_debug_key (dbg, ptr);
214 if (g_hash_table_lookup (caldav_debug_table, "all")) {
215 caldav_debug_all = TRUE;
216 g_hash_table_destroy (caldav_debug_table);
217 caldav_debug_table = NULL;
225 caldav_debug_init (void)
227 static GOnce debug_once = G_ONCE_INIT;
230 caldav_debug_init_once,
235 caldav_debug_show (const gchar *component)
237 if (G_UNLIKELY (caldav_debug_all)) {
239 } else if (G_UNLIKELY (caldav_debug_table != NULL) &&
240 g_hash_table_lookup (caldav_debug_table, component)) {
247 #define DEBUG_MAX_BODY_SIZE (100 * 1024 * 1024)
250 caldav_debug_setup (SoupSession *session)
253 SoupLoggerLogLevel level;
255 if (caldav_debug_show (DEBUG_MESSAGE_BODY))
256 level = SOUP_LOGGER_LOG_BODY;
257 else if (caldav_debug_show (DEBUG_MESSAGE_HEADER))
258 level = SOUP_LOGGER_LOG_HEADERS;
260 level = SOUP_LOGGER_LOG_MINIMAL;
262 logger = soup_logger_new (level, DEBUG_MAX_BODY_SIZE);
263 soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
264 g_object_unref (logger);
267 /* TODO Do not replicate this in every backend */
268 static icaltimezone *
269 resolve_tzid (const gchar *tzid,
274 zone = (!strcmp (tzid, "UTC"))
275 ? icaltimezone_get_utc_timezone ()
276 : icaltimezone_get_builtin_timezone_from_tzid (tzid);
279 zone = e_cal_backend_internal_get_timezone (E_CAL_BACKEND (user_data), tzid);
285 put_component_to_store (ECalBackendCalDAV *cbdav,
288 time_t time_start, time_end;
289 ECalBackendCalDAVPrivate *priv;
293 e_cal_util_get_component_occur_times (comp, &time_start, &time_end,
294 resolve_tzid, cbdav, icaltimezone_get_utc_timezone (),
295 e_cal_backend_get_kind (E_CAL_BACKEND (cbdav)));
297 return e_cal_backend_store_put_component_with_time_range (priv->store, comp, time_start, time_end);
300 static ECalBackendSyncClass *parent_class = NULL;
302 static icaltimezone *caldav_internal_get_timezone (ECalBackend *backend, const gchar *tzid);
303 static void caldav_source_changed_cb (ESource *source, ECalBackendCalDAV *cbdav);
305 static gboolean remove_comp_from_cache (ECalBackendCalDAV *cbdav, const gchar *uid, const gchar *rid);
306 static gboolean put_comp_to_cache (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp, const gchar *href, const gchar *etag);
308 /* ************************************************************************* */
309 /* Misc. utility functions */
310 #define X_E_CALDAV "X-EVOLUTION-CALDAV-"
311 #define X_E_CALDAV_ATTACHMENT_NAME X_E_CALDAV "ATTACHMENT-NAME"
314 icomp_x_prop_set (icalcomponent *comp,
320 /* Find the old one first */
321 xprop = icalcomponent_get_first_property (comp, ICAL_X_PROPERTY);
324 const gchar *str = icalproperty_get_x_name (xprop);
326 if (!strcmp (str, key)) {
328 icalproperty_set_value_from_string (xprop, value, "NO");
330 icalcomponent_remove_property (comp, xprop);
331 icalproperty_free (xprop);
336 xprop = icalcomponent_get_next_property (comp, ICAL_X_PROPERTY);
339 if (!xprop && value) {
340 xprop = icalproperty_new_x (value);
341 icalproperty_set_x_name (xprop, key);
342 icalcomponent_add_property (comp, xprop);
347 icomp_x_prop_get (icalcomponent *comp,
352 /* Find the old one first */
353 xprop = icalcomponent_get_first_property (comp, ICAL_X_PROPERTY);
356 const gchar *str = icalproperty_get_x_name (xprop);
358 if (!strcmp (str, key)) {
362 xprop = icalcomponent_get_next_property (comp, ICAL_X_PROPERTY);
366 return icalproperty_get_value_as_string_r (xprop);
372 /* passing NULL as 'href' removes the property */
374 ecalcomp_set_href (ECalComponent *comp,
377 icalcomponent *icomp;
379 icomp = e_cal_component_get_icalcomponent (comp);
380 g_return_if_fail (icomp != NULL);
382 icomp_x_prop_set (icomp, X_E_CALDAV "HREF", href);
386 ecalcomp_get_href (ECalComponent *comp)
388 icalcomponent *icomp;
392 icomp = e_cal_component_get_icalcomponent (comp);
393 g_return_val_if_fail (icomp != NULL, NULL);
395 str = icomp_x_prop_get (icomp, X_E_CALDAV "HREF");
400 /* passing NULL as 'etag' removes the property */
402 ecalcomp_set_etag (ECalComponent *comp,
405 icalcomponent *icomp;
407 icomp = e_cal_component_get_icalcomponent (comp);
408 g_return_if_fail (icomp != NULL);
410 icomp_x_prop_set (icomp, X_E_CALDAV "ETAG", etag);
414 ecalcomp_get_etag (ECalComponent *comp)
416 icalcomponent *icomp;
420 icomp = e_cal_component_get_icalcomponent (comp);
421 g_return_val_if_fail (icomp != NULL, NULL);
423 str = icomp_x_prop_get (icomp, X_E_CALDAV "ETAG");
430 / * object is in synch,
431 * now isnt that ironic? :) * /
432 ECALCOMP_IN_SYNCH = 0,
434 / * local changes * /
435 ECALCOMP_LOCALLY_CREATED,
436 ECALCOMP_LOCALLY_DELETED,
437 ECALCOMP_LOCALLY_MODIFIED
441 / * oos = out of synch * /
443 ecalcomp_set_synch_state (ECalComponent *comp,
444 * ECalCompSyncState state)
446 icalcomponent *icomp;
449 icomp = e_cal_component_get_icalcomponent (comp);
451 state_string = g_strdup_printf ("%d", state);
453 icomp_x_prop_set (icomp, X_E_CALDAV "ETAG", state_string);
455 g_free (state_string);
459 ecalcomp_gen_href (ECalComponent *comp)
461 gchar *href, *uid, *tmp;
462 icalcomponent *icomp;
464 icomp = e_cal_component_get_icalcomponent (comp);
465 g_return_val_if_fail (icomp != NULL, NULL);
467 uid = g_strdup (icalcomponent_get_uid (icomp));
470 uid = e_cal_component_gen_uid ();
472 tmp = uid ? strchr (uid, '@') : NULL;
478 tmp = isodate_from_time_t (time (NULL));
480 /* quite long, but ensures uniqueness quite well, without using UUIDs */
481 href = g_strconcat (uid ? uid : "no-uid", tmp ? "-" : "", tmp ? tmp : "", ".ics", NULL);
486 icomp_x_prop_set (icomp, X_E_CALDAV "HREF", href);
488 return g_strdelimit (href, " /'\"`&();|<>$%{}!\\:*?#@", '_');
491 /* ensure etag is quoted (to workaround potential server bugs) */
493 quote_etag (const gchar *etag)
497 if (etag && (strlen (etag) < 2 || etag[strlen (etag) - 1] != '\"')) {
498 ret = g_strdup_printf ("\"%s\"", etag);
500 ret = g_strdup (etag);
506 /* ************************************************************************* */
509 status_code_to_result (SoupMessage *message,
510 ECalBackendCalDAVPrivate *priv,
513 g_return_val_if_fail (message != NULL, FALSE);
515 if (SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
519 switch (message->status_code) {
520 case SOUP_STATUS_CANT_CONNECT:
521 case SOUP_STATUS_CANT_CONNECT_PROXY:
522 g_propagate_error (perror,
523 e_data_cal_create_error_fmt (
525 _("Server is unreachable (%s)"),
526 message->reason_phrase && *message->reason_phrase ? message->reason_phrase :
527 (soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : _("Unknown error"))));
529 priv->opened = FALSE;
530 priv->read_only = TRUE;
534 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
538 g_propagate_error (perror, EDC_ERROR (AuthenticationFailed));
542 if (priv && priv->auth_required)
543 g_propagate_error (perror, EDC_ERROR (AuthenticationFailed));
545 g_propagate_error (perror, EDC_ERROR (AuthenticationRequired));
549 d(g_debug ("CalDAV:%s: Unhandled status code %d\n", G_STRFUNC, status_code));
550 g_propagate_error (perror,
551 e_data_cal_create_error_fmt (
553 _("Unexpected HTTP status code %d returned (%s)"),
554 message->status_code,
555 message->reason_phrase && *message->reason_phrase ? message->reason_phrase :
556 (soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : _("Unknown error"))));
563 /* !TS, call with lock held */
565 check_state (ECalBackendCalDAV *cbdav,
569 ECalBackendCalDAVPrivate *priv;
576 g_propagate_error (perror, EDC_ERROR_EX (OtherError, "Not loaded"));
580 if (!e_backend_get_online (E_BACKEND (cbdav))) {
582 if (!priv->do_offline) {
583 g_propagate_error (perror, EDC_ERROR (RepositoryOffline));
594 /* ************************************************************************* */
595 /* XML Parsing code */
597 static xmlXPathObjectPtr
598 xpath_eval (xmlXPathContextPtr ctx,
602 xmlXPathObjectPtr result;
610 va_start (args, format);
611 expr = g_strdup_vprintf (format, args);
614 result = xmlXPathEvalExpression ((xmlChar *) expr, ctx);
617 if (result == NULL) {
621 if (result->type == XPATH_NODESET &&
622 xmlXPathNodeSetIsEmpty (result->nodesetval)) {
623 xmlXPathFreeObject (result);
632 parse_status_node (xmlNodePtr node,
638 content = xmlNodeGetContent (node);
640 res = soup_headers_parse_status_line ((gchar *) content,
651 xp_object_get_string (xmlXPathObjectPtr result)
658 if (result->type == XPATH_STRING) {
659 ret = g_strdup ((gchar *) result->stringval);
662 xmlXPathFreeObject (result);
666 /* like get_string but will quote the etag if necessary */
668 xp_object_get_etag (xmlXPathObjectPtr result)
676 if (result->type == XPATH_STRING) {
677 str = (gchar *) result->stringval;
679 ret = quote_etag (str);
682 xmlXPathFreeObject (result);
687 xp_object_get_status (xmlXPathObjectPtr result)
695 if (result->type == XPATH_STRING) {
696 res = soup_headers_parse_status_line ((gchar *) result->stringval,
706 xmlXPathFreeObject (result);
712 xp_object_get_number (xmlXPathObjectPtr result)
719 if (result->type == XPATH_STRING) {
720 ret = result->boolval;
723 xmlXPathFreeObject (result);
728 /*** *** *** *** *** *** */
729 #define XPATH_HREF "string(/D:multistatus/D:response[%d]/D:href)"
730 #define XPATH_STATUS "string(/D:multistatus/D:response[%d]/D:propstat/D:status)"
731 #define XPATH_GETETAG_STATUS "string(/D:multistatus/D:response[%d]/D:propstat/D:prop/D:getetag/../../D:status)"
732 #define XPATH_GETETAG "string(/D:multistatus/D:response[%d]/D:propstat/D:prop/D:getetag)"
733 #define XPATH_CALENDAR_DATA "string(/D:multistatus/D:response[%d]/D:propstat/D:prop/C:calendar-data)"
734 #define XPATH_GETCTAG_STATUS "string(/D:multistatus/D:response/D:propstat/D:prop/CS:getctag/../../D:status)"
735 #define XPATH_GETCTAG "string(/D:multistatus/D:response/D:propstat/D:prop/CS:getctag)"
736 #define XPATH_OWNER_STATUS "string(/D:multistatus/D:response/D:propstat/D:prop/D:owner/D:href/../../../D:status)"
737 #define XPATH_OWNER "string(/D:multistatus/D:response/D:propstat/D:prop/D:owner/D:href)"
738 #define XPATH_SCHEDULE_OUTBOX_URL_STATUS "string(/D:multistatus/D:response/D:propstat/D:prop/C:schedule-outbox-URL/D:href/../../../D:status)"
739 #define XPATH_SCHEDULE_OUTBOX_URL "string(/D:multistatus/D:response/D:propstat/D:prop/C:schedule-outbox-URL/D:href)"
741 typedef struct _CalDAVObject CalDAVObject;
743 struct _CalDAVObject {
754 caldav_object_free (CalDAVObject *object,
755 gboolean free_object_itself)
757 g_free (object->href);
758 g_free (object->etag);
759 g_free (object->cdata);
761 if (free_object_itself) {
767 parse_report_response (SoupMessage *soup_message,
771 xmlXPathContextPtr xpctx;
772 xmlXPathObjectPtr result;
777 g_return_val_if_fail (soup_message != NULL, FALSE);
778 g_return_val_if_fail (objs != NULL || len != NULL, FALSE);
781 doc = xmlReadMemory (soup_message->response_body->data,
782 soup_message->response_body->length,
791 xpctx = xmlXPathNewContext (doc);
793 xmlXPathRegisterNs (xpctx, (xmlChar *) "D",
796 xmlXPathRegisterNs (xpctx, (xmlChar *) "C",
797 (xmlChar *) "urn:ietf:params:xml:ns:caldav");
799 result = xpath_eval (xpctx, "/D:multistatus/D:response");
801 if (result == NULL || result->type != XPATH_NODESET) {
807 n = xmlXPathNodeSetGetLength (result->nodesetval);
810 *objs = g_new0 (CalDAVObject, n);
812 for (i = 0; i < n; i++) {
813 CalDAVObject *object;
814 xmlXPathObjectPtr xpres;
817 /* see if we got a status child in the response element */
819 xpres = xpath_eval (xpctx, XPATH_HREF, i + 1);
820 /* use full path from a href, to let calendar-multiget work properly */
821 object->href = xp_object_get_string (xpres);
823 xpres = xpath_eval (xpctx,XPATH_STATUS , i + 1);
824 object->status = xp_object_get_status (xpres);
826 if (object->status && object->status != 200) {
830 xpres = xpath_eval (xpctx, XPATH_GETETAG_STATUS, i + 1);
831 object->status = xp_object_get_status (xpres);
833 if (object->status != 200) {
837 xpres = xpath_eval (xpctx, XPATH_GETETAG, i + 1);
838 object->etag = xp_object_get_etag (xpres);
840 xpres = xpath_eval (xpctx, XPATH_CALENDAR_DATA, i + 1);
841 object->cdata = xp_object_get_string (xpres);
846 xmlXPathFreeObject (result);
847 xmlXPathFreeContext (xpctx);
852 /* returns whether was able to read the xpath_value from the server's response; *value contains the result */
854 parse_propfind_response (SoupMessage *message,
855 const gchar *xpath_status,
856 const gchar *xpath_value,
859 xmlXPathContextPtr xpctx;
861 gboolean res = FALSE;
863 g_return_val_if_fail (message != NULL, FALSE);
864 g_return_val_if_fail (value != NULL, FALSE);
866 doc = xmlReadMemory (message->response_body->data,
867 message->response_body->length,
876 xpctx = xmlXPathNewContext (doc);
877 xmlXPathRegisterNs (xpctx, (xmlChar *) "D", (xmlChar *) "DAV:");
878 xmlXPathRegisterNs (xpctx, (xmlChar *) "C", (xmlChar *) "urn:ietf:params:xml:ns:caldav");
879 xmlXPathRegisterNs (xpctx, (xmlChar *) "CS", (xmlChar *) "http://calendarserver.org/ns/");
881 if (xpath_status == NULL || xp_object_get_status (xpath_eval (xpctx, xpath_status)) == 200) {
882 gchar *txt = xp_object_get_string (xpath_eval (xpctx, xpath_value));
885 gint len = strlen (txt);
887 if (*txt == '\"' && len > 2 && txt [len - 1] == '\"') {
889 *value = g_strndup (txt + 1, len - 2);
895 res = (*value) != NULL;
901 xmlXPathFreeContext (xpctx);
907 /* ************************************************************************* */
908 /* Authentication helpers for libsoup */
911 soup_authenticate (SoupSession *session,
917 ECalBackendCalDAVPrivate *priv;
918 ECalBackendCalDAV *cbdav;
920 cbdav = E_CAL_BACKEND_CALDAV (data);
923 /* do not send same password twice, but keep it for later use */
924 if (!retrying && priv->credentials && e_credentials_has_key (priv->credentials, E_CREDENTIALS_KEY_USERNAME)) {
925 soup_auth_authenticate (auth, e_credentials_peek (priv->credentials, E_CREDENTIALS_KEY_USERNAME), e_credentials_peek (priv->credentials, E_CREDENTIALS_KEY_PASSWORD));
926 e_credentials_clear_peek (priv->credentials);
930 /* ************************************************************************* */
931 /* direct CalDAV server access functions */
934 redirect_handler (SoupMessage *msg,
937 if (SOUP_STATUS_IS_REDIRECTION (msg->status_code)) {
938 SoupSession *soup_session = user_data;
940 const gchar *new_loc;
942 new_loc = soup_message_headers_get (msg->response_headers, "Location");
946 new_uri = soup_uri_new_with_base (soup_message_get_uri (msg), new_loc);
948 soup_message_set_status_full (msg,
949 SOUP_STATUS_MALFORMED,
950 "Invalid Redirect URL");
954 if (new_uri->host && g_str_has_suffix (new_uri->host, "yahoo.com")) {
955 /* yahoo! returns port 7070, which is unreachable;
956 * it also requires https being used (below call resets port as well) */
957 soup_uri_set_scheme (new_uri, SOUP_URI_SCHEME_HTTPS);
960 soup_message_set_uri (msg, new_uri);
961 soup_session_requeue_message (soup_session, msg);
963 soup_uri_free (new_uri);
968 send_and_handle_redirection (SoupSession *soup_session,
970 gchar **new_location)
972 gchar *old_uri = NULL;
974 g_return_if_fail (msg != NULL);
977 old_uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
979 soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT);
980 soup_message_add_header_handler (msg, "got_body", "Location", G_CALLBACK (redirect_handler), soup_session);
981 soup_message_headers_append (msg->request_headers, "Connection", "close");
982 soup_session_send_message (soup_session, msg);
985 gchar *new_loc = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
987 if (new_loc && old_uri && !g_str_equal (new_loc, old_uri))
988 *new_location = new_loc;
997 caldav_generate_uri (ECalBackendCalDAV *cbdav,
1000 ECalBackendCalDAVPrivate *priv;
1006 slash = strrchr (target, '/');
1010 /* priv->uri *have* trailing slash already */
1011 uri = g_strconcat (priv->uri, target, NULL);
1017 caldav_server_open_calendar (ECalBackendCalDAV *cbdav,
1018 gboolean *server_unreachable,
1021 ECalBackendCalDAVPrivate *priv;
1022 SoupMessage *message;
1023 const gchar *header;
1024 gboolean calendar_access;
1025 gboolean put_allowed;
1026 gboolean delete_allowed;
1028 g_return_val_if_fail (cbdav != NULL, FALSE);
1029 g_return_val_if_fail (server_unreachable != NULL, FALSE);
1033 /* FIXME: setup text_uri */
1035 message = soup_message_new (SOUP_METHOD_OPTIONS, priv->uri);
1036 if (message == NULL) {
1037 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
1040 soup_message_headers_append (message->request_headers,
1041 "User-Agent", "Evolution/" VERSION);
1043 send_and_handle_redirection (priv->session, message, NULL);
1045 if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1046 switch (message->status_code) {
1047 case SOUP_STATUS_CANT_CONNECT:
1048 case SOUP_STATUS_CANT_CONNECT_PROXY:
1049 *server_unreachable = TRUE;
1053 status_code_to_result (message, priv, perror);
1055 g_object_unref (message);
1059 /* parse the dav header, we are intreseted in the
1060 * calendar-access bit only at the moment */
1061 header = soup_message_headers_get (message->response_headers, "DAV");
1063 calendar_access = soup_header_contains (header, "calendar-access");
1064 priv->calendar_schedule = soup_header_contains (header, "calendar-schedule");
1066 calendar_access = FALSE;
1067 priv->calendar_schedule = FALSE;
1070 /* parse the Allow header and look for PUT, DELETE at the
1071 * moment (maybe we should check more here, for REPORT eg) */
1072 header = soup_message_headers_get (message->response_headers, "Allow");
1074 put_allowed = soup_header_contains (header, "PUT");
1075 delete_allowed = soup_header_contains (header, "DELETE");
1077 put_allowed = delete_allowed = FALSE;
1079 g_object_unref (message);
1081 if (calendar_access) {
1082 priv->read_only = !(put_allowed && delete_allowed);
1086 g_propagate_error (perror, EDC_ERROR (PermissionDenied));
1091 caldav_notify_auth_required (ECalBackendCalDAV *cbdav)
1093 ECredentials *credentials;
1095 gchar *prompt_flags_str;
1097 g_return_if_fail (cbdav != NULL);
1098 g_return_if_fail (cbdav->priv != NULL);
1100 cbdav->priv->opened = FALSE;
1101 cbdav->priv->slave_cmd = SLAVE_SHOULD_SLEEP;
1103 if (!e_backend_get_online (E_BACKEND (cbdav)))
1106 if (cbdav->priv->credentials)
1107 credentials = e_credentials_new_clone (cbdav->priv->credentials);
1109 credentials = e_credentials_new ();
1110 prompt_flags = E_CREDENTIALS_PROMPT_FLAG_REMEMBER_FOREVER
1111 | E_CREDENTIALS_PROMPT_FLAG_SECRET
1112 | E_CREDENTIALS_PROMPT_FLAG_ONLINE
1113 | E_CREDENTIALS_PROMPT_FLAG_REPROMPT;
1115 prompt_flags_str = e_credentials_util_prompt_flags_to_string (prompt_flags);
1116 e_credentials_set (credentials, E_CREDENTIALS_KEY_PROMPT_FLAGS, prompt_flags_str);
1117 g_free (prompt_flags_str);
1119 e_cal_backend_notify_auth_required (E_CAL_BACKEND (cbdav), TRUE, credentials);
1121 e_credentials_free (credentials);
1124 /* Returns whether calendar changed on the server. This works only when server
1125 * supports 'getctag' extension. */
1127 check_calendar_changed_on_server (ECalBackendCalDAV *cbdav)
1129 ECalBackendCalDAVPrivate *priv;
1130 xmlOutputBufferPtr buf;
1131 SoupMessage *message;
1133 xmlNodePtr root, node;
1135 gboolean result = TRUE;
1137 g_return_val_if_fail (cbdav != NULL, TRUE);
1141 /* no support for 'getctag', thus update cache */
1142 if (!priv->ctag_supported)
1145 /* Prepare the soup message */
1146 message = soup_message_new ("PROPFIND", priv->uri);
1147 if (message == NULL)
1150 doc = xmlNewDoc ((xmlChar *) "1.0");
1151 root = xmlNewDocNode (doc, NULL, (xmlChar *) "propfind", NULL);
1152 xmlDocSetRootElement (doc, root);
1153 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", NULL);
1154 ns = xmlNewNs (root, (xmlChar *) "http://calendarserver.org/ns/", (xmlChar *) "CS");
1156 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1157 node = xmlNewTextChild (node, nsdav, (xmlChar *) "getctag", NULL);
1158 xmlSetNs (node, ns);
1160 buf = xmlAllocOutputBuffer (NULL);
1161 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1162 xmlOutputBufferFlush (buf);
1164 soup_message_headers_append (message->request_headers,
1165 "User-Agent", "Evolution/" VERSION);
1166 soup_message_headers_append (message->request_headers,
1169 soup_message_set_request (message,
1172 (gchar *) buf->buffer->content,
1175 /* Send the request now */
1176 send_and_handle_redirection (priv->session, message, NULL);
1178 /* Clean up the memory */
1179 xmlOutputBufferClose (buf);
1182 /* Check the result */
1183 if (message->status_code == 401) {
1184 caldav_notify_auth_required (cbdav);
1185 } else if (message->status_code != 207) {
1186 /* does not support it, but report calendar changed to update cache */
1187 priv->ctag_supported = FALSE;
1191 if (parse_propfind_response (message, XPATH_GETCTAG_STATUS, XPATH_GETCTAG, &ctag)) {
1192 const gchar *my_ctag;
1194 my_ctag = e_cal_backend_store_get_key_value (priv->store, CALDAV_CTAG_KEY);
1196 if (ctag && my_ctag && g_str_equal (ctag, my_ctag)) {
1197 /* ctag is same, no change in the calendar */
1200 /* do not store ctag now, do it rather after complete sync */
1201 g_free (priv->ctag_to_store);
1202 priv->ctag_to_store = ctag;
1208 priv->ctag_supported = FALSE;
1212 g_object_unref (message);
1217 /* only_hrefs is a list of requested objects to fetch; it has precedence from
1218 * start_time/end_time, which are used only when both positive.
1219 * Times are supposed to be in UTC, if set.
1222 caldav_server_list_objects (ECalBackendCalDAV *cbdav,
1223 CalDAVObject **objs,
1229 ECalBackendCalDAVPrivate *priv;
1230 xmlOutputBufferPtr buf;
1231 SoupMessage *message;
1241 /* Allocate the soup message */
1242 message = soup_message_new ("REPORT", priv->uri);
1243 if (message == NULL)
1246 /* Maybe we should just do a g_strdup_printf here? */
1247 /* Prepare request body */
1248 doc = xmlNewDoc ((xmlChar *) "1.0");
1250 root = xmlNewDocNode (doc, NULL, (xmlChar *) "calendar-query", NULL);
1252 root = xmlNewDocNode (doc, NULL, (xmlChar *) "calendar-multiget", NULL);
1253 nscd = xmlNewNs (root, (xmlChar *) "urn:ietf:params:xml:ns:caldav", (xmlChar *) "C");
1254 xmlSetNs (root, nscd);
1255 xmlDocSetRootElement (doc, root);
1257 /* Add webdav tags */
1258 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", (xmlChar *) "D");
1259 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1260 xmlNewTextChild (node, nsdav, (xmlChar *) "getetag", NULL);
1264 xmlNewTextChild (node, nscd, (xmlChar *) "calendar-data", NULL);
1265 for (l = only_hrefs; l; l = l->next) {
1267 xmlNewTextChild (root, nsdav, (xmlChar *) "href", (xmlChar *) l->data);
1271 node = xmlNewTextChild (root, nscd, (xmlChar *) "filter", NULL);
1272 node = xmlNewTextChild (node, nscd, (xmlChar *) "comp-filter", NULL);
1273 xmlSetProp (node, (xmlChar *) "name", (xmlChar *) "VCALENDAR");
1275 sn = xmlNewTextChild (node, nscd, (xmlChar *) "comp-filter", NULL);
1276 switch (e_cal_backend_get_kind (E_CAL_BACKEND (cbdav))) {
1278 case ICAL_VEVENT_COMPONENT:
1279 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VEVENT");
1281 case ICAL_VJOURNAL_COMPONENT:
1282 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VJOURNAL");
1284 case ICAL_VTODO_COMPONENT:
1285 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VTODO");
1289 if (start_time > 0 || end_time > 0) {
1292 sn = xmlNewTextChild (sn, nscd, (xmlChar *) "time-range", NULL);
1294 if (start_time > 0) {
1295 tmp = isodate_from_time_t (start_time);
1296 xmlSetProp (sn, (xmlChar *) "start", (xmlChar *) tmp);
1301 tmp = isodate_from_time_t (end_time);
1302 xmlSetProp (sn, (xmlChar *) "end", (xmlChar *) tmp);
1308 buf = xmlAllocOutputBuffer (NULL);
1309 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1310 xmlOutputBufferFlush (buf);
1312 /* Prepare the soup message */
1313 soup_message_headers_append (message->request_headers,
1314 "User-Agent", "Evolution/" VERSION);
1315 soup_message_headers_append (message->request_headers,
1318 soup_message_set_request (message,
1321 (gchar *) buf->buffer->content,
1324 /* Send the request now */
1325 send_and_handle_redirection (priv->session, message, NULL);
1327 /* Clean up the memory */
1328 xmlOutputBufferClose (buf);
1331 /* Check the result */
1332 if (message->status_code != 207) {
1333 switch (message->status_code) {
1334 case SOUP_STATUS_CANT_CONNECT:
1335 case SOUP_STATUS_CANT_CONNECT_PROXY:
1336 priv->opened = FALSE;
1337 priv->slave_cmd = SLAVE_SHOULD_SLEEP;
1338 priv->read_only = TRUE;
1339 e_cal_backend_notify_readonly (E_CAL_BACKEND (cbdav), priv->read_only);
1342 caldav_notify_auth_required (cbdav);
1345 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");
1349 g_object_unref (message);
1353 /* Parse the response body */
1354 result = parse_report_response (message, objs, len);
1356 g_object_unref (message);
1361 caldav_server_download_attachment (ECalBackendCalDAV *cbdav,
1362 const gchar *attachment_uri,
1367 SoupMessage *message;
1369 g_return_val_if_fail (cbdav != NULL, FALSE);
1370 g_return_val_if_fail (cbdav->priv != NULL, FALSE);
1371 g_return_val_if_fail (attachment_uri != NULL, FALSE);
1372 g_return_val_if_fail (content != NULL, FALSE);
1373 g_return_val_if_fail (len != NULL, FALSE);
1375 message = soup_message_new (SOUP_METHOD_GET, attachment_uri);
1376 if (message == NULL) {
1377 g_propagate_error (error, EDC_ERROR (InvalidObject));
1381 soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1382 send_and_handle_redirection (cbdav->priv->session, message, NULL);
1384 if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1385 status_code_to_result (message, cbdav->priv, error);
1387 if (message->status_code == 401)
1388 caldav_notify_auth_required (cbdav);
1390 g_object_unref (message);
1394 *len = message->response_body->length;
1395 *content = g_memdup (message->response_body->data, *len);
1397 g_object_unref (message);
1403 caldav_server_get_object (ECalBackendCalDAV *cbdav,
1404 CalDAVObject *object,
1407 ECalBackendCalDAVPrivate *priv;
1408 SoupMessage *message;
1414 g_assert (object != NULL && object->href != NULL);
1416 uri = caldav_generate_uri (cbdav, object->href);
1417 message = soup_message_new (SOUP_METHOD_GET, uri);
1418 if (message == NULL) {
1420 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
1424 soup_message_headers_append (message->request_headers,
1425 "User-Agent", "Evolution/" VERSION);
1427 send_and_handle_redirection (priv->session, message, NULL);
1429 if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1430 status_code_to_result (message, priv, perror);
1432 if (message->status_code == 401)
1433 caldav_notify_auth_required (cbdav);
1435 g_warning ("Could not fetch object '%s' from server, status:%d (%s)", uri, message->status_code, soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : "Unknown code");
1436 g_object_unref (message);
1441 hdr = soup_message_headers_get (message->response_headers, "Content-Type");
1443 if (hdr == NULL || g_ascii_strncasecmp (hdr, "text/calendar", 13)) {
1444 g_propagate_error (perror, EDC_ERROR (InvalidObject));
1445 g_object_unref (message);
1446 g_warning ("Object to fetch '%s' not of type text/calendar", uri);
1451 hdr = soup_message_headers_get (message->response_headers, "ETag");
1454 g_free (object->etag);
1455 object->etag = quote_etag (hdr);
1456 } else if (!object->etag) {
1457 g_warning ("UUHH no ETag, now that's bad! (at '%s')", uri);
1461 g_free (object->cdata);
1462 object->cdata = g_strdup (message->response_body->data);
1464 g_object_unref (message);
1470 caldav_post_freebusy (ECalBackendCalDAV *cbdav,
1475 ECalBackendCalDAVPrivate *priv;
1476 SoupMessage *message;
1478 e_return_data_cal_error_if_fail (cbdav != NULL, InvalidArg);
1479 e_return_data_cal_error_if_fail (url != NULL, InvalidArg);
1480 e_return_data_cal_error_if_fail (post_fb != NULL, InvalidArg);
1481 e_return_data_cal_error_if_fail (*post_fb != NULL, InvalidArg);
1485 message = soup_message_new (SOUP_METHOD_POST, url);
1486 if (message == NULL) {
1487 g_propagate_error (error, EDC_ERROR (NoSuchCal));
1491 soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1492 soup_message_set_request (message,
1493 "text/calendar; charset=utf-8",
1495 *post_fb, strlen (*post_fb));
1497 send_and_handle_redirection (priv->session, message, NULL);
1499 if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1500 status_code_to_result (message, priv, error);
1501 if (message->status_code == 401)
1502 caldav_notify_auth_required (cbdav);
1504 g_warning ("Could not post free/busy request to '%s', status:%d (%s)", url, message->status_code, soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : "Unknown code");
1506 g_object_unref (message);
1512 *post_fb = g_strdup (message->response_body->data);
1514 g_object_unref (message);
1518 caldav_server_put_object (ECalBackendCalDAV *cbdav,
1519 CalDAVObject *object,
1520 icalcomponent *icalcomp,
1523 ECalBackendCalDAVPrivate *priv;
1524 SoupMessage *message;
1531 g_assert (object != NULL && object->cdata != NULL);
1533 uri = caldav_generate_uri (cbdav, object->href);
1534 message = soup_message_new (SOUP_METHOD_PUT, uri);
1536 if (message == NULL) {
1537 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
1541 soup_message_headers_append (message->request_headers,
1542 "User-Agent", "Evolution/" VERSION);
1544 /* For new items we use the If-None-Match so we don't
1545 * acidently override resources, for item updates we
1546 * use the If-Match header to avoid the Lost-update
1548 if (object->etag == NULL) {
1549 soup_message_headers_append (message->request_headers, "If-None-Match", "*");
1551 soup_message_headers_append (message->request_headers,
1552 "If-Match", object->etag);
1555 soup_message_set_request (message,
1556 "text/calendar; charset=utf-8",
1559 strlen (object->cdata));
1562 send_and_handle_redirection (priv->session, message, &uri);
1565 gchar *file = strrchr (uri, '/');
1567 /* there was a redirect, update href properly */
1571 g_free (object->href);
1573 decoded = soup_uri_decode (file + 1);
1574 object->href = soup_uri_encode (decoded ? decoded : (file + 1), NULL);
1582 if (status_code_to_result (message, priv, perror)) {
1583 gboolean was_get = FALSE;
1585 hdr = soup_message_headers_get (message->response_headers, "ETag");
1587 g_free (object->etag);
1588 object->etag = quote_etag (hdr);
1590 /* no ETag header returned, check for it with a GET */
1591 hdr = soup_message_headers_get (message->response_headers, "Location");
1593 /* reflect possible href change first */
1594 gchar *file = strrchr (hdr, '/');
1599 g_free (object->href);
1601 decoded = soup_uri_decode (file + 1);
1602 object->href = soup_uri_encode (decoded ? decoded : (file + 1), NULL);
1611 if (caldav_server_get_object (cbdav, object, perror)) {
1612 icalcomponent *use_comp = NULL;
1614 if (object->cdata && was_get) {
1615 /* maybe server also modified component, thus rather store the server's */
1616 use_comp = icalparser_parse_string (object->cdata);
1620 use_comp = icalcomp;
1622 put_comp_to_cache (cbdav, use_comp, object->href, object->etag);
1624 if (use_comp != icalcomp)
1625 icalcomponent_free (use_comp);
1627 } else if (message->status_code == 401) {
1628 caldav_notify_auth_required (cbdav);
1631 g_object_unref (message);
1637 caldav_server_delete_object (ECalBackendCalDAV *cbdav,
1638 CalDAVObject *object,
1641 ECalBackendCalDAVPrivate *priv;
1642 SoupMessage *message;
1647 g_assert (object != NULL && object->href != NULL);
1649 uri = caldav_generate_uri (cbdav, object->href);
1650 message = soup_message_new (SOUP_METHOD_DELETE, uri);
1652 if (message == NULL) {
1653 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
1657 soup_message_headers_append (message->request_headers,
1658 "User-Agent", "Evolution/" VERSION);
1660 if (object->etag != NULL) {
1661 soup_message_headers_append (message->request_headers,
1662 "If-Match", object->etag);
1665 send_and_handle_redirection (priv->session, message, NULL);
1667 status_code_to_result (message, priv, perror);
1669 if (message->status_code == 401)
1670 caldav_notify_auth_required (cbdav);
1672 g_object_unref (message);
1676 caldav_receive_schedule_outbox_url (ECalBackendCalDAV *cbdav)
1678 ECalBackendCalDAVPrivate *priv;
1679 SoupMessage *message;
1680 xmlOutputBufferPtr buf;
1682 xmlNodePtr root, node;
1684 gchar *owner = NULL;
1686 g_return_val_if_fail (cbdav != NULL, FALSE);
1689 g_return_val_if_fail (priv != NULL, FALSE);
1690 g_return_val_if_fail (priv->schedule_outbox_url == NULL, TRUE);
1692 /* Prepare the soup message */
1693 message = soup_message_new ("PROPFIND", priv->uri);
1694 if (message == NULL)
1697 doc = xmlNewDoc ((xmlChar *) "1.0");
1698 root = xmlNewDocNode (doc, NULL, (xmlChar *) "propfind", NULL);
1699 xmlDocSetRootElement (doc, root);
1700 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", NULL);
1702 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1703 node = xmlNewTextChild (node, nsdav, (xmlChar *) "owner", NULL);
1705 buf = xmlAllocOutputBuffer (NULL);
1706 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1707 xmlOutputBufferFlush (buf);
1709 soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1710 soup_message_headers_append (message->request_headers, "Depth", "0");
1712 soup_message_set_request (message,
1715 (gchar *) buf->buffer->content,
1718 /* Send the request now */
1719 send_and_handle_redirection (priv->session, message, NULL);
1721 /* Clean up the memory */
1722 xmlOutputBufferClose (buf);
1725 /* Check the result */
1726 if (message->status_code == 207 && parse_propfind_response (message, XPATH_OWNER_STATUS, XPATH_OWNER, &owner) && owner && *owner) {
1730 g_object_unref (message);
1732 /* owner is a full path to the user's URL, thus change it in
1733 * calendar's uri when asking for schedule-outbox-URL */
1734 suri = soup_uri_new (priv->uri);
1735 soup_uri_set_path (suri, owner);
1737 owner = soup_uri_to_string (suri, FALSE);
1738 soup_uri_free (suri);
1740 message = soup_message_new ("PROPFIND", owner);
1741 if (message == NULL) {
1746 doc = xmlNewDoc ((xmlChar *) "1.0");
1747 root = xmlNewDocNode (doc, NULL, (xmlChar *) "propfind", NULL);
1748 xmlDocSetRootElement (doc, root);
1749 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", NULL);
1750 nscd = xmlNewNs (root, (xmlChar *) "urn:ietf:params:xml:ns:caldav", (xmlChar *) "C");
1752 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1753 node = xmlNewTextChild (node, nscd, (xmlChar *) "schedule-outbox-URL", NULL);
1755 buf = xmlAllocOutputBuffer (NULL);
1756 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1757 xmlOutputBufferFlush (buf);
1759 soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1760 soup_message_headers_append (message->request_headers, "Depth", "0");
1762 soup_message_set_request (message,
1765 (gchar *) buf->buffer->content,
1768 /* Send the request now */
1769 send_and_handle_redirection (priv->session, message, NULL);
1771 if (message->status_code == 207 && parse_propfind_response (message, XPATH_SCHEDULE_OUTBOX_URL_STATUS, XPATH_SCHEDULE_OUTBOX_URL, &priv->schedule_outbox_url)) {
1772 if (!*priv->schedule_outbox_url) {
1773 g_free (priv->schedule_outbox_url);
1774 priv->schedule_outbox_url = NULL;
1776 /* make it a full URI */
1777 suri = soup_uri_new (priv->uri);
1778 soup_uri_set_path (suri, priv->schedule_outbox_url);
1779 g_free (priv->schedule_outbox_url);
1780 priv->schedule_outbox_url = soup_uri_to_string (suri, FALSE);
1781 soup_uri_free (suri);
1785 /* Clean up the memory */
1786 xmlOutputBufferClose (buf);
1788 } else if (message->status_code == 401) {
1789 caldav_notify_auth_required (cbdav);
1793 g_object_unref (message);
1797 return priv->schedule_outbox_url != NULL;
1800 /* ************************************************************************* */
1801 /* Synchronization foo */
1803 static gboolean extract_timezones (ECalBackendCalDAV *cbdav, icalcomponent *icomp);
1805 struct cache_comp_list
1811 remove_complist_from_cache_and_notify_cb (gpointer key,
1816 struct cache_comp_list *ccl = value;
1817 ECalBackendCalDAV *cbdav = data;
1818 ECalBackendCalDAVPrivate *priv = cbdav->priv;
1820 for (l = ccl->slist; l; l = l->next) {
1821 ECalComponent *old_comp = l->data;
1822 ECalComponentId *id;
1824 id = e_cal_component_get_id (old_comp);
1829 if (e_cal_backend_store_remove_component (priv->store, id->uid, id->rid)) {
1830 e_cal_backend_notify_component_removed ((ECalBackend *) cbdav, id, old_comp, NULL);
1833 e_cal_component_free_id (id);
1835 remove_cached_attachment (cbdav, (const gchar *) key);
1841 free_comp_list (gpointer cclist)
1843 struct cache_comp_list *ccl = cclist;
1845 g_return_if_fail (ccl != NULL);
1847 g_slist_foreach (ccl->slist, (GFunc) g_object_unref, NULL);
1848 g_slist_free (ccl->slist);
1852 #define etags_match(_tag1, _tag2) ((_tag1 == _tag2) ? TRUE : \
1853 g_str_equal (_tag1 != NULL ? _tag1 : "", \
1854 _tag2 != NULL ? _tag2 : ""))
1856 /* start_time/end_time is an interval for checking changes. If both greater than zero,
1857 * only the interval is checked and the removed items are not notified, as they can
1861 synchronize_cache (ECalBackendCalDAV *cbdav,
1865 ECalBackendCalDAVPrivate *priv;
1867 CalDAVObject *sobjs, *object;
1868 GSList *c_objs, *c_iter; /* list of all items known from our cache */
1869 GTree *c_uid2complist; /* cache components list (with detached instances) sorted by (master's) uid */
1870 GHashTable *c_href2uid; /* connection between href and a (master's) uid */
1871 GSList *hrefs_to_update, *htu; /* list of href-s to update */
1874 if (!check_calendar_changed_on_server (cbdav)) {
1875 /* no changes on the server, no update required */
1880 bkend = E_CAL_BACKEND (cbdav);
1884 /* get list of server objects */
1885 if (!caldav_server_list_objects (cbdav, &sobjs, &len, NULL, start_time, end_time))
1888 c_objs = e_cal_backend_store_get_components (priv->store);
1890 if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
1891 printf ("CalDAV - found %d objects on the server, locally stored %d objects\n", len, g_slist_length (c_objs)); fflush (stdout);
1894 /* do not store changes in cache immediately - makes things significantly quicker */
1895 e_cal_backend_store_freeze_changes (priv->store);
1897 c_uid2complist = g_tree_new_full ((GCompareDataFunc) g_strcmp0, NULL, g_free, free_comp_list);
1898 c_href2uid = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1900 /* fill indexed hash and tree with cached components */
1901 for (c_iter = c_objs; c_iter; c_iter = g_slist_next (c_iter)) {
1902 ECalComponent *ccomp = E_CAL_COMPONENT (c_iter->data);
1903 const gchar *uid = NULL;
1904 struct cache_comp_list *ccl;
1907 e_cal_component_get_uid (ccomp, &uid);
1909 g_warning ("broken component with NULL Id");
1913 href = ecalcomp_get_href (ccomp);
1916 g_warning ("href of object NULL :(");
1920 ccl = g_tree_lookup (c_uid2complist, uid);
1922 ccl->slist = g_slist_prepend (ccl->slist, g_object_ref (ccomp));
1924 ccl = g_new0 (struct cache_comp_list, 1);
1925 ccl->slist = g_slist_append (NULL, g_object_ref (ccomp));
1927 /* make a copy, which will be used in the c_href2uid too */
1928 uid = g_strdup (uid);
1930 g_tree_insert (c_uid2complist, (gpointer) uid, ccl);
1933 if (g_hash_table_lookup (c_href2uid, href) == NULL) {
1934 /* uid is from a component or c_uid2complist key, thus will not be
1935 * freed before a removal from c_uid2complist, thus do not duplicate it,
1936 * rather save memory */
1937 g_hash_table_insert (c_href2uid, href, (gpointer) uid);
1943 /* clear it now, we do not need it later */
1944 g_slist_foreach (c_objs, (GFunc) g_object_unref, NULL);
1945 g_slist_free (c_objs);
1948 hrefs_to_update = NULL;
1950 /* see if we have to update or add some objects */
1951 for (i = 0, object = sobjs; i < len && priv->slave_cmd == SLAVE_SHOULD_WORK; i++, object++) {
1952 ECalComponent *ccomp = NULL;
1955 struct cache_comp_list *ccl;
1957 if (object->status != 200) {
1958 /* just continue here, so that the object
1959 * doesnt get removed from the cobjs list
1960 * - therefore it will be removed */
1964 uid = g_hash_table_lookup (c_href2uid, object->href);
1966 ccl = g_tree_lookup (c_uid2complist, uid);
1969 for (sl = ccl->slist; sl && !etag; sl = sl->next) {
1972 etag = ecalcomp_get_etag (ccomp);
1980 if (!etag || !etags_match (etag, object->etag)) {
1981 hrefs_to_update = g_slist_prepend (hrefs_to_update, object->href);
1982 } else if (uid && ccl) {
1983 /* all components cover by this uid are up-to-date */
1986 for (p = ccl->slist; p; p = p->next) {
1987 g_object_unref (p->data);
1990 g_slist_free (ccl->slist);
1997 /* free hash table, as it is not used anymore */
1998 g_hash_table_destroy (c_href2uid);
2001 if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2002 printf ("CalDAV - recognized %d items to update\n", g_slist_length (hrefs_to_update)); fflush (stdout);
2005 htu = hrefs_to_update;
2006 while (htu && priv->slave_cmd == SLAVE_SHOULD_WORK) {
2008 GSList *to_fetch = NULL;
2010 while (count < CALDAV_MAX_MULTIGET_AMOUNT && htu) {
2011 to_fetch = g_slist_prepend (to_fetch, htu->data);
2016 if (to_fetch && priv->slave_cmd == SLAVE_SHOULD_WORK) {
2017 CalDAVObject *up_sobjs = NULL;
2019 if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2020 printf ("CalDAV - going to fetch %d items\n", g_slist_length (to_fetch)); fflush (stdout);
2024 if (!caldav_server_list_objects (cbdav, &up_sobjs, &count, to_fetch, 0, 0)) {
2025 fprintf (stderr, "CalDAV - failed to retrieve bunch of items\n"); fflush (stderr);
2029 if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2030 printf ("CalDAV - fetched bunch of %d items\n", count); fflush (stdout);
2033 /* we are going to update cache */
2034 /* they are downloaded, so process them */
2035 for (i = 0, object = up_sobjs; i < count /*&& priv->slave_cmd == SLAVE_SHOULD_WORK */; i++, object++) {
2036 if (object->status == 200 && object->href && object->etag && object->cdata && *object->cdata) {
2037 icalcomponent *icomp = icalparser_parse_string (object->cdata);
2040 icalcomponent_kind kind = icalcomponent_isa (icomp);
2042 extract_timezones (cbdav, icomp);
2044 if (kind == ICAL_VCALENDAR_COMPONENT) {
2045 icalcomponent *subcomp;
2047 kind = e_cal_backend_get_kind (bkend);
2049 for (subcomp = icalcomponent_get_first_component (icomp, kind);
2051 subcomp = icalcomponent_get_next_component (icomp, kind)) {
2052 ECalComponent *new_comp, *old_comp;
2054 convert_to_url_attachment (cbdav, subcomp);
2055 new_comp = e_cal_component_new ();
2056 if (e_cal_component_set_icalcomponent (new_comp, icalcomponent_new_clone (subcomp))) {
2057 const gchar *uid = NULL;
2058 struct cache_comp_list *ccl;
2060 e_cal_component_get_uid (new_comp, &uid);
2062 g_warning ("%s: no UID on component!", G_STRFUNC);
2063 g_object_unref (new_comp);
2067 ecalcomp_set_href (new_comp, object->href);
2068 ecalcomp_set_etag (new_comp, object->etag);
2071 ccl = g_tree_lookup (c_uid2complist, uid);
2073 gchar *nc_rid = e_cal_component_get_recurid_as_string (new_comp);
2076 for (p = ccl->slist; p && !old_comp; p = p->next) {
2081 oc_rid = e_cal_component_get_recurid_as_string (old_comp);
2082 if (g_strcmp0 (nc_rid, oc_rid) != 0) {
2092 put_component_to_store (cbdav, new_comp);
2094 if (old_comp == NULL) {
2095 e_cal_backend_notify_component_created (bkend, new_comp);
2097 e_cal_backend_notify_component_modified (bkend, old_comp, new_comp);
2099 ccl->slist = g_slist_remove (ccl->slist, old_comp);
2100 g_object_unref (old_comp);
2104 g_object_unref (new_comp);
2108 icalcomponent_free (icomp);
2112 /* these free immediately */
2113 caldav_object_free (object, FALSE);
2116 /* cache update done for fetched items */
2120 /* do not free 'data' itself, it's part of 'sobjs' */
2121 g_slist_free (to_fetch);
2124 /* if not interrupted and not using the time range... */
2125 if (priv->slave_cmd == SLAVE_SHOULD_WORK && (!start_time || !end_time)) {
2126 /* ...remove old (not on server anymore) items from our cache and notify of a removal */
2127 g_tree_foreach (c_uid2complist, remove_complist_from_cache_and_notify_cb, cbdav);
2130 if (priv->ctag_to_store) {
2131 /* store only when wasn't interrupted */
2132 if (priv->slave_cmd == SLAVE_SHOULD_WORK && start_time == 0 && end_time == 0) {
2133 e_cal_backend_store_put_key_value (priv->store, CALDAV_CTAG_KEY, priv->ctag_to_store);
2136 g_free (priv->ctag_to_store);
2137 priv->ctag_to_store = NULL;
2140 /* save cache changes to disk finally */
2141 e_cal_backend_store_thaw_changes (priv->store);
2143 for (i = 0, object = sobjs; i < len; i++, object++) {
2144 caldav_object_free (object, FALSE);
2147 g_tree_destroy (c_uid2complist);
2148 g_slist_free (hrefs_to_update);
2153 is_google_uri (const gchar *uri)
2158 g_return_val_if_fail (uri != NULL, FALSE);
2160 suri = soup_uri_new (uri);
2161 g_return_val_if_fail (suri != NULL, FALSE);
2163 res = suri->host && g_ascii_strcasecmp (suri->host, "www.google.com") == 0;
2165 soup_uri_free (suri);
2170 /* ************************************************************************* */
2173 caldav_synch_slave_loop (gpointer data)
2175 ECalBackendCalDAVPrivate *priv;
2176 ECalBackendCalDAV *cbdav;
2178 icaltimezone *utc = icaltimezone_get_utc_timezone ();
2179 gboolean know_unreachable;
2181 cbdav = E_CAL_BACKEND_CALDAV (data);
2184 g_mutex_lock (priv->busy_lock);
2186 know_unreachable = !priv->opened;
2188 while (priv->slave_cmd != SLAVE_SHOULD_DIE) {
2189 GTimeVal alarm_clock;
2190 if (priv->slave_cmd == SLAVE_SHOULD_SLEEP) {
2191 /* just sleep until we get woken up again */
2192 g_cond_wait (priv->cond, priv->busy_lock);
2194 /* check if we should die, work or sleep again */
2198 /* Ok here we go, do some real work
2199 * Synch it baby one more time ...
2201 priv->slave_busy = TRUE;
2203 if (!priv->opened) {
2204 gboolean server_unreachable = FALSE;
2205 GError *local_error = NULL;
2208 if (caldav_server_open_calendar (cbdav, &server_unreachable, &local_error)) {
2209 priv->opened = TRUE;
2210 priv->slave_cmd = SLAVE_SHOULD_WORK;
2211 g_cond_signal (priv->cond);
2213 priv->is_google = is_google_uri (priv->uri);
2214 know_unreachable = FALSE;
2215 } else if (local_error) {
2216 priv->opened = FALSE;
2217 priv->read_only = TRUE;
2219 if (!know_unreachable) {
2222 know_unreachable = TRUE;
2224 msg = g_strdup_printf (_("Server is unreachable, calendar is opened in read-only mode.\nError message: %s"), local_error->message);
2225 e_cal_backend_notify_error (E_CAL_BACKEND (cbdav), msg);
2229 g_clear_error (&local_error);
2231 priv->opened = FALSE;
2232 priv->read_only = TRUE;
2233 know_unreachable = TRUE;
2236 e_cal_backend_notify_readonly (E_CAL_BACKEND (cbdav), priv->read_only);
2238 online = e_backend_get_online (E_BACKEND (cbdav));
2239 e_cal_backend_notify_online (E_CAL_BACKEND (cbdav), online);
2244 /* check for events in the month before/after today first,
2245 * to show user actual data as soon as possible */
2246 synchronize_cache (cbdav, time_add_week_with_zone (now, -5, utc), time_add_week_with_zone (now, +5, utc));
2248 if (priv->slave_cmd != SLAVE_SHOULD_SLEEP) {
2249 /* and then check for changes in a whole calendar */
2250 synchronize_cache (cbdav, 0, 0);
2253 if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2256 c_objs = e_cal_backend_store_get_components (priv->store);
2258 printf ("CalDAV - finished syncing with %d items in a cache\n", g_slist_length (c_objs)); fflush (stdout);
2260 g_slist_foreach (c_objs, (GFunc) g_object_unref, NULL);
2261 g_slist_free (c_objs);
2265 priv->slave_busy = FALSE;
2267 /* puhh that was hard, get some rest :) */
2268 g_get_current_time (&alarm_clock);
2269 alarm_clock.tv_sec += priv->refresh_time.tv_sec;
2270 g_cond_timed_wait (priv->cond,
2276 /* signal we are done */
2277 g_cond_signal (priv->slave_gone_cond);
2279 priv->synch_slave = NULL;
2281 /* we got killed ... */
2282 g_mutex_unlock (priv->busy_lock);
2287 maybe_append_email_domain (const gchar *username,
2288 const gchar *may_append)
2290 if (!username || !*username)
2293 if (strchr (username, '@'))
2294 return g_strdup (username);
2296 return g_strconcat (username, may_append, NULL);
2300 get_usermail (ECalBackend *backend)
2302 ECalBackendCalDAV *cbdav;
2303 ECalBackendCalDAVPrivate *priv;
2307 g_return_val_if_fail (backend != NULL, NULL);
2309 source = e_backend_get_source (E_BACKEND (backend));
2311 res = e_source_get_duped_property (source, "usermail");
2319 cbdav = E_CAL_BACKEND_CALDAV (backend);
2322 if (priv && priv->is_google && priv->credentials) {
2323 res = maybe_append_email_domain (e_credentials_peek (priv->credentials, E_CREDENTIALS_KEY_USERNAME), "@gmail.com");
2329 /* ************************************************************************* */
2330 /* ********** ECalBackendSync virtual function implementation ************* */
2333 caldav_get_backend_property (ECalBackendSync *backend,
2335 GCancellable *cancellable,
2336 const gchar *prop_name,
2340 gboolean processed = TRUE;
2342 g_return_val_if_fail (prop_name != NULL, FALSE);
2343 g_return_val_if_fail (prop_value != NULL, FALSE);
2345 if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
2350 caps = g_string_new (CAL_STATIC_CAPABILITY_NO_THISANDFUTURE ","
2351 CAL_STATIC_CAPABILITY_NO_THISANDPRIOR ","
2352 CAL_STATIC_CAPABILITY_REFRESH_SUPPORTED);
2354 usermail = get_usermail (E_CAL_BACKEND (backend));
2355 if (!usermail || !*usermail)
2356 g_string_append (caps, "," CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS);
2359 source = e_backend_get_source (E_BACKEND (backend));
2361 const gchar *prop = e_source_get_property (source, "autoschedule");
2363 if (prop && g_str_equal (prop, "1"))
2364 g_string_append (caps, "," CAL_STATIC_CAPABILITY_CREATE_MESSAGES
2365 "," CAL_STATIC_CAPABILITY_SAVE_SCHEDULES);
2368 *prop_value = g_string_free (caps, FALSE);
2369 } else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS) ||
2370 g_str_equal (prop_name, CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS)) {
2371 *prop_value = get_usermail (E_CAL_BACKEND (backend));
2372 } else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_DEFAULT_OBJECT)) {
2373 ECalComponent *comp;
2375 comp = e_cal_component_new ();
2377 switch (e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
2378 case ICAL_VEVENT_COMPONENT:
2379 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
2381 case ICAL_VTODO_COMPONENT:
2382 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
2384 case ICAL_VJOURNAL_COMPONENT:
2385 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
2388 g_object_unref (comp);
2389 g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
2393 *prop_value = e_cal_component_get_as_string (comp);
2394 g_object_unref (comp);
2403 initialize_backend (ECalBackendCalDAV *cbdav,
2406 ECalBackendCalDAVPrivate *priv;
2407 ECalBackend *backend;
2409 const gchar *os_val;
2412 const gchar *refresh;
2413 const gchar *cache_dir;
2417 backend = E_CAL_BACKEND (cbdav);
2418 cache_dir = e_cal_backend_get_cache_dir (backend);
2419 source = e_backend_get_source (E_BACKEND (backend));
2420 uri = e_source_get_uri (source);
2422 if (!g_signal_handler_find (G_OBJECT (source), G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, caldav_source_changed_cb, cbdav))
2423 g_signal_connect (G_OBJECT (source), "changed", G_CALLBACK (caldav_source_changed_cb), cbdav);
2425 os_val = e_source_get_property (source, "offline_sync");
2427 if (!os_val || !g_str_equal (os_val, "1")) {
2428 priv->do_offline = FALSE;
2431 os_val = e_source_get_property (source, "auth");
2432 priv->auth_required = os_val != NULL;
2434 os_val = e_source_get_property(source, "ssl");
2438 if (g_str_has_prefix (uri, "caldav://")) {
2441 if (os_val && os_val[0] == '1') {
2447 priv->uri = g_strconcat (proto, uri + 9, NULL);
2449 priv->uri = g_strdup (uri);
2453 SoupURI *suri = soup_uri_new (priv->uri);
2455 /* properly encode uri */
2456 if (suri && suri->path) {
2459 if (suri->path && strchr (suri->path, '%')) {
2460 /* If path contains anything already encoded, then decode it first,
2461 * thus it'll be managed properly. For example, the '#' in a path
2462 * is in URI shown as %23 and not doing this decode makes it being
2463 * like %2523, which is not what is wanted here. */
2464 tmp = soup_uri_decode (suri->path);
2465 soup_uri_set_path (suri, tmp);
2469 tmp = soup_uri_encode (suri->path, NULL);
2470 path = soup_uri_normalize (tmp, "/");
2472 soup_uri_set_path (suri, path);
2478 priv->uri = soup_uri_to_string (suri, FALSE);
2481 soup_uri_free (suri);
2484 /* remove trailing slashes... */
2485 len = strlen (priv->uri);
2487 if (priv->uri[len] == '/') {
2488 priv->uri[len] = '\0';
2494 /* ...and append exactly one slash */
2495 if (priv->uri && *priv->uri) {
2496 gchar *tmp = priv->uri;
2498 priv->uri = g_strconcat (priv->uri, "/", NULL);
2503 if (priv->store == NULL) {
2504 /* remove the old cache while migrating to ECalBackendStore */
2505 e_cal_backend_cache_remove (cache_dir, "cache.xml");
2506 priv->store = e_cal_backend_file_store_new (cache_dir);
2508 if (priv->store == NULL) {
2509 g_propagate_error (perror, EDC_ERROR_EX (OtherError, "Cannot create local store"));
2513 e_cal_backend_store_load (priv->store);
2516 /* Set the local attachment store */
2517 if (g_mkdir_with_parents (cache_dir, 0700) < 0) {
2518 g_propagate_error (perror, EDC_ERROR_EX (OtherError, "mkdir failed"));
2522 refresh = e_source_get_property (source, "refresh");
2523 priv->refresh_time.tv_sec = (refresh && atoi (refresh) > 0) ? (60 * atoi (refresh)) : (DEFAULT_REFRESH_TIME);
2525 if (!priv->synch_slave) {
2528 priv->slave_cmd = SLAVE_SHOULD_SLEEP;
2529 slave = g_thread_create (caldav_synch_slave_loop, cbdav, FALSE, NULL);
2531 if (slave == NULL) {
2532 g_propagate_error (perror, EDC_ERROR_EX (OtherError, "Could not create synch slave"));
2535 priv->synch_slave = slave;
2542 proxy_settings_changed (EProxy *proxy,
2545 SoupURI *proxy_uri = NULL;
2546 ECalBackendCalDAVPrivate *priv = (ECalBackendCalDAVPrivate *) user_data;
2548 if (!priv || !priv->uri || !priv->session)
2551 /* use proxy if necessary */
2552 if (e_proxy_require_proxy_for_uri (proxy, priv->uri)) {
2553 proxy_uri = e_proxy_peek_uri_for (proxy, priv->uri);
2556 g_object_set (priv->session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL);
2560 open_calendar (ECalBackendCalDAV *cbdav,
2563 gboolean server_unreachable = FALSE;
2564 ECalBackendCalDAVPrivate *priv;
2565 GError *local_error = NULL;
2567 g_return_if_fail (cbdav != NULL);
2571 /* set forward proxy */
2572 proxy_settings_changed (priv->proxy, priv);
2574 if (caldav_server_open_calendar (cbdav, &server_unreachable, &local_error)) {
2575 priv->slave_cmd = SLAVE_SHOULD_WORK;
2576 g_cond_signal (priv->cond);
2578 priv->is_google = is_google_uri (priv->uri);
2579 } else if (server_unreachable) {
2580 priv->opened = FALSE;
2581 priv->read_only = TRUE;
2583 gchar *msg = g_strdup_printf (_("Server is unreachable, calendar is opened in read-only mode.\nError message: %s"), local_error->message);
2584 e_cal_backend_notify_error (E_CAL_BACKEND (cbdav), msg);
2586 g_clear_error (&local_error);
2588 } else if (local_error) {
2589 g_propagate_error (error, local_error);
2594 caldav_do_open (ECalBackendSync *backend,
2596 GCancellable *cancellable,
2597 gboolean only_if_exists,
2600 ECalBackendCalDAV *cbdav;
2601 ECalBackendCalDAVPrivate *priv;
2604 cbdav = E_CAL_BACKEND_CALDAV (backend);
2607 g_mutex_lock (priv->busy_lock);
2609 /* let it decide the 'getctag' extension availability again */
2610 priv->ctag_supported = TRUE;
2612 if (!priv->loaded && !initialize_backend (cbdav, perror)) {
2613 g_mutex_unlock (priv->busy_lock);
2617 online = e_backend_get_online (E_BACKEND (backend));
2619 if (!priv->do_offline && !online) {
2620 g_mutex_unlock (priv->busy_lock);
2621 g_propagate_error (perror, EDC_ERROR (RepositoryOffline));
2625 priv->loaded = TRUE;
2626 priv->opened = TRUE;
2627 priv->is_google = FALSE;
2630 GError *local_error = NULL;
2632 open_calendar (cbdav, &local_error);
2634 if (g_error_matches (local_error, E_DATA_CAL_ERROR, AuthenticationRequired) || g_error_matches (local_error, E_DATA_CAL_ERROR, AuthenticationFailed)) {
2635 g_clear_error (&local_error);
2636 e_cal_backend_notify_auth_required (E_CAL_BACKEND (cbdav), TRUE, priv->credentials);
2638 e_cal_backend_notify_opened (E_CAL_BACKEND (backend), NULL);
2642 g_propagate_error (perror, local_error);
2644 priv->read_only = TRUE;
2645 e_cal_backend_notify_opened (E_CAL_BACKEND (backend), NULL);
2648 e_cal_backend_notify_readonly (E_CAL_BACKEND (backend), priv->read_only);
2649 e_cal_backend_notify_online (E_CAL_BACKEND (backend), online);
2651 g_mutex_unlock (priv->busy_lock);
2655 caldav_authenticate_user (ECalBackendSync *backend,
2656 GCancellable *cancellable,
2657 ECredentials *credentials,
2660 ECalBackendCalDAV *cbdav;
2661 ECalBackendCalDAVPrivate *priv;
2663 cbdav = E_CAL_BACKEND_CALDAV (backend);
2666 g_mutex_lock (priv->busy_lock);
2668 e_credentials_free (priv->credentials);
2669 priv->credentials = NULL;
2671 if (!credentials || !e_credentials_has_key (credentials, E_CREDENTIALS_KEY_USERNAME)) {
2672 g_mutex_unlock (priv->busy_lock);
2673 g_propagate_error (error, EDC_ERROR (AuthenticationRequired));
2677 priv->credentials = e_credentials_new_clone (credentials);
2679 open_calendar (cbdav, error);
2681 g_mutex_unlock (priv->busy_lock);
2685 caldav_refresh (ECalBackendSync *backend,
2687 GCancellable *cancellable,
2690 ECalBackendCalDAV *cbdav;
2691 ECalBackendCalDAVPrivate *priv;
2694 cbdav = E_CAL_BACKEND_CALDAV (backend);
2697 g_mutex_lock (priv->busy_lock);
2700 || priv->slave_cmd != SLAVE_SHOULD_SLEEP
2701 || !check_state (cbdav, &online, NULL)
2703 g_mutex_unlock (priv->busy_lock);
2707 priv->slave_cmd = SLAVE_SHOULD_WORK;
2710 g_cond_signal (priv->cond);
2711 g_mutex_unlock (priv->busy_lock);
2715 caldav_remove (ECalBackendSync *backend,
2717 GCancellable *cancellable,
2720 ECalBackendCalDAV *cbdav;
2721 ECalBackendCalDAVPrivate *priv;
2724 cbdav = E_CAL_BACKEND_CALDAV (backend);
2727 /* first tell it to die, then wait for its lock */
2728 priv->slave_cmd = SLAVE_SHOULD_DIE;
2730 g_mutex_lock (priv->busy_lock);
2732 if (!priv->loaded) {
2733 g_mutex_unlock (priv->busy_lock);
2737 if (!check_state (cbdav, &online, NULL)) {
2738 /* lie here a bit, but otherwise the calendar will not be removed, even it should */
2739 g_print (G_STRLOC ": Failed to check state");
2742 e_cal_backend_store_remove (priv->store);
2744 priv->loaded = FALSE;
2745 priv->opened = FALSE;
2747 if (priv->synch_slave) {
2748 g_cond_signal (priv->cond);
2750 /* wait until the slave died */
2751 g_cond_wait (priv->slave_gone_cond, priv->busy_lock);
2754 g_mutex_unlock (priv->busy_lock);
2758 remove_comp_from_cache_cb (gpointer value,
2761 ECalComponent *comp = value;
2762 ECalBackendStore *store = user_data;
2763 ECalComponentId *id;
2765 g_return_if_fail (comp != NULL);
2766 g_return_if_fail (store != NULL);
2768 id = e_cal_component_get_id (comp);
2769 g_return_if_fail (id != NULL);
2771 e_cal_backend_store_remove_component (store, id->uid, id->rid);
2772 e_cal_component_free_id (id);
2776 remove_comp_from_cache (ECalBackendCalDAV *cbdav,
2780 ECalBackendCalDAVPrivate *priv;
2781 gboolean res = FALSE;
2785 if (!rid || !*rid) {
2786 /* get with detached instances */
2787 GSList *objects = e_cal_backend_store_get_components_by_uid (priv->store, uid);
2790 g_slist_foreach (objects, (GFunc) remove_comp_from_cache_cb, priv->store);
2791 g_slist_foreach (objects, (GFunc) g_object_unref, NULL);
2792 g_slist_free (objects);
2797 res = e_cal_backend_store_remove_component (priv->store, uid, rid);
2804 add_detached_recur_to_vcalendar_cb (gpointer value,
2807 icalcomponent *recurrence = e_cal_component_get_icalcomponent (value);
2808 icalcomponent *vcalendar = user_data;
2810 icalcomponent_add_component (
2812 icalcomponent_new_clone (recurrence));
2816 sort_master_first (gconstpointer a,
2819 icalcomponent *ca, *cb;
2821 ca = e_cal_component_get_icalcomponent ((ECalComponent *) a);
2822 cb = e_cal_component_get_icalcomponent ((ECalComponent *) b);
2833 return icaltime_compare (icalcomponent_get_recurrenceid (ca), icalcomponent_get_recurrenceid (cb));
2836 /* Returns new icalcomponent, with all detached instances stored in a cache.
2837 * The cache lock should be locked when called this function.
2839 static icalcomponent *
2840 get_comp_from_cache (ECalBackendCalDAV *cbdav,
2846 ECalBackendCalDAVPrivate *priv;
2847 icalcomponent *icalcomp = NULL;
2851 if (rid == NULL || !*rid) {
2852 /* get with detached instances */
2853 GSList *objects = e_cal_backend_store_get_components_by_uid (priv->store, uid);
2859 if (g_slist_length (objects) == 1) {
2860 ECalComponent *comp = objects->data;
2862 /* will be unreffed a bit later */
2864 icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
2866 /* if we have detached recurrences, return a VCALENDAR */
2867 icalcomp = e_cal_util_new_top_level ();
2869 objects = g_slist_sort (objects, sort_master_first);
2871 /* add all detached recurrences and the master object */
2872 g_slist_foreach (objects, add_detached_recur_to_vcalendar_cb, icalcomp);
2875 /* every component has set same href and etag, thus it doesn't matter where it will be read */
2877 *href = ecalcomp_get_href (objects->data);
2879 *etag = ecalcomp_get_etag (objects->data);
2881 g_slist_foreach (objects, (GFunc) g_object_unref, NULL);
2882 g_slist_free (objects);
2884 /* get the exact object */
2885 ECalComponent *comp = e_cal_backend_store_get_component (priv->store, uid, rid);
2888 icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
2890 *href = ecalcomp_get_href (comp);
2892 *etag = ecalcomp_get_etag (comp);
2893 g_object_unref (comp);
2901 put_comp_to_cache (ECalBackendCalDAV *cbdav,
2902 icalcomponent *icalcomp,
2906 icalcomponent_kind my_kind;
2907 ECalComponent *comp;
2908 gboolean res = FALSE;
2910 g_return_val_if_fail (cbdav != NULL, FALSE);
2911 g_return_val_if_fail (icalcomp != NULL, FALSE);
2913 my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
2914 comp = e_cal_component_new ();
2916 if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
2917 icalcomponent *subcomp;
2919 /* remove all old components from the cache first */
2920 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
2922 subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
2923 remove_comp_from_cache (cbdav, icalcomponent_get_uid (subcomp), NULL);
2926 /* then put new. It's because some detached instances could be removed on the server. */
2927 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
2929 subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
2930 /* because reusing the same comp doesn't clear recur_id member properly */
2931 g_object_unref (comp);
2932 comp = e_cal_component_new ();
2934 if (e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (subcomp))) {
2936 ecalcomp_set_href (comp, href);
2938 ecalcomp_set_etag (comp, etag);
2940 if (put_component_to_store (cbdav, comp))
2944 } else if (icalcomponent_isa (icalcomp) == my_kind) {
2945 remove_comp_from_cache (cbdav, icalcomponent_get_uid (icalcomp), NULL);
2947 if (e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (icalcomp))) {
2949 ecalcomp_set_href (comp, href);
2951 ecalcomp_set_etag (comp, etag);
2953 res = put_component_to_store (cbdav, comp);
2957 g_object_unref (comp);
2963 remove_property (gpointer prop,
2966 icalcomponent_remove_property (icomp, prop);
2967 icalproperty_free (prop);
2971 strip_unneeded_x_props (icalcomponent *icomp)
2974 GSList *to_remove = NULL;
2976 g_return_if_fail (icomp != NULL);
2977 g_return_if_fail (icalcomponent_isa (icomp) != ICAL_VCALENDAR_COMPONENT);
2979 for (prop = icalcomponent_get_first_property (icomp, ICAL_X_PROPERTY);
2981 prop = icalcomponent_get_next_property (icomp, ICAL_X_PROPERTY)) {
2982 if (g_str_has_prefix (icalproperty_get_x_name (prop), X_E_CALDAV)) {
2983 to_remove = g_slist_prepend (to_remove, prop);
2987 for (prop = icalcomponent_get_first_property (icomp, ICAL_XLICERROR_PROPERTY);
2989 prop = icalcomponent_get_next_property (icomp, ICAL_XLICERROR_PROPERTY)) {
2990 to_remove = g_slist_prepend (to_remove, prop);
2993 g_slist_foreach (to_remove, remove_property, icomp);
2994 g_slist_free (to_remove);
2998 is_stored_on_server (ECalBackendCalDAV *cbdav,
3001 SoupURI *my_uri, *test_uri;
3004 g_return_val_if_fail (cbdav != NULL, FALSE);
3005 g_return_val_if_fail (cbdav->priv != NULL, FALSE);
3006 g_return_val_if_fail (cbdav->priv->uri != NULL, FALSE);
3007 g_return_val_if_fail (uri != NULL, FALSE);
3009 my_uri = soup_uri_new (cbdav->priv->uri);
3010 g_return_val_if_fail (my_uri != NULL, FALSE);
3012 test_uri = soup_uri_new (uri);
3014 soup_uri_free (my_uri);
3018 res = my_uri->host && test_uri->host && g_ascii_strcasecmp (my_uri->host, test_uri->host) == 0;
3020 soup_uri_free (my_uri);
3021 soup_uri_free (test_uri);
3027 convert_to_inline_attachment (ECalBackendCalDAV *cbdav,
3028 icalcomponent *icalcomp)
3030 icalcomponent *cclone;
3032 GSList *to_remove = NULL;
3034 g_return_if_fail (icalcomp != NULL);
3036 cclone = icalcomponent_new_clone (icalcomp);
3038 /* Remove local url attachments first */
3039 for (p = icalcomponent_get_first_property (icalcomp, ICAL_ATTACH_PROPERTY);
3041 p = icalcomponent_get_next_property (icalcomp, ICAL_ATTACH_PROPERTY)) {
3044 attach = icalproperty_get_attach ((const icalproperty *) p);
3045 if (icalattach_get_is_url (attach)) {
3048 url = icalattach_get_url (attach);
3049 if (g_str_has_prefix (url, LOCAL_PREFIX))
3050 to_remove = g_slist_prepend (to_remove, p);
3053 g_slist_foreach (to_remove, remove_property, icalcomp);
3054 g_slist_free (to_remove);
3056 /* convert local url attachments to inline attachments now */
3057 for (p = icalcomponent_get_first_property (cclone, ICAL_ATTACH_PROPERTY);
3059 p = icalcomponent_get_next_property (cclone, ICAL_ATTACH_PROPERTY)) {
3062 GError *error = NULL;
3068 attach = icalproperty_get_attach ((const icalproperty *) p);
3069 if (!icalattach_get_is_url (attach))
3072 uri = icalattach_get_url (attach);
3073 if (!g_str_has_prefix (uri, LOCAL_PREFIX))
3076 file = g_file_new_for_uri (uri);
3077 basename = g_file_get_basename (file);
3078 if (g_file_load_contents (file, NULL, &content, &len, NULL, &error)) {
3080 icalparameter *param;
3084 * do a base64 encoding so it can
3085 * be embedded in a soap message
3087 encoded = g_base64_encode ((guchar *) content, len);
3088 attach = icalattach_new_from_data (encoded, NULL, NULL);
3092 prop = icalproperty_new_attach (attach);
3093 icalattach_unref (attach);
3095 param = icalparameter_new_value (ICAL_VALUE_BINARY);
3096 icalproperty_add_parameter (prop, param);
3098 param = icalparameter_new_encoding (ICAL_ENCODING_BASE64);
3099 icalproperty_add_parameter (prop, param);
3101 param = icalparameter_new_x (basename);
3102 icalparameter_set_xname (param, X_E_CALDAV_ATTACHMENT_NAME);
3103 icalproperty_add_parameter (prop, param);
3105 icalcomponent_add_property (icalcomp, prop);
3107 g_warning ("%s\n", error->message);
3108 g_clear_error (&error);
3111 g_object_unref (file);
3113 icalcomponent_free (cclone);
3117 convert_to_url_attachment (ECalBackendCalDAV *cbdav,
3118 icalcomponent *icalcomp)
3120 ECalBackend *backend;
3121 GSList *to_remove = NULL, *to_remove_after_download = NULL;
3122 icalcomponent *cclone;
3126 g_return_if_fail (cbdav != NULL);
3127 g_return_if_fail (icalcomp != NULL);
3129 backend = E_CAL_BACKEND (cbdav);
3130 cclone = icalcomponent_new_clone (icalcomp);
3132 /* Remove all inline attachments first */
3133 for (p = icalcomponent_get_first_property (icalcomp, ICAL_ATTACH_PROPERTY);
3135 p = icalcomponent_get_next_property (icalcomp, ICAL_ATTACH_PROPERTY)) {
3138 attach = icalproperty_get_attach ((const icalproperty *) p);
3139 if (!icalattach_get_is_url (attach))
3140 to_remove = g_slist_prepend (to_remove, p);
3141 else if (is_stored_on_server (cbdav, icalattach_get_url (attach)))
3142 to_remove_after_download = g_slist_prepend (to_remove_after_download, p);
3144 g_slist_foreach (to_remove, remove_property, icalcomp);
3145 g_slist_free (to_remove);
3147 /* convert inline attachments to url attachments now */
3148 for (p = icalcomponent_get_first_property (cclone, ICAL_ATTACH_PROPERTY), fileindex = 0;
3150 p = icalcomponent_get_next_property (cclone, ICAL_ATTACH_PROPERTY), fileindex++) {
3153 gchar *decoded = NULL;
3154 gchar *basename, *local_filename;
3156 attach = icalproperty_get_attach ((const icalproperty *) p);
3157 if (icalattach_get_is_url (attach)) {
3158 const gchar *attach_url = icalattach_get_url (attach);
3159 GError *error = NULL;
3161 if (!is_stored_on_server (cbdav, attach_url))
3164 if (!caldav_server_download_attachment (cbdav, attach_url, &decoded, &len, &error)) {
3165 if (caldav_debug_show (DEBUG_ATTACHMENTS))
3166 g_print ("CalDAV::%s: Failed to download from a server: %s\n", G_STRFUNC, error ? error->message : "Unknown error");
3171 basename = icalproperty_get_parameter_as_string_r (p, X_E_CALDAV_ATTACHMENT_NAME);
3172 local_filename = e_cal_backend_create_cache_filename (backend, icalcomponent_get_uid (icalcomp), basename, fileindex);
3175 if (local_filename) {
3176 GError *error = NULL;
3178 if (decoded == NULL) {
3181 content = (gchar *) icalattach_get_data (attach);
3182 decoded = (gchar *) g_base64_decode (content, &len);
3185 if (g_file_set_contents (local_filename, decoded, len, &error)) {
3189 url = g_filename_to_uri (local_filename, NULL, NULL);
3190 attach = icalattach_new_from_url (url);
3191 prop = icalproperty_new_attach (attach);
3192 icalattach_unref (attach);
3193 icalcomponent_add_property (icalcomp, prop);
3196 g_warning ("%s\n", error->message);
3197 g_clear_error (&error);
3200 g_free (local_filename);
3204 icalcomponent_free (cclone);
3206 g_slist_foreach (to_remove_after_download, remove_property, icalcomp);
3207 g_slist_free (to_remove_after_download);
3211 remove_files (const gchar *dir,
3212 const gchar *fileprefix)
3216 g_return_if_fail (dir != NULL);
3217 g_return_if_fail (fileprefix != NULL);
3218 g_return_if_fail (*fileprefix != '\0');
3220 d = g_dir_open (dir, 0, NULL);
3223 gint len = strlen (fileprefix);
3225 while ((entry = g_dir_read_name (d)) != NULL) {
3226 if (entry && strncmp (entry, fileprefix, len) == 0) {
3229 path = g_build_filename (dir, entry, NULL);
3230 if (!g_file_test (path, G_FILE_TEST_IS_DIR))
3240 remove_cached_attachment (ECalBackendCalDAV *cbdav,
3243 ECalBackendCalDAVPrivate *priv;
3249 g_return_if_fail (cbdav != NULL);
3250 g_return_if_fail (uid != NULL);
3253 l = e_cal_backend_store_get_components_by_uid (priv->store, uid);
3254 len = g_slist_length (l);
3255 g_slist_foreach (l, (GFunc) g_object_unref, NULL);
3260 dir = e_cal_backend_create_cache_filename (E_CAL_BACKEND (cbdav), uid, "a", 0);
3264 fileprefix = g_strrstr (dir, G_DIR_SEPARATOR_S);
3270 fileprefix[strlen (fileprefix) - 1] = '\0';
3272 remove_files (dir, fileprefix);
3278 /* callback for icalcomponent_foreach_tzid */
3280 ECalBackendStore *store;
3281 icalcomponent *vcal_comp;
3282 icalcomponent *icalcomp;
3286 add_timezone_cb (icalparameter *param,
3291 icalcomponent *vtz_comp;
3292 ForeachTzidData *f_data = (ForeachTzidData *) data;
3294 tzid = icalparameter_get_tzid (param);
3298 tz = icalcomponent_get_timezone (f_data->vcal_comp, tzid);
3302 tz = icalcomponent_get_timezone (f_data->icalcomp, tzid);
3304 tz = icaltimezone_get_builtin_timezone_from_tzid (tzid);
3306 tz = (icaltimezone *) e_cal_backend_store_get_timezone (f_data->store, tzid);
3311 vtz_comp = icaltimezone_get_component (tz);
3315 icalcomponent_add_component (f_data->vcal_comp,
3316 icalcomponent_new_clone (vtz_comp));
3320 add_timezones_from_component (ECalBackendCalDAV *cbdav,
3321 icalcomponent *vcal_comp,
3322 icalcomponent *icalcomp)
3324 ForeachTzidData f_data;
3325 ECalBackendCalDAVPrivate *priv;
3327 g_return_if_fail (cbdav != NULL);
3328 g_return_if_fail (vcal_comp != NULL);
3329 g_return_if_fail (icalcomp != NULL);
3333 f_data.store = priv->store;
3334 f_data.vcal_comp = vcal_comp;
3335 f_data.icalcomp = icalcomp;
3337 icalcomponent_foreach_tzid (icalcomp, add_timezone_cb, &f_data);
3340 /* also removes X-EVOLUTION-CALDAV from all the components */
3342 pack_cobj (ECalBackendCalDAV *cbdav,
3343 icalcomponent *icomp)
3345 icalcomponent *calcomp;
3348 if (icalcomponent_isa (icomp) != ICAL_VCALENDAR_COMPONENT) {
3349 icalcomponent *cclone;
3351 calcomp = e_cal_util_new_top_level ();
3353 cclone = icalcomponent_new_clone (icomp);
3354 strip_unneeded_x_props (cclone);
3355 convert_to_inline_attachment (cbdav, cclone);
3356 icalcomponent_add_component (calcomp, cclone);
3357 add_timezones_from_component (cbdav, calcomp, cclone);
3359 icalcomponent *subcomp;
3360 icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
3362 calcomp = icalcomponent_new_clone (icomp);
3363 for (subcomp = icalcomponent_get_first_component (calcomp, my_kind);
3365 subcomp = icalcomponent_get_next_component (calcomp, my_kind)) {
3366 strip_unneeded_x_props (subcomp);
3367 convert_to_inline_attachment (cbdav, subcomp);
3368 add_timezones_from_component (cbdav, calcomp, subcomp);
3372 objstr = icalcomponent_as_ical_string_r (calcomp);
3373 icalcomponent_free (calcomp);
3382 sanitize_component (ECalBackend *cb,
3383 ECalComponent *comp)
3385 ECalComponentDateTime dt;
3388 /* Check dtstart, dtend and due's timezone, and convert it to local
3389 * default timezone if the timezone is not in our builtin timezone
3391 e_cal_component_get_dtstart (comp, &dt);
3392 if (dt.value && dt.tzid) {
3393 zone = caldav_internal_get_timezone (cb, dt.tzid);
3395 g_free ((gchar *) dt.tzid);
3396 dt.tzid = g_strdup ("UTC");
3397 e_cal_component_set_dtstart (comp, &dt);
3400 e_cal_component_free_datetime (&dt);
3402 e_cal_component_get_dtend (comp, &dt);
3403 if (dt.value && dt.tzid) {
3404 zone = caldav_internal_get_timezone (cb, dt.tzid);
3406 g_free ((gchar *) dt.tzid);
3407 dt.tzid = g_strdup ("UTC");
3408 e_cal_component_set_dtend (comp, &dt);
3411 e_cal_component_free_datetime (&dt);
3413 e_cal_component_get_due (comp, &dt);
3414 if (dt.value && dt.tzid) {
3415 zone = caldav_internal_get_timezone (cb, dt.tzid);
3417 g_free ((gchar *) dt.tzid);
3418 dt.tzid = g_strdup ("UTC");
3419 e_cal_component_set_due (comp, &dt);
3422 e_cal_component_free_datetime (&dt);
3423 e_cal_component_abort_sequence (comp);
3427 cache_contains (ECalBackendCalDAV *cbdav,
3431 ECalBackendCalDAVPrivate *priv;
3433 ECalComponent *comp;
3435 g_return_val_if_fail (cbdav != NULL, FALSE);
3436 g_return_val_if_fail (uid != NULL, FALSE);
3439 g_return_val_if_fail (priv != NULL && priv->store != NULL, FALSE);
3441 comp = e_cal_backend_store_get_component (priv->store, uid, rid);
3445 g_object_unref (comp);
3450 /* Returns subcomponent of icalcomp, which is a master object, or icalcomp itself, if it's not a VCALENDAR;
3451 * Do not free returned pointer, it'll be freed together with the icalcomp.
3453 static icalcomponent *
3454 get_master_comp (ECalBackendCalDAV *cbdav,
3455 icalcomponent *icalcomp)
3457 icalcomponent *master = icalcomp;
3459 g_return_val_if_fail (cbdav != NULL, NULL);
3460 g_return_val_if_fail (icalcomp != NULL, NULL);
3462 if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
3463 icalcomponent *subcomp;
3464 icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
3468 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
3470 subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
3471 struct icaltimetype sub_rid = icalcomponent_get_recurrenceid (subcomp);
3473 if (icaltime_is_null_time (sub_rid)) {
3484 remove_instance (ECalBackendCalDAV *cbdav,
3485 icalcomponent *icalcomp,
3486 struct icaltimetype rid,
3488 gboolean also_exdate)
3490 icalcomponent *master = icalcomp;
3491 gboolean res = FALSE;
3493 g_return_val_if_fail (icalcomp != NULL, res);
3494 g_return_val_if_fail (!icaltime_is_null_time (rid), res);
3496 /* remove an instance only */
3497 if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
3498 icalcomponent *subcomp;
3499 icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
3501 gboolean start_first = FALSE;
3505 /* remove old instance first */
3506 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
3508 subcomp = start_first ? icalcomponent_get_first_component (icalcomp, my_kind) : icalcomponent_get_next_component (icalcomp, my_kind)) {
3509 struct icaltimetype sub_rid = icalcomponent_get_recurrenceid (subcomp);
3511 start_first = FALSE;
3513 if (icaltime_is_null_time (sub_rid)) {
3516 } else if (icaltime_compare (sub_rid, rid) == 0) {
3517 icalcomponent_remove_component (icalcomp, subcomp);
3518 icalcomponent_free (subcomp);
3522 /* either no master or master not as the first component, thus rescan */
3531 /* whether left at least one instance or a master object */
3537 if (master && also_exdate) {
3538 e_cal_util_remove_instances (master, rid, mod);
3544 static icalcomponent *
3545 replace_master (ECalBackendCalDAV *cbdav,
3546 icalcomponent *old_comp,
3547 icalcomponent *new_master)
3549 icalcomponent *old_master;
3550 if (icalcomponent_isa (old_comp) != ICAL_VCALENDAR_COMPONENT) {
3551 icalcomponent_free (old_comp);
3555 old_master = get_master_comp (cbdav, old_comp);
3557 /* no master, strange */
3558 icalcomponent_free (new_master);
3560 icalcomponent_remove_component (old_comp, old_master);
3561 icalcomponent_free (old_master);
3563 icalcomponent_add_component (old_comp, new_master);
3569 /* the resulting component should be unreffed when done with it;
3570 the fallback_comp is cloned, if used */
3571 static ECalComponent *
3572 get_ecalcomp_master_from_cache_or_fallback (ECalBackendCalDAV *cbdav,
3575 ECalComponent *fallback_comp)
3577 ECalComponent *comp = NULL;
3578 icalcomponent *icalcomp;
3580 g_return_val_if_fail (cbdav != NULL, NULL);
3581 g_return_val_if_fail (uid != NULL, NULL);
3583 icalcomp = get_comp_from_cache (cbdav, uid, rid, NULL, NULL);
3585 icalcomponent *master = get_master_comp (cbdav, icalcomp);
3588 comp = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master));
3591 icalcomponent_free (icalcomp);
3594 if (!comp && fallback_comp)
3595 comp = e_cal_component_clone (fallback_comp);
3600 /* a busy_lock is supposed to be locked already, when calling this function */
3602 do_create_object (ECalBackendCalDAV *cbdav,
3603 const gchar *in_calobj,
3605 ECalComponent **new_component,
3608 ECalComponent *comp;
3609 gboolean online, did_put = FALSE;
3610 struct icaltimetype current;
3611 icalcomponent *icalcomp;
3612 const gchar *comp_uid;
3614 if (!check_state (cbdav, &online, perror))
3617 comp = e_cal_component_new_from_string (in_calobj);
3620 g_propagate_error (perror, EDC_ERROR (InvalidObject));
3624 icalcomp = e_cal_component_get_icalcomponent (comp);
3625 if (icalcomp == NULL) {
3626 g_object_unref (comp);
3627 g_propagate_error (perror, EDC_ERROR (InvalidObject));
3631 comp_uid = icalcomponent_get_uid (icalcomp);
3635 new_uid = e_cal_component_gen_uid ();
3637 g_object_unref (comp);
3638 g_propagate_error (perror, EDC_ERROR (InvalidObject));
3642 icalcomponent_set_uid (icalcomp, new_uid);
3643 comp_uid = icalcomponent_get_uid (icalcomp);
3648 /* check the object is not in our cache */
3649 if (cache_contains (cbdav, comp_uid, NULL)) {
3650 g_object_unref (comp);
3651 g_propagate_error (perror, EDC_ERROR (ObjectIdAlreadyExists));
3655 /* Set the created and last modified times on the component */
3656 current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
3657 e_cal_component_set_created (comp, ¤t);
3658 e_cal_component_set_last_modified (comp, ¤t);
3660 /* sanitize the component*/
3661 sanitize_component ((ECalBackend *) cbdav, comp);
3664 CalDAVObject object;
3666 object.href = ecalcomp_gen_href (comp);
3668 object.cdata = pack_cobj (cbdav, icalcomp);
3670 did_put = caldav_server_put_object (cbdav, &object, icalcomp, perror);
3672 caldav_object_free (&object, FALSE);
3674 /* mark component as out of synch */
3675 /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_CREATED); */
3680 *uid = g_strdup (comp_uid);
3683 *new_component = get_ecalcomp_master_from_cache_or_fallback (cbdav, comp_uid, NULL, comp);
3686 g_object_unref (comp);
3689 /* a busy_lock is supposed to be locked already, when calling this function */
3691 do_modify_object (ECalBackendCalDAV *cbdav,
3692 const gchar *calobj,
3694 ECalComponent **old_component,
3695 ECalComponent **new_component,
3698 ECalBackendCalDAVPrivate *priv;
3699 ECalComponent *comp;
3700 icalcomponent *cache_comp;
3701 gboolean online, did_put = FALSE;
3702 ECalComponentId *id;
3703 struct icaltimetype current;
3704 gchar *href = NULL, *etag = NULL;
3709 *new_component = NULL;
3711 if (!check_state (cbdav, &online, error))
3714 comp = e_cal_component_new_from_string (calobj);
3717 g_propagate_error (error, EDC_ERROR (InvalidObject));
3721 if (!e_cal_component_get_icalcomponent (comp) ||
3722 icalcomponent_isa (e_cal_component_get_icalcomponent (comp)) != e_cal_backend_get_kind (E_CAL_BACKEND (cbdav))) {
3723 g_object_unref (comp);
3724 g_propagate_error (error, EDC_ERROR (InvalidObject));
3728 /* Set the last modified time on the component */
3729 current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
3730 e_cal_component_set_last_modified (comp, ¤t);
3732 /* sanitize the component */
3733 sanitize_component ((ECalBackend *) cbdav, comp);
3735 id = e_cal_component_get_id (comp);
3736 e_return_data_cal_error_if_fail (id != NULL, InvalidObject);
3738 /* fetch full component from cache, it will be pushed to the server */
3739 cache_comp = get_comp_from_cache (cbdav, id->uid, NULL, &href, &etag);
3741 if (cache_comp == NULL) {
3742 e_cal_component_free_id (id);
3743 g_object_unref (comp);
3746 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
3751 /* mark component as out of synch */
3752 /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_MODIFIED);*/
3755 if (old_component) {
3756 *old_component = NULL;
3758 if (e_cal_component_is_instance (comp)) {
3759 /* set detached instance as the old object, if any */
3760 ECalComponent *old_instance = e_cal_backend_store_get_component (priv->store, id->uid, id->rid);
3762 /* This will give a reference to 'old_component' */
3764 *old_component = e_cal_component_clone (old_instance);
3765 g_object_unref (old_instance);
3769 if (!*old_component) {
3770 icalcomponent *master = get_master_comp (cbdav, cache_comp);
3773 /* set full component as the old object */
3774 *old_component = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master));
3780 case CALOBJ_MOD_ONLY_THIS:
3781 case CALOBJ_MOD_THIS:
3782 if (e_cal_component_is_instance (comp)) {
3783 icalcomponent *new_comp = e_cal_component_get_icalcomponent (comp);
3785 /* new object is only this instance */
3787 *new_component = e_cal_component_clone (comp);
3789 /* add the detached instance */
3790 if (icalcomponent_isa (cache_comp) == ICAL_VCALENDAR_COMPONENT) {
3791 /* do not modify the EXDATE, as the component will be put back */
3792 remove_instance (cbdav, cache_comp, icalcomponent_get_recurrenceid (new_comp), mod, FALSE);
3794 /* this is only a master object, thus make is a VCALENDAR component */
3795 icalcomponent *icomp;
3797 icomp = e_cal_util_new_top_level ();
3798 icalcomponent_add_component (icomp, cache_comp);
3800 /* no need to free the cache_comp, as it is inside icomp */
3804 if (cache_comp && priv->is_google) {
3805 icalcomponent_set_sequence (cache_comp, icalcomponent_get_sequence (cache_comp) + 1);
3806 icalcomponent_set_sequence (new_comp, icalcomponent_get_sequence (new_comp) + 1);
3809 /* add the detached instance finally */
3810 icalcomponent_add_component (cache_comp, icalcomponent_new_clone (new_comp));
3812 cache_comp = replace_master (cbdav, cache_comp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
3815 case CALOBJ_MOD_ALL:
3816 cache_comp = replace_master (cbdav, cache_comp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
3818 case CALOBJ_MOD_THISANDPRIOR:
3819 case CALOBJ_MOD_THISANDFUTURE:
3824 CalDAVObject object;
3828 object.cdata = pack_cobj (cbdav, cache_comp);
3830 did_put = caldav_server_put_object (cbdav, &object, cache_comp, error);
3832 caldav_object_free (&object, FALSE);
3836 /* mark component as out of synch */
3837 /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_MODIFIED);*/
3841 if (new_component && !*new_component) {
3842 /* read the comp from cache again, as some servers can modify it on put */
3843 *new_component = get_ecalcomp_master_from_cache_or_fallback (cbdav, id->uid, id->rid, NULL);
3845 g_warn_if_fail (*new_component != NULL);
3849 e_cal_component_free_id (id);
3850 icalcomponent_free (cache_comp);
3851 g_object_unref (comp);
3856 /* a busy_lock is supposed to be locked already, when calling this function */
3858 do_remove_object (ECalBackendCalDAV *cbdav,
3862 ECalComponent **old_component,
3863 ECalComponent **new_component,
3866 ECalBackendCalDAVPrivate *priv;
3867 icalcomponent *cache_comp;
3869 gchar *href = NULL, *etag = NULL;
3874 *new_component = NULL;
3876 if (!check_state (cbdav, &online, perror))
3879 cache_comp = get_comp_from_cache (cbdav, uid, NULL, &href, &etag);
3881 if (cache_comp == NULL) {
3882 g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
3886 if (old_component) {
3887 ECalComponent *old = e_cal_backend_store_get_component (priv->store, uid, rid);
3890 *old_component = e_cal_component_clone (old);
3891 g_object_unref (old);
3893 icalcomponent *master = get_master_comp (cbdav, cache_comp);
3896 *old_component = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master));
3902 case CALOBJ_MOD_ONLY_THIS:
3903 case CALOBJ_MOD_THIS:
3905 /* remove one instance from the component */
3906 if (remove_instance (cbdav, cache_comp, icaltime_from_string (rid), mod, mod != CALOBJ_MOD_ONLY_THIS)) {
3907 if (new_component) {
3908 icalcomponent *master = get_master_comp (cbdav, cache_comp);
3911 *new_component = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master));
3914 /* this was the last instance, thus delete whole component */
3916 remove_comp_from_cache (cbdav, uid, NULL);
3919 /* remove whole object */
3920 remove_comp_from_cache (cbdav, uid, NULL);
3923 case CALOBJ_MOD_ALL:
3924 remove_comp_from_cache (cbdav, uid, NULL);
3926 case CALOBJ_MOD_THISANDPRIOR:
3927 case CALOBJ_MOD_THISANDFUTURE:
3932 CalDAVObject caldav_object;
3934 caldav_object.href = href;
3935 caldav_object.etag = etag;
3936 caldav_object.cdata = NULL;
3938 if (mod == CALOBJ_MOD_THIS && rid && *rid) {
3939 caldav_object.cdata = pack_cobj (cbdav, cache_comp);
3941 caldav_server_put_object (cbdav, &caldav_object, cache_comp, perror);
3943 caldav_server_delete_object (cbdav, &caldav_object, perror);
3945 caldav_object_free (&caldav_object, FALSE);
3949 /* mark component as out of synch */
3950 /*if (mod == CALOBJ_MOD_THIS && rid && *rid)
3951 ecalcomp_set_synch_state (cache_comp_master, ECALCOMP_LOCALLY_MODIFIED);
3953 ecalcomp_set_synch_state (cache_comp_master, ECALCOMP_LOCALLY_DELETED);*/
3955 remove_cached_attachment (cbdav, uid);
3957 icalcomponent_free (cache_comp);
3963 extract_objects (icalcomponent *icomp,
3964 icalcomponent_kind ekind,
3968 icalcomponent *scomp;
3969 icalcomponent_kind kind;
3971 e_return_data_cal_error_if_fail (icomp, InvalidArg);
3972 e_return_data_cal_error_if_fail (objects, InvalidArg);
3974 kind = icalcomponent_isa (icomp);
3976 if (kind == ekind) {
3977 *objects = g_slist_prepend (NULL, icomp);
3981 if (kind != ICAL_VCALENDAR_COMPONENT) {
3982 g_propagate_error (error, EDC_ERROR (InvalidObject));
3987 scomp = icalcomponent_get_first_component (icomp,
3991 /* Remove components from toplevel here */
3992 *objects = g_slist_prepend (*objects, scomp);
3993 icalcomponent_remove_component (icomp, scomp);
3995 scomp = icalcomponent_get_next_component (icomp, ekind);
4000 extract_timezones (ECalBackendCalDAV *cbdav,
4001 icalcomponent *icomp)
4003 ECalBackendCalDAVPrivate *priv;
4004 GSList *timezones = NULL, *iter;
4008 g_return_val_if_fail (cbdav != NULL, FALSE);
4009 g_return_val_if_fail (icomp != NULL, FALSE);
4011 extract_objects (icomp, ICAL_VTIMEZONE_COMPONENT, &timezones, &err);
4019 zone = icaltimezone_new ();
4020 for (iter = timezones; iter; iter = iter->next) {
4021 if (icaltimezone_set_component (zone, iter->data)) {
4022 e_cal_backend_store_put_timezone (priv->store, zone);
4024 icalcomponent_free (iter->data);
4028 icaltimezone_free (zone, TRUE);
4029 g_slist_free (timezones);
4035 process_object (ECalBackendCalDAV *cbdav,
4036 ECalComponent *ecomp,
4038 icalproperty_method method,
4041 ECalBackend *backend;
4042 struct icaltimetype now;
4044 gboolean is_declined, is_in_cache;
4046 ECalComponentId *id = e_cal_component_get_id (ecomp);
4049 backend = E_CAL_BACKEND (cbdav);
4051 e_return_data_cal_error_if_fail (id != NULL, InvalidObject);
4054 now = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
4055 e_cal_component_set_created (ecomp, &now);
4056 e_cal_component_set_last_modified (ecomp, &now);
4058 /* just to check whether component exists in a cache */
4059 is_in_cache = cache_contains (cbdav, id->uid, NULL) || cache_contains (cbdav, id->uid, id->rid);
4061 new_obj_str = e_cal_component_get_as_string (ecomp);
4062 mod = e_cal_component_is_instance (ecomp) ? CALOBJ_MOD_THIS : CALOBJ_MOD_ALL;
4065 case ICAL_METHOD_PUBLISH:
4066 case ICAL_METHOD_REQUEST:
4067 case ICAL_METHOD_REPLY:
4068 is_declined = e_cal_backend_user_declined (e_cal_component_get_icalcomponent (ecomp));
4071 ECalComponent *new_component = NULL, *old_component = NULL;
4073 do_modify_object (cbdav, new_obj_str, mod,
4074 &old_component, &new_component, &err);
4077 e_cal_backend_notify_component_created (backend, new_component);
4079 e_cal_backend_notify_component_modified (backend, old_component, new_component);
4083 g_object_unref (new_component);
4085 g_object_unref (old_component);
4087 ECalComponent *new_component = NULL, *old_component = NULL;
4089 do_remove_object (cbdav, id->uid, id->rid, mod, &old_component, &new_component, &err);
4091 if (new_component) {
4092 e_cal_backend_notify_component_modified (backend, old_component, new_component);
4094 e_cal_backend_notify_component_removed (backend, id, old_component, NULL);
4099 g_object_unref (new_component);
4101 g_object_unref (old_component);
4103 } else if (!is_declined) {
4104 ECalComponent *new_component = NULL;
4106 do_create_object (cbdav, new_obj_str, NULL, &new_component, &err);
4109 e_cal_backend_notify_component_created (backend, new_component);
4113 g_object_unref (new_component);
4117 case ICAL_METHOD_CANCEL:
4119 ECalComponent *new_component = NULL, *old_component = NULL;
4121 do_remove_object (cbdav, id->uid, id->rid, CALOBJ_MOD_THIS, &old_component, &new_component, &err);
4123 if (new_component) {
4124 e_cal_backend_notify_component_modified (backend, old_component, new_component);
4126 e_cal_backend_notify_component_removed (backend, id, old_component, NULL);
4131 g_object_unref (new_component);
4133 g_object_unref (old_component);
4135 err = EDC_ERROR (ObjectNotFound);
4140 err = EDC_ERROR (UnsupportedMethod);
4144 e_cal_component_free_id (id);
4145 g_free (new_obj_str);
4148 g_propagate_error (error, err);
4152 do_receive_objects (ECalBackendSync *backend,
4154 GCancellable *cancellable,
4155 const gchar *calobj,
4158 ECalBackendCalDAV *cbdav;
4159 icalcomponent *icomp;
4160 icalcomponent_kind kind;
4161 icalproperty_method tmethod;
4163 GSList *objects, *iter;
4166 cbdav = E_CAL_BACKEND_CALDAV (backend);
4168 if (!check_state (cbdav, &online, perror))
4171 icomp = icalparser_parse_string (calobj);
4173 /* Try to parse cal object string */
4174 if (icomp == NULL) {
4175 g_propagate_error (perror, EDC_ERROR (InvalidObject));
4179 kind = e_cal_backend_get_kind (E_CAL_BACKEND (backend));
4180 extract_objects (icomp, kind, &objects, &err);
4183 icalcomponent_free (icomp);
4184 g_propagate_error (perror, err);
4188 /* Extract optional timezone compnents */
4189 extract_timezones (cbdav, icomp);
4191 tmethod = icalcomponent_get_method (icomp);
4193 for (iter = objects; iter && !err; iter = iter->next) {
4194 icalcomponent *scomp;
4195 ECalComponent *ecomp;
4196 icalproperty_method method;
4198 scomp = (icalcomponent *) iter->data;
4199 ecomp = e_cal_component_new ();
4201 e_cal_component_set_icalcomponent (ecomp, scomp);
4203 if (icalcomponent_get_first_property (scomp, ICAL_METHOD_PROPERTY)) {
4204 method = icalcomponent_get_method (scomp);
4209 process_object (cbdav, ecomp, online, method, &err);
4210 g_object_unref (ecomp);
4213 g_slist_free (objects);
4215 icalcomponent_free (icomp);
4218 g_propagate_error (perror, err);
4221 #define caldav_busy_stub(_func_name, _params, _call_func, _call_params) \
4223 _func_name _params \
4225 ECalBackendCalDAV *cbdav; \
4226 ECalBackendCalDAVPrivate *priv; \
4227 SlaveCommand old_slave_cmd; \
4228 gboolean was_slave_busy; \
4230 cbdav = E_CAL_BACKEND_CALDAV (backend); \
4231 priv = cbdav->priv; \
4233 /* this is done before locking */ \
4234 old_slave_cmd = priv->slave_cmd; \
4235 was_slave_busy = priv->slave_busy; \
4236 if (was_slave_busy) { \
4237 /* let it pause its work and do our job */ \
4238 priv->slave_cmd = SLAVE_SHOULD_SLEEP; \
4241 g_mutex_lock (priv->busy_lock); \
4242 _call_func _call_params; \
4244 /* this is done before unlocking */ \
4245 if (was_slave_busy) { \
4246 priv->slave_cmd = old_slave_cmd; \
4247 g_cond_signal (priv->cond); \
4250 g_mutex_unlock (priv->busy_lock); \
4254 caldav_create_object,
4255 (ECalBackendSync *backend,
4257 GCancellable *cancellable,
4258 const gchar *in_calobj,
4260 ECalComponent **new_component,
4270 caldav_modify_object,
4271 (ECalBackendSync *backend,
4273 GCancellable *cancellable,
4274 const gchar *calobj,
4276 ECalComponent **old_component,
4277 ECalComponent **new_component,
4288 caldav_remove_object,
4289 (ECalBackendSync *backend,
4291 GCancellable *cancellable,
4295 ECalComponent **old_component,
4296 ECalComponent **new_component,
4308 caldav_receive_objects,
4309 (ECalBackendSync *backend,
4311 GCancellable *cancellable,
4312 const gchar *calobj,
4322 caldav_send_objects (ECalBackendSync *backend,
4324 GCancellable *cancellable,
4325 const gchar *calobj,
4327 gchar **modified_calobj,
4331 *modified_calobj = g_strdup (calobj);
4335 caldav_get_object (ECalBackendSync *backend,
4337 GCancellable *cancellable,
4343 ECalBackendCalDAV *cbdav;
4344 icalcomponent *icalcomp;
4346 cbdav = E_CAL_BACKEND_CALDAV (backend);
4349 icalcomp = get_comp_from_cache (cbdav, uid, rid, NULL, NULL);
4352 g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
4356 *object = icalcomponent_as_ical_string_r (icalcomp);
4357 icalcomponent_free (icalcomp);
4361 caldav_add_timezone (ECalBackendSync *backend,
4363 GCancellable *cancellable,
4367 icalcomponent *tz_comp;
4368 ECalBackendCalDAV *cbdav;
4369 ECalBackendCalDAVPrivate *priv;
4371 cbdav = E_CAL_BACKEND_CALDAV (backend);
4373 e_return_data_cal_error_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav), InvalidArg);
4374 e_return_data_cal_error_if_fail (tzobj != NULL, InvalidArg);
4378 tz_comp = icalparser_parse_string (tzobj);
4380 g_propagate_error (error, EDC_ERROR (InvalidObject));
4384 if (icalcomponent_isa (tz_comp) == ICAL_VTIMEZONE_COMPONENT) {
4387 zone = icaltimezone_new ();
4388 icaltimezone_set_component (zone, tz_comp);
4390 e_cal_backend_store_put_timezone (priv->store, zone);
4392 icaltimezone_free (zone, TRUE);
4394 icalcomponent_free (tz_comp);
4399 caldav_get_object_list (ECalBackendSync *backend,
4401 GCancellable *cancellable,
4402 const gchar *sexp_string,
4406 ECalBackendCalDAV *cbdav;
4407 ECalBackendCalDAVPrivate *priv;
4408 ECalBackendSExp *sexp;
4411 GSList *list, *iter;
4412 time_t occur_start = -1, occur_end = -1;
4413 gboolean prunning_by_time;
4415 cbdav = E_CAL_BACKEND_CALDAV (backend);
4418 sexp = e_cal_backend_sexp_new (sexp_string);
4421 g_propagate_error (perror, EDC_ERROR (InvalidQuery));
4425 if (g_str_equal (sexp_string, "#t")) {
4433 prunning_by_time = e_cal_backend_sexp_evaluate_occur_times (sexp, &occur_start, &occur_end);
4435 bkend = E_CAL_BACKEND (backend);
4437 list = prunning_by_time ?
4438 e_cal_backend_store_get_components_occuring_in_range (priv->store, occur_start, occur_end)
4439 : e_cal_backend_store_get_components (priv->store);
4441 bkend = E_CAL_BACKEND (backend);
4443 for (iter = list; iter; iter = g_slist_next (iter)) {
4444 ECalComponent *comp = E_CAL_COMPONENT (iter->data);
4447 e_cal_backend_sexp_match_comp (sexp, comp, bkend)) {
4448 *objects = g_slist_prepend (*objects, e_cal_component_get_as_string (comp));
4451 g_object_unref (comp);
4454 g_object_unref (sexp);
4455 g_slist_free (list);
4459 caldav_start_view (ECalBackend *backend,
4460 EDataCalView *query)
4462 ECalBackendCalDAV *cbdav;
4463 ECalBackendCalDAVPrivate *priv;
4464 ECalBackendSExp *sexp;
4467 GSList *list, *iter;
4468 const gchar *sexp_string;
4469 time_t occur_start = -1, occur_end = -1;
4470 gboolean prunning_by_time;
4471 cbdav = E_CAL_BACKEND_CALDAV (backend);
4474 sexp_string = e_data_cal_view_get_text (query);
4475 sexp = e_cal_backend_sexp_new (sexp_string);
4477 /* FIXME:check invalid sexp */
4479 if (g_str_equal (sexp_string, "#t")) {
4484 prunning_by_time = e_cal_backend_sexp_evaluate_occur_times (sexp,
4488 bkend = E_CAL_BACKEND (backend);
4490 list = prunning_by_time ?
4491 e_cal_backend_store_get_components_occuring_in_range (priv->store, occur_start, occur_end)
4492 : e_cal_backend_store_get_components (priv->store);
4494 for (iter = list; iter; iter = g_slist_next (iter)) {
4495 ECalComponent *comp = E_CAL_COMPONENT (iter->data);
4498 e_cal_backend_sexp_match_comp (sexp, comp, bkend)) {
4499 e_data_cal_view_notify_components_added_1 (query, comp);
4502 g_object_unref (comp);
4505 g_object_unref (sexp);
4506 g_slist_free (list);
4508 e_data_cal_view_notify_complete (query, NULL /* Success */);
4512 caldav_get_free_busy (ECalBackendSync *backend,
4514 GCancellable *cancellable,
4515 const GSList *users,
4521 ECalBackendCalDAV *cbdav;
4522 ECalBackendCalDAVPrivate *priv;
4523 icalcomponent *icalcomp;
4524 ECalComponent *comp;
4525 ECalComponentDateTime dt;
4526 struct icaltimetype dtvalue;
4528 gchar *str, *usermail;
4530 GSList *attendees = NULL, *to_free = NULL;
4533 cbdav = E_CAL_BACKEND_CALDAV (backend);
4536 e_return_data_cal_error_if_fail (priv != NULL, InvalidArg);
4537 e_return_data_cal_error_if_fail (users != NULL, InvalidArg);
4538 e_return_data_cal_error_if_fail (freebusy != NULL, InvalidArg);
4539 e_return_data_cal_error_if_fail (start < end, InvalidArg);
4541 if (!priv->calendar_schedule) {
4542 g_propagate_error (error, EDC_ERROR_EX (OtherError, _("Calendar doesn't support Free/Busy")));
4546 if (!priv->schedule_outbox_url) {
4547 caldav_receive_schedule_outbox_url (cbdav);
4548 if (!priv->schedule_outbox_url) {
4549 priv->calendar_schedule = FALSE;
4550 g_propagate_error (error, EDC_ERROR_EX (OtherError, "Schedule outbox url not found"));
4555 comp = e_cal_component_new ();
4556 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_FREEBUSY);
4558 str = e_cal_component_gen_uid ();
4559 e_cal_component_set_uid (comp, str);
4562 utc = icaltimezone_get_utc_timezone ();
4563 dt.value = &dtvalue;
4564 dt.tzid = icaltimezone_get_tzid (utc);
4566 dtvalue = icaltime_current_time_with_zone (utc);
4567 e_cal_component_set_dtstamp (comp, &dtvalue);
4569 dtvalue = icaltime_from_timet_with_zone (start, FALSE, utc);
4570 e_cal_component_set_dtstart (comp, &dt);
4572 dtvalue = icaltime_from_timet_with_zone (end, FALSE, utc);
4573 e_cal_component_set_dtend (comp, &dt);
4575 usermail = get_usermail (E_CAL_BACKEND (backend));
4576 if (usermail && !*usermail) {
4581 if ((priv->credentials && e_credentials_has_key (priv->credentials, E_CREDENTIALS_KEY_USERNAME)) || usermail) {
4582 ECalComponentOrganizer organizer = {NULL};
4584 organizer.value = usermail ? usermail : e_credentials_peek (priv->credentials, E_CREDENTIALS_KEY_USERNAME);
4585 organizer.value = g_strconcat ("mailto:", organizer.value, NULL);
4587 e_cal_component_set_organizer (comp, &organizer);
4589 g_free ((gchar *) organizer.value);
4594 for (u = users; u; u = u->next) {
4595 ECalComponentAttendee *ca;
4596 gchar *temp = g_strconcat ("mailto:", (const gchar *)u->data, NULL);
4598 ca = g_new0 (ECalComponentAttendee, 1);
4601 ca->cutype = ICAL_CUTYPE_INDIVIDUAL;
4602 ca->status = ICAL_PARTSTAT_NEEDSACTION;
4603 ca->role = ICAL_ROLE_CHAIR;
4605 to_free = g_slist_prepend (to_free, temp);
4606 attendees = g_slist_append (attendees, ca);
4609 e_cal_component_set_attendee_list (comp, attendees);
4611 g_slist_foreach (attendees, (GFunc) g_free, NULL);
4612 g_slist_free (attendees);
4614 g_slist_foreach (to_free, (GFunc) g_free, NULL);
4615 g_slist_free (to_free);
4617 e_cal_component_abort_sequence (comp);
4619 /* put the free/busy request to a VCALENDAR */
4620 icalcomp = e_cal_util_new_top_level ();
4621 icalcomponent_set_method (icalcomp, ICAL_METHOD_REQUEST);
4622 icalcomponent_add_component (icalcomp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
4624 str = icalcomponent_as_ical_string_r (icalcomp);
4626 icalcomponent_free (icalcomp);
4627 g_object_unref (comp);
4629 e_return_data_cal_error_if_fail (str != NULL, OtherError);
4631 caldav_post_freebusy (cbdav, priv->schedule_outbox_url, &str, &err);
4634 /* parse returned xml */
4637 doc = xmlReadMemory (str, strlen (str), "response.xml", NULL, 0);
4639 xmlXPathContextPtr xpctx;
4640 xmlXPathObjectPtr result;
4642 xpctx = xmlXPathNewContext (doc);
4643 xmlXPathRegisterNs (xpctx, (xmlChar *) "D", (xmlChar *) "DAV:");
4644 xmlXPathRegisterNs (xpctx, (xmlChar *) "C", (xmlChar *) "urn:ietf:params:xml:ns:caldav");
4646 result = xpath_eval (xpctx, "/C:schedule-response/C:response");
4648 if (result == NULL || result->type != XPATH_NODESET) {
4649 err = EDC_ERROR_EX (OtherError, "Unexpected result in schedule-response");
4653 n = xmlXPathNodeSetGetLength (result->nodesetval);
4654 for (i = 0; i < n; i++) {
4657 tmp = xp_object_get_string (xpath_eval (xpctx, "string(/C:schedule-response/C:response[%d]/C:calendar-data)", i + 1));
4659 GSList *objects = NULL, *o;
4661 icalcomp = icalparser_parse_string (tmp);
4663 extract_objects (icalcomp, ICAL_VFREEBUSY_COMPONENT, &objects, &err);
4664 if (icalcomp && !err) {
4665 for (o = objects; o; o = o->next) {
4666 gchar *obj_str = icalcomponent_as_ical_string_r (o->data);
4668 if (obj_str && *obj_str)
4669 *freebusy = g_slist_append (*freebusy, obj_str);
4675 g_slist_foreach (objects, (GFunc) icalcomponent_free, NULL);
4676 g_slist_free (objects);
4679 icalcomponent_free (icalcomp);
4690 xmlXPathFreeObject (result);
4691 xmlXPathFreeContext (xpctx);
4699 g_propagate_error (error, err);
4703 caldav_notify_online_cb (ECalBackend *backend,
4706 ECalBackendCalDAV *cbdav;
4707 ECalBackendCalDAVPrivate *priv;
4710 cbdav = E_CAL_BACKEND_CALDAV (backend);
4713 /*g_mutex_lock (priv->busy_lock);*/
4715 online = e_backend_get_online (E_BACKEND (backend));
4717 if (!priv->loaded) {
4718 e_cal_backend_notify_online (backend, online);
4719 /*g_mutex_unlock (priv->busy_lock);*/
4724 /* Wake up the slave thread */
4725 priv->slave_cmd = SLAVE_SHOULD_WORK;
4726 g_cond_signal (priv->cond);
4728 soup_session_abort (priv->session);
4729 priv->slave_cmd = SLAVE_SHOULD_SLEEP;
4732 e_cal_backend_notify_online (backend, online);
4734 /*g_mutex_unlock (priv->busy_lock);*/
4737 static icaltimezone *
4738 caldav_internal_get_timezone (ECalBackend *backend,
4742 ECalBackendCalDAV *cbdav;
4743 ECalBackendCalDAVPrivate *priv;
4745 cbdav = E_CAL_BACKEND_CALDAV (backend);
4750 zone = (icaltimezone *) e_cal_backend_store_get_timezone (priv->store, tzid);
4752 if (!zone && E_CAL_BACKEND_CLASS (parent_class)->internal_get_timezone)
4753 zone = E_CAL_BACKEND_CLASS (parent_class)->internal_get_timezone (backend, tzid);
4759 caldav_source_changed_cb (ESource *source,
4760 ECalBackendCalDAV *cbdav)
4762 ECalBackendCalDAVPrivate *priv;
4763 SlaveCommand old_slave_cmd;
4764 gboolean old_slave_busy;
4766 g_return_if_fail (source != NULL);
4767 g_return_if_fail (cbdav != NULL);
4770 g_return_if_fail (priv != NULL);
4772 old_slave_cmd = priv->slave_cmd;
4773 old_slave_busy = priv->slave_busy;
4774 if (old_slave_busy) {
4775 priv->slave_cmd = SLAVE_SHOULD_SLEEP;
4776 g_mutex_lock (priv->busy_lock);
4779 initialize_backend (cbdav, NULL);
4781 /* always wakeup thread, even when it was sleeping */
4782 g_cond_signal (priv->cond);
4784 if (old_slave_busy) {
4785 priv->slave_cmd = old_slave_cmd;
4786 g_mutex_unlock (priv->busy_lock);
4790 /* ************************************************************************* */
4791 /* ***************************** GObject Foo ******************************* */
4793 G_DEFINE_TYPE (ECalBackendCalDAV, e_cal_backend_caldav, E_TYPE_CAL_BACKEND_SYNC)
4796 e_cal_backend_caldav_dispose (GObject *object)
4798 ECalBackendCalDAV *cbdav;
4799 ECalBackendCalDAVPrivate *priv;
4802 cbdav = E_CAL_BACKEND_CALDAV (object);
4805 /* tell the slave to stop before acquiring a lock,
4806 * as it can work at the moment, and lock can be locked */
4807 priv->slave_cmd = SLAVE_SHOULD_DIE;
4809 g_mutex_lock (priv->busy_lock);
4811 if (priv->disposed) {
4812 g_mutex_unlock (priv->busy_lock);
4816 source = e_backend_get_source (E_BACKEND (cbdav));
4818 g_signal_handlers_disconnect_by_func (G_OBJECT (source), caldav_source_changed_cb, cbdav);
4820 /* stop the slave */
4821 if (priv->synch_slave) {
4822 g_cond_signal (priv->cond);
4824 /* wait until the slave died */
4825 g_cond_wait (priv->slave_gone_cond, priv->busy_lock);
4828 g_object_unref (priv->session);
4829 g_object_unref (priv->proxy);
4831 e_credentials_free (priv->credentials);
4832 priv->credentials = NULL;
4835 g_free (priv->schedule_outbox_url);
4837 if (priv->store != NULL) {
4838 g_object_unref (priv->store);
4841 priv->disposed = TRUE;
4842 g_mutex_unlock (priv->busy_lock);
4844 /* Chain up to parent's dispose() method. */
4845 G_OBJECT_CLASS (parent_class)->dispose (object);
4849 e_cal_backend_caldav_finalize (GObject *object)
4851 ECalBackendCalDAV *cbdav;
4852 ECalBackendCalDAVPrivate *priv;
4854 cbdav = E_CAL_BACKEND_CALDAV (object);
4857 g_mutex_free (priv->busy_lock);
4858 g_cond_free (priv->cond);
4859 g_cond_free (priv->slave_gone_cond);
4861 /* Chain up to parent's finalize() method. */
4862 G_OBJECT_CLASS (parent_class)->finalize (object);
4866 e_cal_backend_caldav_init (ECalBackendCalDAV *cbdav)
4868 cbdav->priv = G_TYPE_INSTANCE_GET_PRIVATE (
4869 cbdav, E_TYPE_CAL_BACKEND_CALDAV, ECalBackendCalDAVPrivate);
4871 cbdav->priv->session = soup_session_sync_new ();
4872 g_object_set (cbdav->priv->session, SOUP_SESSION_TIMEOUT, 90, NULL);
4874 cbdav->priv->proxy = e_proxy_new ();
4875 e_proxy_setup_proxy (cbdav->priv->proxy);
4876 g_signal_connect (cbdav->priv->proxy, "changed", G_CALLBACK (proxy_settings_changed), cbdav->priv);
4878 if (G_UNLIKELY (caldav_debug_show (DEBUG_MESSAGE)))
4879 caldav_debug_setup (cbdav->priv->session);
4881 cbdav->priv->disposed = FALSE;
4882 cbdav->priv->loaded = FALSE;
4883 cbdav->priv->opened = FALSE;
4885 /* Thinks the 'getctag' extension is available the first time, but unset it when realizes it isn't. */
4886 cbdav->priv->ctag_supported = TRUE;
4887 cbdav->priv->ctag_to_store = NULL;
4889 cbdav->priv->schedule_outbox_url = NULL;
4891 cbdav->priv->is_google = FALSE;
4893 cbdav->priv->busy_lock = g_mutex_new ();
4894 cbdav->priv->cond = g_cond_new ();
4895 cbdav->priv->slave_gone_cond = g_cond_new ();
4897 /* Slave control ... */
4898 cbdav->priv->slave_cmd = SLAVE_SHOULD_SLEEP;
4899 cbdav->priv->slave_busy = FALSE;
4900 cbdav->priv->refresh_time.tv_usec = 0;
4901 cbdav->priv->refresh_time.tv_sec = DEFAULT_REFRESH_TIME;
4903 g_signal_connect (cbdav->priv->session, "authenticate",
4904 G_CALLBACK (soup_authenticate), cbdav);
4906 e_cal_backend_sync_set_lock (E_CAL_BACKEND_SYNC (cbdav), FALSE);
4909 cbdav, "notify::online",
4910 G_CALLBACK (caldav_notify_online_cb), NULL);
4914 e_cal_backend_caldav_class_init (ECalBackendCalDAVClass *class)
4916 GObjectClass *object_class;
4917 ECalBackendClass *backend_class;
4918 ECalBackendSyncClass *sync_class;
4920 object_class = (GObjectClass *) class;
4921 backend_class = (ECalBackendClass *) class;
4922 sync_class = (ECalBackendSyncClass *) class;
4924 caldav_debug_init ();
4926 parent_class = (ECalBackendSyncClass *) g_type_class_peek_parent (class);
4927 g_type_class_add_private (class, sizeof (ECalBackendCalDAVPrivate));
4929 object_class->dispose = e_cal_backend_caldav_dispose;
4930 object_class->finalize = e_cal_backend_caldav_finalize;
4932 sync_class->get_backend_property_sync = caldav_get_backend_property;
4934 sync_class->open_sync = caldav_do_open;
4935 sync_class->authenticate_user_sync = caldav_authenticate_user;
4936 sync_class->refresh_sync = caldav_refresh;
4937 sync_class->remove_sync = caldav_remove;
4939 sync_class->create_object_sync = caldav_create_object;
4940 sync_class->modify_object_sync = caldav_modify_object;
4941 sync_class->remove_object_sync = caldav_remove_object;
4943 sync_class->receive_objects_sync = caldav_receive_objects;
4944 sync_class->send_objects_sync = caldav_send_objects;
4945 sync_class->get_object_sync = caldav_get_object;
4946 sync_class->get_object_list_sync = caldav_get_object_list;
4947 sync_class->add_timezone_sync = caldav_add_timezone;
4948 sync_class->get_free_busy_sync = caldav_get_free_busy;
4950 backend_class->start_view = caldav_start_view;
4952 backend_class->internal_get_timezone = caldav_internal_get_timezone;