2 * Evolution calendar - caldav backend
4 * This library is free software you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
8 * This library is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this library; if not, see <http://www.gnu.org/licenses/>.
18 * Christian Kellner <gicmo@gnome.org>
20 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
27 #include <glib/gstdio.h>
28 #include <glib/gi18n-lib.h>
30 /* LibXML2 includes */
31 #include <libxml/parser.h>
32 #include <libxml/tree.h>
33 #include <libxml/xpath.h>
34 #include <libxml/xpathInternals.h>
36 /* LibSoup includes */
37 #include <libsoup/soup.h>
39 #include "e-cal-backend-caldav.h"
43 #define E_CAL_BACKEND_CALDAV_GET_PRIVATE(obj) \
44 (G_TYPE_INSTANCE_GET_PRIVATE \
45 ((obj), E_TYPE_CAL_BACKEND_CALDAV, ECalBackendCalDAVPrivate))
47 #define CALDAV_CTAG_KEY "CALDAV_CTAG"
48 #define CALDAV_MAX_MULTIGET_AMOUNT 100 /* what's the maximum count of items to fetch within a multiget request */
49 #define LOCAL_PREFIX "file://"
52 #define DEFAULT_REFRESH_TIME 60
54 #define EDC_ERROR(_code) e_data_cal_create_error (_code, NULL)
55 #define EDC_ERROR_EX(_code, _msg) e_data_cal_create_error (_code, _msg)
61 SLAVE_SHOULD_WORK_NO_CTAG_CHECK,
66 /* Private part of the ECalBackendHttp structure */
67 struct _ECalBackendCalDAVPrivate {
69 /* The local disk cache */
70 ECalBackendStore *store;
72 /* should we sync for offline mode? */
75 /* TRUE after caldav_open */
77 /* TRUE when server reachable */
80 /* lock to indicate a busy state */
83 /* cond to synch threads */
86 /* cond to know the slave gone */
87 GCond slave_gone_cond;
90 const GThread *synch_slave; /* just for a reference, whether thread exists */
91 SlaveCommand slave_cmd;
92 gboolean slave_busy; /* whether is slave working */
94 /* The main soup session */
100 /* Authentication info */
102 gboolean auth_required;
103 gboolean force_ask_password;
105 /* support for 'getctag' extension */
106 gboolean ctag_supported;
107 gchar *ctag_to_store;
109 /* TRUE when 'calendar-schedule' supported on the server */
110 gboolean calendar_schedule;
111 /* with 'calendar-schedule' supported, here's an outbox url
112 * for queries of free/busy information */
113 gchar *schedule_outbox_url;
115 /* "Temporary hack" to indicate it's talking to a google calendar.
116 * The proper solution should be to subclass whole backend and change only
117 * necessary parts in it, but this will give us more freedom, as also direct
118 * caldav calendars can profit from this. */
121 /* set to true if thread for ESource::changed is invoked */
122 gboolean updating_source;
126 /* If we fail to obtain an OAuth2 access token,
127 * soup_authenticate_bearer() stashes an error
128 * here to be claimed in caldav_authenticate().
129 * This lets us propagate a more useful error
130 * message than a generic 401 description. */
131 GError *bearer_auth_error;
132 GMutex bearer_auth_error_lock;
135 /* Forward Declarations */
136 static void caldav_source_authenticator_init
137 (ESourceAuthenticatorInterface *iface);
139 G_DEFINE_TYPE_WITH_CODE (
141 e_cal_backend_caldav,
142 E_TYPE_CAL_BACKEND_SYNC,
143 G_IMPLEMENT_INTERFACE (
144 E_TYPE_SOURCE_AUTHENTICATOR,
145 caldav_source_authenticator_init))
147 /* ************************************************************************* */
150 #define DEBUG_MESSAGE "message"
151 #define DEBUG_MESSAGE_HEADER "message:header"
152 #define DEBUG_MESSAGE_BODY "message:body"
153 #define DEBUG_SERVER_ITEMS "items"
154 #define DEBUG_ATTACHMENTS "attachments"
156 static void convert_to_inline_attachment (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp);
157 static void convert_to_url_attachment (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp);
158 static void remove_cached_attachment (ECalBackendCalDAV *cbdav, const gchar *uid);
160 static gboolean caldav_debug_all = FALSE;
161 static GHashTable *caldav_debug_table = NULL;
164 add_debug_key (const gchar *start,
174 debug_key = debug_value = g_strndup (start, end - start);
176 debug_key = g_strchug (debug_key);
177 debug_key = g_strchomp (debug_key);
179 if (strlen (debug_key) == 0) {
180 g_free (debug_value);
184 g_hash_table_insert (
189 d (g_debug ("Adding %s to enabled debugging keys", debug_key));
193 caldav_debug_init_once (gpointer data)
197 dbg = g_getenv ("CALDAV_DEBUG");
202 d (g_debug ("Got debug env variable: [%s]", dbg));
204 caldav_debug_table = g_hash_table_new (
210 while (*ptr != '\0') {
211 if (*ptr == ',' || *ptr == ':') {
213 add_debug_key (dbg, ptr);
224 add_debug_key (dbg, ptr);
227 if (g_hash_table_lookup (caldav_debug_table, "all")) {
228 caldav_debug_all = TRUE;
229 g_hash_table_destroy (caldav_debug_table);
230 caldav_debug_table = NULL;
238 caldav_debug_init (void)
240 static GOnce debug_once = G_ONCE_INIT;
244 caldav_debug_init_once,
249 caldav_debug_show (const gchar *component)
251 if (G_UNLIKELY (caldav_debug_all)) {
253 } else if (G_UNLIKELY (caldav_debug_table != NULL) &&
254 g_hash_table_lookup (caldav_debug_table, component)) {
261 #define DEBUG_MAX_BODY_SIZE (100 * 1024 * 1024)
264 caldav_debug_setup (SoupSession *session)
267 SoupLoggerLogLevel level;
269 if (caldav_debug_show (DEBUG_MESSAGE_BODY))
270 level = SOUP_LOGGER_LOG_BODY;
271 else if (caldav_debug_show (DEBUG_MESSAGE_HEADER))
272 level = SOUP_LOGGER_LOG_HEADERS;
274 level = SOUP_LOGGER_LOG_MINIMAL;
276 logger = soup_logger_new (level, DEBUG_MAX_BODY_SIZE);
277 soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
278 g_object_unref (logger);
281 /* TODO Do not replicate this in every backend */
282 static icaltimezone *
283 resolve_tzid (const gchar *tzid,
286 ETimezoneCache *timezone_cache;
288 timezone_cache = E_TIMEZONE_CACHE (user_data);
290 return e_timezone_cache_get_timezone (timezone_cache, tzid);
294 put_component_to_store (ECalBackendCalDAV *cbdav,
297 time_t time_start, time_end;
299 e_cal_util_get_component_occur_times (
300 comp, &time_start, &time_end,
301 resolve_tzid, cbdav, icaltimezone_get_utc_timezone (),
302 e_cal_backend_get_kind (E_CAL_BACKEND (cbdav)));
304 return e_cal_backend_store_put_component_with_time_range (
305 cbdav->priv->store, comp, time_start, time_end);
308 static ECalBackendSyncClass *parent_class = NULL;
310 static void caldav_source_changed_cb (ESource *source, ECalBackendCalDAV *cbdav);
312 static gboolean remove_comp_from_cache (ECalBackendCalDAV *cbdav, const gchar *uid, const gchar *rid);
313 static gboolean put_comp_to_cache (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp, const gchar *href, const gchar *etag);
314 static void put_server_comp_to_cache (ECalBackendCalDAV *cbdav, icalcomponent *icomp, const gchar *href, const gchar *etag, GTree *c_uid2complist);
316 /* ************************************************************************* */
317 /* Misc. utility functions */
320 update_slave_cmd (ECalBackendCalDAVPrivate *priv,
321 SlaveCommand slave_cmd)
323 g_return_if_fail (priv != NULL);
325 if (priv->slave_cmd == SLAVE_SHOULD_DIE)
328 priv->slave_cmd = slave_cmd;
331 #define X_E_CALDAV "X-EVOLUTION-CALDAV-"
332 #define X_E_CALDAV_ATTACHMENT_NAME X_E_CALDAV "ATTACHMENT-NAME"
335 icomp_x_prop_set (icalcomponent *comp,
341 /* Find the old one first */
342 xprop = icalcomponent_get_first_property (comp, ICAL_X_PROPERTY);
345 const gchar *str = icalproperty_get_x_name (xprop);
347 if (!strcmp (str, key)) {
349 icalproperty_set_value_from_string (xprop, value, "NO");
351 icalcomponent_remove_property (comp, xprop);
352 icalproperty_free (xprop);
357 xprop = icalcomponent_get_next_property (comp, ICAL_X_PROPERTY);
360 if (!xprop && value) {
361 xprop = icalproperty_new_x (value);
362 icalproperty_set_x_name (xprop, key);
363 icalcomponent_add_property (comp, xprop);
368 icomp_x_prop_get (icalcomponent *comp,
373 /* Find the old one first */
374 xprop = icalcomponent_get_first_property (comp, ICAL_X_PROPERTY);
377 const gchar *str = icalproperty_get_x_name (xprop);
379 if (!strcmp (str, key)) {
383 xprop = icalcomponent_get_next_property (comp, ICAL_X_PROPERTY);
387 return icalproperty_get_value_as_string_r (xprop);
393 /* passing NULL as 'href' removes the property */
395 ecalcomp_set_href (ECalComponent *comp,
398 icalcomponent *icomp;
400 icomp = e_cal_component_get_icalcomponent (comp);
401 g_return_if_fail (icomp != NULL);
403 icomp_x_prop_set (icomp, X_E_CALDAV "HREF", href);
407 ecalcomp_get_href (ECalComponent *comp)
409 icalcomponent *icomp;
413 icomp = e_cal_component_get_icalcomponent (comp);
414 g_return_val_if_fail (icomp != NULL, NULL);
416 str = icomp_x_prop_get (icomp, X_E_CALDAV "HREF");
421 /* passing NULL as 'etag' removes the property */
423 ecalcomp_set_etag (ECalComponent *comp,
426 icalcomponent *icomp;
428 icomp = e_cal_component_get_icalcomponent (comp);
429 g_return_if_fail (icomp != NULL);
431 icomp_x_prop_set (icomp, X_E_CALDAV "ETAG", etag);
435 ecalcomp_get_etag (ECalComponent *comp)
437 icalcomponent *icomp;
441 icomp = e_cal_component_get_icalcomponent (comp);
442 g_return_val_if_fail (icomp != NULL, NULL);
444 str = icomp_x_prop_get (icomp, X_E_CALDAV "ETAG");
446 /* libical 0.48 escapes quotes, thus unescape them */
447 if (str && strchr (str, '\\')) {
450 for (ii = 0, jj = 0; str[ii]; ii++) {
451 if (str[ii] == '\\') {
469 / * object is in synch,
470 * now isnt that ironic? :) * /
471 ECALCOMP_IN_SYNCH = 0,
473 / * local changes * /
474 ECALCOMP_LOCALLY_CREATED,
475 ECALCOMP_LOCALLY_DELETED,
476 ECALCOMP_LOCALLY_MODIFIED
480 / * oos = out of synch * /
482 ecalcomp_set_synch_state (ECalComponent *comp,
483 * ECalCompSyncState state)
485 icalcomponent *icomp;
488 icomp = e_cal_component_get_icalcomponent (comp);
490 state_string = g_strdup_printf ("%d", state);
492 icomp_x_prop_set (icomp, X_E_CALDAV "ETAG", state_string);
494 g_free (state_string);
498 ecalcomp_gen_href (ECalComponent *comp)
500 gchar *href, *uid, *tmp;
501 icalcomponent *icomp;
503 icomp = e_cal_component_get_icalcomponent (comp);
504 g_return_val_if_fail (icomp != NULL, NULL);
506 uid = g_strdup (icalcomponent_get_uid (icomp));
509 uid = e_cal_component_gen_uid ();
511 tmp = uid ? strchr (uid, '@') : NULL;
517 tmp = isodate_from_time_t (time (NULL));
519 /* quite long, but ensures uniqueness quite well, without using UUIDs */
520 href = g_strconcat (uid ? uid : "no-uid", tmp ? "-" : "", tmp ? tmp : "", ".ics", NULL);
525 icomp_x_prop_set (icomp, X_E_CALDAV "HREF", href);
527 return g_strdelimit (href, " /'\"`&();|<>$%{}!\\:*?#@", '_');
530 /* ensure etag is quoted (to workaround potential server bugs) */
532 quote_etag (const gchar *etag)
536 if (etag && (strlen (etag) < 2 || etag[strlen (etag) - 1] != '\"')) {
537 ret = g_strdup_printf ("\"%s\"", etag);
539 ret = g_strdup (etag);
545 /* ************************************************************************* */
548 status_code_to_result (SoupMessage *message,
549 ECalBackendCalDAV *cbdav,
553 ECalBackendCalDAVPrivate *priv;
555 g_return_val_if_fail (cbdav != NULL, FALSE);
556 g_return_val_if_fail (message != NULL, FALSE);
560 if (SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
564 if (perror && *perror)
567 switch (message->status_code) {
568 case SOUP_STATUS_CANT_CONNECT:
569 case SOUP_STATUS_CANT_CONNECT_PROXY:
572 e_data_cal_create_error_fmt (
574 _("Server is unreachable (%s)"),
575 message->reason_phrase && *message->reason_phrase ? message->reason_phrase :
576 (soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : _("Unknown error"))));
578 priv->opened = FALSE;
579 e_cal_backend_set_writable (
580 E_CAL_BACKEND (cbdav), FALSE);
583 case SOUP_STATUS_NOT_FOUND:
585 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
587 g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
590 case SOUP_STATUS_FORBIDDEN:
591 g_propagate_error (perror, EDC_ERROR (AuthenticationRequired));
594 case SOUP_STATUS_UNAUTHORIZED:
595 if (priv && priv->auth_required)
596 g_propagate_error (perror, EDC_ERROR (AuthenticationFailed));
598 g_propagate_error (perror, EDC_ERROR (AuthenticationRequired));
601 case SOUP_STATUS_SSL_FAILED:
604 e_data_cal_create_error_fmt ( OtherError,
605 _("Failed to connect to a server using SSL: %s"),
606 message->reason_phrase && *message->reason_phrase ? message->reason_phrase :
607 (soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : _("Unknown error"))));
611 d (g_debug ("CalDAV:%s: Unhandled status code %d\n", G_STRFUNC, status_code));
614 e_data_cal_create_error_fmt (
616 _("Unexpected HTTP status code %d returned (%s)"),
617 message->status_code,
618 message->reason_phrase && *message->reason_phrase ? message->reason_phrase :
619 (soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : _("Unknown error"))));
626 /* !TS, call with lock held */
628 check_state (ECalBackendCalDAV *cbdav,
634 if (!cbdav->priv->loaded) {
635 g_propagate_error (perror, EDC_ERROR_EX (OtherError, _("CalDAV backend is not loaded yet")));
639 if (!e_backend_get_online (E_BACKEND (cbdav))) {
641 if (!cbdav->priv->do_offline) {
642 g_propagate_error (perror, EDC_ERROR (RepositoryOffline));
653 /* ************************************************************************* */
654 /* XML Parsing code */
656 static xmlXPathObjectPtr
657 xpath_eval (xmlXPathContextPtr ctx,
661 xmlXPathObjectPtr result;
669 va_start (args, format);
670 expr = g_strdup_vprintf (format, args);
673 result = xmlXPathEvalExpression ((xmlChar *) expr, ctx);
676 if (result == NULL) {
680 if (result->type == XPATH_NODESET &&
681 xmlXPathNodeSetIsEmpty (result->nodesetval)) {
682 xmlXPathFreeObject (result);
691 parse_status_node (xmlNodePtr node,
697 content = xmlNodeGetContent (node);
699 res = soup_headers_parse_status_line (
711 xp_object_get_string (xmlXPathObjectPtr result)
718 if (result->type == XPATH_STRING) {
719 ret = g_strdup ((gchar *) result->stringval);
722 xmlXPathFreeObject (result);
726 /* like get_string but will quote the etag if necessary */
728 xp_object_get_etag (xmlXPathObjectPtr result)
736 if (result->type == XPATH_STRING) {
737 str = (gchar *) result->stringval;
739 ret = quote_etag (str);
742 xmlXPathFreeObject (result);
747 xp_object_get_status (xmlXPathObjectPtr result)
755 if (result->type == XPATH_STRING) {
756 res = soup_headers_parse_status_line (
757 (gchar *) result->stringval,
767 xmlXPathFreeObject (result);
773 xp_object_get_number (xmlXPathObjectPtr result)
780 if (result->type == XPATH_STRING) {
781 ret = result->boolval;
784 xmlXPathFreeObject (result);
789 /*** *** *** *** *** *** */
790 #define XPATH_HREF "string(/D:multistatus/D:response[%d]/D:href)"
791 #define XPATH_STATUS "string(/D:multistatus/D:response[%d]/D:propstat/D:status)"
792 #define XPATH_GETETAG_STATUS "string(/D:multistatus/D:response[%d]/D:propstat/D:prop/D:getetag/../../D:status)"
793 #define XPATH_GETETAG "string(/D:multistatus/D:response[%d]/D:propstat/D:prop/D:getetag)"
794 #define XPATH_CALENDAR_DATA "string(/D:multistatus/D:response[%d]/D:propstat/D:prop/C:calendar-data)"
795 #define XPATH_GETCTAG_STATUS "string(/D:multistatus/D:response/D:propstat/D:prop/CS:getctag/../../D:status)"
796 #define XPATH_GETCTAG "string(/D:multistatus/D:response/D:propstat/D:prop/CS:getctag)"
797 #define XPATH_OWNER_STATUS "string(/D:multistatus/D:response/D:propstat/D:prop/D:owner/D:href/../../../D:status)"
798 #define XPATH_OWNER "string(/D:multistatus/D:response/D:propstat/D:prop/D:owner/D:href)"
799 #define XPATH_SCHEDULE_OUTBOX_URL_STATUS "string(/D:multistatus/D:response/D:propstat/D:prop/C:schedule-outbox-URL/D:href/../../../D:status)"
800 #define XPATH_SCHEDULE_OUTBOX_URL "string(/D:multistatus/D:response/D:propstat/D:prop/C:schedule-outbox-URL/D:href)"
802 typedef struct _CalDAVObject CalDAVObject;
804 struct _CalDAVObject {
815 caldav_object_free (CalDAVObject *object,
816 gboolean free_object_itself)
818 g_free (object->href);
819 g_free (object->etag);
820 g_free (object->cdata);
822 if (free_object_itself) {
828 parse_report_response (SoupMessage *soup_message,
832 xmlXPathContextPtr xpctx;
833 xmlXPathObjectPtr result;
838 g_return_val_if_fail (soup_message != NULL, FALSE);
839 g_return_val_if_fail (objs != NULL || len != NULL, FALSE);
842 doc = xmlReadMemory (
843 soup_message->response_body->data,
844 soup_message->response_body->length,
853 xpctx = xmlXPathNewContext (doc);
856 xpctx, (xmlChar *) "D",
860 xpctx, (xmlChar *) "C",
861 (xmlChar *) "urn:ietf:params:xml:ns:caldav");
863 result = xpath_eval (xpctx, "/D:multistatus/D:response");
865 if (result == NULL || result->type != XPATH_NODESET) {
871 n = xmlXPathNodeSetGetLength (result->nodesetval);
874 *objs = g_new0 (CalDAVObject, n);
876 for (i = 0; i < n; i++) {
877 CalDAVObject *object;
878 xmlXPathObjectPtr xpres;
881 /* see if we got a status child in the response element */
883 xpres = xpath_eval (xpctx, XPATH_HREF, i + 1);
884 /* use full path from a href, to let calendar-multiget work properly */
885 object->href = xp_object_get_string (xpres);
887 xpres = xpath_eval (xpctx,XPATH_STATUS , i + 1);
888 object->status = xp_object_get_status (xpres);
890 if (object->status && object->status != 200) {
894 xpres = xpath_eval (xpctx, XPATH_GETETAG_STATUS, i + 1);
895 object->status = xp_object_get_status (xpres);
897 if (object->status != 200) {
901 xpres = xpath_eval (xpctx, XPATH_GETETAG, i + 1);
902 object->etag = xp_object_get_etag (xpres);
904 xpres = xpath_eval (xpctx, XPATH_CALENDAR_DATA, i + 1);
905 object->cdata = xp_object_get_string (xpres);
910 xmlXPathFreeObject (result);
911 xmlXPathFreeContext (xpctx);
916 /* returns whether was able to read the xpath_value from the server's response; *value contains the result */
918 parse_propfind_response (SoupMessage *message,
919 const gchar *xpath_status,
920 const gchar *xpath_value,
923 xmlXPathContextPtr xpctx;
925 gboolean res = FALSE;
927 g_return_val_if_fail (message != NULL, FALSE);
928 g_return_val_if_fail (value != NULL, FALSE);
930 doc = xmlReadMemory (
931 message->response_body->data,
932 message->response_body->length,
941 xpctx = xmlXPathNewContext (doc);
942 xmlXPathRegisterNs (xpctx, (xmlChar *) "D", (xmlChar *) "DAV:");
943 xmlXPathRegisterNs (xpctx, (xmlChar *) "C", (xmlChar *) "urn:ietf:params:xml:ns:caldav");
944 xmlXPathRegisterNs (xpctx, (xmlChar *) "CS", (xmlChar *) "http://calendarserver.org/ns/");
946 if (xpath_status == NULL || xp_object_get_status (xpath_eval (xpctx, xpath_status)) == 200) {
947 gchar *txt = xp_object_get_string (xpath_eval (xpctx, xpath_value));
950 gint len = strlen (txt);
952 if (*txt == '\"' && len > 2 && txt[len - 1] == '\"') {
954 *value = g_strndup (txt + 1, len - 2);
960 res = (*value) != NULL;
966 xmlXPathFreeContext (xpctx);
972 /* ************************************************************************* */
973 /* Authentication helpers for libsoup */
976 soup_authenticate_bearer (SoupSession *session,
977 SoupMessage *message,
979 ECalBackendCalDAV *cbdav)
982 gchar *access_token = NULL;
983 gint expires_in_seconds = -1;
984 GError *local_error = NULL;
986 source = e_backend_get_source (E_BACKEND (cbdav));
988 e_source_get_oauth2_access_token_sync (
989 source, NULL, &access_token,
990 &expires_in_seconds, &local_error);
992 e_soup_auth_bearer_set_access_token (
993 E_SOUP_AUTH_BEARER (auth),
994 access_token, expires_in_seconds);
996 /* Stash the error to be picked up by caldav_authenticate().
997 * There's no way to explicitly propagate a GError directly
998 * through libsoup, so we have to work around it. */
999 if (local_error != NULL) {
1000 g_mutex_lock (&cbdav->priv->bearer_auth_error_lock);
1002 /* Warn about an unclaimed error before we clear it.
1003 * This is just to verify the errors we set here are
1004 * actually making it back to the user. */
1005 g_warn_if_fail (cbdav->priv->bearer_auth_error == NULL);
1006 g_clear_error (&cbdav->priv->bearer_auth_error);
1009 &cbdav->priv->bearer_auth_error, local_error);
1011 g_mutex_unlock (&cbdav->priv->bearer_auth_error_lock);
1014 g_free (access_token);
1018 soup_authenticate (SoupSession *session,
1024 ECalBackendCalDAV *cbdav;
1025 ESourceAuthentication *auth_extension;
1027 const gchar *extension_name;
1029 cbdav = E_CAL_BACKEND_CALDAV (data);
1031 source = e_backend_get_source (E_BACKEND (data));
1032 extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
1033 auth_extension = e_source_get_extension (source, extension_name);
1035 if (retrying || cbdav->priv->force_ask_password) {
1036 cbdav->priv->force_ask_password = TRUE;
1040 if (E_IS_SOUP_AUTH_BEARER (auth)) {
1041 soup_authenticate_bearer (session, msg, auth, cbdav);
1043 /* do not send same password twice, but keep it for later use */
1044 } else if (cbdav->priv->password != NULL) {
1047 user = e_source_authentication_dup_user (auth_extension);
1048 if (!user || !*user)
1049 soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
1051 soup_auth_authenticate (auth, user, cbdav->priv->password);
1056 /* ************************************************************************* */
1057 /* direct CalDAV server access functions */
1060 redirect_handler (SoupMessage *msg,
1063 if (SOUP_STATUS_IS_REDIRECTION (msg->status_code)) {
1064 SoupSession *soup_session = user_data;
1066 const gchar *new_loc;
1068 new_loc = soup_message_headers_get_list (msg->response_headers, "Location");
1072 new_uri = soup_uri_new_with_base (soup_message_get_uri (msg), new_loc);
1074 soup_message_set_status_full (
1076 SOUP_STATUS_MALFORMED,
1077 _("Invalid Redirect URL"));
1081 if (new_uri->host && g_str_has_suffix (new_uri->host, "yahoo.com")) {
1082 /* yahoo! returns port 7070, which is unreachable;
1083 * it also requires https being used (below call resets port as well) */
1084 soup_uri_set_scheme (new_uri, SOUP_URI_SCHEME_HTTPS);
1087 soup_message_set_uri (msg, new_uri);
1088 soup_session_requeue_message (soup_session, msg);
1090 soup_uri_free (new_uri);
1095 send_and_handle_redirection (ECalBackendCalDAV *cbdav,
1097 gchar **new_location,
1098 GCancellable *cancellable,
1101 gchar *old_uri = NULL;
1103 g_return_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav));
1104 g_return_if_fail (msg != NULL);
1107 old_uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
1109 soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT);
1110 soup_message_add_header_handler (msg, "got_body", "Location", G_CALLBACK (redirect_handler), cbdav->priv->session);
1111 soup_message_headers_append (msg->request_headers, "Connection", "close");
1112 soup_session_send_message (cbdav->priv->session, msg);
1114 if (msg->status_code == SOUP_STATUS_SSL_FAILED) {
1116 ESourceWebdav *extension;
1117 ESourceRegistry *registry;
1119 ETrustPromptResponse response;
1120 ENamedParameters *parameters;
1122 backend = E_BACKEND (cbdav);
1123 source = e_backend_get_source (backend);
1124 registry = e_cal_backend_get_registry (E_CAL_BACKEND (backend));
1125 extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
1127 parameters = e_named_parameters_new ();
1129 response = e_source_webdav_prepare_ssl_trust_prompt (extension, msg, registry, parameters);
1130 if (response == E_TRUST_PROMPT_RESPONSE_UNKNOWN) {
1131 response = e_backend_trust_prompt_sync (backend, parameters, cancellable, error);
1132 if (response != E_TRUST_PROMPT_RESPONSE_UNKNOWN)
1133 e_source_webdav_store_ssl_trust_prompt (extension, msg, response);
1136 e_named_parameters_free (parameters);
1138 if (response == E_TRUST_PROMPT_RESPONSE_ACCEPT ||
1139 response == E_TRUST_PROMPT_RESPONSE_ACCEPT_TEMPORARILY) {
1140 g_object_set (cbdav->priv->session, SOUP_SESSION_SSL_STRICT, FALSE, NULL);
1141 soup_session_send_message (cbdav->priv->session, msg);
1146 gchar *new_loc = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
1148 if (new_loc && old_uri && !g_str_equal (new_loc, old_uri))
1149 *new_location = new_loc;
1158 caldav_generate_uri (ECalBackendCalDAV *cbdav,
1159 const gchar *target)
1164 slash = strrchr (target, '/');
1168 /* uri *have* trailing slash already */
1169 uri = g_strconcat (cbdav->priv->uri, target, NULL);
1175 caldav_server_open_calendar (ECalBackendCalDAV *cbdav,
1176 gboolean *server_unreachable,
1177 GCancellable *cancellable,
1180 SoupMessage *message;
1181 const gchar *header;
1182 gboolean calendar_access;
1183 gboolean put_allowed;
1184 gboolean delete_allowed;
1186 ESourceWebdav *webdav_extension;
1188 g_return_val_if_fail (cbdav != NULL, FALSE);
1189 g_return_val_if_fail (server_unreachable != NULL, FALSE);
1191 message = soup_message_new (SOUP_METHOD_OPTIONS, cbdav->priv->uri);
1192 if (message == NULL) {
1193 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
1196 soup_message_headers_append (
1197 message->request_headers,
1198 "User-Agent", "Evolution/" VERSION);
1200 /* re-check server's certificate trust, if needed */
1201 g_object_set (cbdav->priv->session, SOUP_SESSION_SSL_STRICT, TRUE, NULL);
1203 source = e_backend_get_source (E_BACKEND (cbdav));
1204 webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
1205 e_source_webdav_unset_temporary_ssl_trust (webdav_extension);
1207 send_and_handle_redirection (cbdav, message, NULL, cancellable, perror);
1209 if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1210 switch (message->status_code) {
1211 case SOUP_STATUS_CANT_CONNECT:
1212 case SOUP_STATUS_CANT_CONNECT_PROXY:
1213 *server_unreachable = TRUE;
1217 status_code_to_result (message, cbdav, TRUE, perror);
1219 g_object_unref (message);
1223 /* parse the dav header, we are intreseted in the
1224 * calendar-access bit only at the moment */
1225 header = soup_message_headers_get_list (message->response_headers, "DAV");
1227 calendar_access = soup_header_contains (header, "calendar-access");
1228 cbdav->priv->calendar_schedule = soup_header_contains (header, "calendar-schedule");
1230 calendar_access = FALSE;
1231 cbdav->priv->calendar_schedule = FALSE;
1234 /* parse the Allow header and look for PUT, DELETE at the
1235 * moment (maybe we should check more here, for REPORT eg) */
1236 header = soup_message_headers_get_list (message->response_headers, "Allow");
1238 put_allowed = soup_header_contains (header, "PUT");
1239 delete_allowed = soup_header_contains (header, "DELETE");
1241 put_allowed = delete_allowed = FALSE;
1243 g_object_unref (message);
1245 if (calendar_access) {
1246 e_cal_backend_set_writable (
1247 E_CAL_BACKEND (cbdav),
1248 put_allowed && delete_allowed);
1252 g_propagate_error (perror, EDC_ERROR (PermissionDenied));
1257 caldav_unref_thread (gpointer cbdav)
1259 g_object_unref (cbdav);
1265 caldav_unref_in_thread (ECalBackendCalDAV *cbdav)
1269 g_return_if_fail (cbdav != NULL);
1271 thread = g_thread_new (NULL, caldav_unref_thread, cbdav);
1272 g_thread_unref (thread);
1276 caldav_authenticate (ECalBackendCalDAV *cbdav,
1278 GCancellable *cancellable,
1281 gboolean success = TRUE;
1284 g_object_ref (cbdav);
1286 /* This function is called when we receive a 4xx response code for
1287 * authentication failures. If we're using Bearer authentication,
1288 * there should be a GError available. Return the GError to avoid
1289 * inappropriately prompting for a password. */
1290 g_mutex_lock (&cbdav->priv->bearer_auth_error_lock);
1291 if (cbdav->priv->bearer_auth_error != NULL) {
1292 g_propagate_error (error, cbdav->priv->bearer_auth_error);
1293 cbdav->priv->bearer_auth_error = NULL;
1296 g_mutex_unlock (&cbdav->priv->bearer_auth_error_lock);
1299 success = e_backend_authenticate_sync (
1301 E_SOURCE_AUTHENTICATOR (cbdav),
1302 cancellable, error);
1306 caldav_unref_in_thread (cbdav);
1311 static gconstpointer
1312 compat_libxml_output_buffer_get_content (xmlOutputBufferPtr buf,
1315 #ifdef LIBXML2_NEW_BUFFER
1316 *out_len = xmlOutputBufferGetSize (buf);
1317 return xmlOutputBufferGetContent (buf);
1319 *out_len = buf->buffer->use;
1320 return buf->buffer->content;
1324 /* Returns whether calendar changed on the server. This works only when server
1325 * supports 'getctag' extension. */
1327 check_calendar_changed_on_server (ECalBackendCalDAV *cbdav,
1330 xmlOutputBufferPtr buf;
1331 SoupMessage *message;
1333 xmlNodePtr root, node;
1335 gconstpointer buf_content;
1337 gboolean result = TRUE;
1339 g_return_val_if_fail (cbdav != NULL, TRUE);
1341 /* no support for 'getctag', thus update cache */
1342 if (!cbdav->priv->ctag_supported)
1345 /* Prepare the soup message */
1346 message = soup_message_new ("PROPFIND", cbdav->priv->uri);
1347 if (message == NULL)
1350 doc = xmlNewDoc ((xmlChar *) "1.0");
1351 root = xmlNewDocNode (doc, NULL, (xmlChar *) "propfind", NULL);
1352 xmlDocSetRootElement (doc, root);
1353 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", NULL);
1354 ns = xmlNewNs (root, (xmlChar *) "http://calendarserver.org/ns/", (xmlChar *) "CS");
1356 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1357 node = xmlNewTextChild (node, nsdav, (xmlChar *) "getctag", NULL);
1358 xmlSetNs (node, ns);
1360 buf = xmlAllocOutputBuffer (NULL);
1361 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1362 xmlOutputBufferFlush (buf);
1364 soup_message_headers_append (
1365 message->request_headers,
1366 "User-Agent", "Evolution/" VERSION);
1367 soup_message_headers_append (
1368 message->request_headers,
1371 buf_content = compat_libxml_output_buffer_get_content (buf, &buf_size);
1372 soup_message_set_request (
1376 buf_content, buf_size);
1378 /* Send the request now */
1379 send_and_handle_redirection (cbdav, message, NULL, NULL, NULL);
1381 /* Clean up the memory */
1382 xmlOutputBufferClose (buf);
1385 /* Check the result */
1386 if (message->status_code == 401) {
1387 caldav_authenticate (cbdav, TRUE, NULL, NULL);
1388 } else if (message->status_code != 207) {
1389 /* does not support it, but report calendar changed to update cache */
1390 cbdav->priv->ctag_supported = FALSE;
1394 if (parse_propfind_response (message, XPATH_GETCTAG_STATUS, XPATH_GETCTAG, &ctag)) {
1395 const gchar *my_ctag;
1397 my_ctag = e_cal_backend_store_get_key_value (
1398 cbdav->priv->store, CALDAV_CTAG_KEY);
1400 if (ctag && my_ctag && g_str_equal (ctag, my_ctag)) {
1401 /* ctag is same, no change in the calendar */
1403 } else if (save_ctag) {
1404 /* do not store ctag now, do it rather after complete sync */
1405 g_free (cbdav->priv->ctag_to_store);
1406 cbdav->priv->ctag_to_store = ctag;
1412 cbdav->priv->ctag_supported = FALSE;
1416 g_object_unref (message);
1421 /* only_hrefs is a list of requested objects to fetch; it has precedence from
1422 * start_time/end_time, which are used only when both positive.
1423 * Times are supposed to be in UTC, if set.
1426 caldav_server_list_objects (ECalBackendCalDAV *cbdav,
1427 CalDAVObject **objs,
1433 xmlOutputBufferPtr buf;
1434 SoupMessage *message;
1441 gconstpointer buf_content;
1445 /* Allocate the soup message */
1446 message = soup_message_new ("REPORT", cbdav->priv->uri);
1447 if (message == NULL)
1450 /* Maybe we should just do a g_strdup_printf here? */
1451 /* Prepare request body */
1452 doc = xmlNewDoc ((xmlChar *) "1.0");
1454 root = xmlNewDocNode (doc, NULL, (xmlChar *) "calendar-query", NULL);
1456 root = xmlNewDocNode (doc, NULL, (xmlChar *) "calendar-multiget", NULL);
1457 nscd = xmlNewNs (root, (xmlChar *) "urn:ietf:params:xml:ns:caldav", (xmlChar *) "C");
1458 xmlSetNs (root, nscd);
1459 xmlDocSetRootElement (doc, root);
1461 /* Add webdav tags */
1462 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", (xmlChar *) "D");
1463 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1464 xmlNewTextChild (node, nsdav, (xmlChar *) "getetag", NULL);
1468 xmlNewTextChild (node, nscd, (xmlChar *) "calendar-data", NULL);
1469 for (l = only_hrefs; l; l = l->next) {
1471 xmlNewTextChild (root, nsdav, (xmlChar *) "href", (xmlChar *) l->data);
1475 node = xmlNewTextChild (root, nscd, (xmlChar *) "filter", NULL);
1476 node = xmlNewTextChild (node, nscd, (xmlChar *) "comp-filter", NULL);
1477 xmlSetProp (node, (xmlChar *) "name", (xmlChar *) "VCALENDAR");
1479 sn = xmlNewTextChild (node, nscd, (xmlChar *) "comp-filter", NULL);
1480 switch (e_cal_backend_get_kind (E_CAL_BACKEND (cbdav))) {
1482 case ICAL_VEVENT_COMPONENT:
1483 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VEVENT");
1485 case ICAL_VJOURNAL_COMPONENT:
1486 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VJOURNAL");
1488 case ICAL_VTODO_COMPONENT:
1489 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VTODO");
1493 if (start_time > 0 || end_time > 0) {
1496 sn = xmlNewTextChild (sn, nscd, (xmlChar *) "time-range", NULL);
1498 if (start_time > 0) {
1499 tmp = isodate_from_time_t (start_time);
1500 xmlSetProp (sn, (xmlChar *) "start", (xmlChar *) tmp);
1505 tmp = isodate_from_time_t (end_time);
1506 xmlSetProp (sn, (xmlChar *) "end", (xmlChar *) tmp);
1512 buf = xmlAllocOutputBuffer (NULL);
1513 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1514 xmlOutputBufferFlush (buf);
1516 /* Prepare the soup message */
1517 soup_message_headers_append (
1518 message->request_headers,
1519 "User-Agent", "Evolution/" VERSION);
1520 soup_message_headers_append (
1521 message->request_headers,
1524 buf_content = compat_libxml_output_buffer_get_content (buf, &buf_size);
1525 soup_message_set_request (
1529 buf_content, buf_size);
1531 /* Send the request now */
1532 send_and_handle_redirection (cbdav, message, NULL, NULL, NULL);
1534 /* Clean up the memory */
1535 xmlOutputBufferClose (buf);
1538 /* Check the result */
1539 if (message->status_code != 207) {
1540 switch (message->status_code) {
1541 case SOUP_STATUS_CANT_CONNECT:
1542 case SOUP_STATUS_CANT_CONNECT_PROXY:
1543 cbdav->priv->opened = FALSE;
1544 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_SLEEP);
1545 e_cal_backend_set_writable (
1546 E_CAL_BACKEND (cbdav), FALSE);
1549 caldav_authenticate (cbdav, TRUE, NULL, NULL);
1552 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");
1556 g_object_unref (message);
1560 /* Parse the response body */
1561 result = parse_report_response (message, objs, len);
1563 g_object_unref (message);
1568 caldav_server_query_for_uid (ECalBackendCalDAV *cbdav,
1570 GCancellable *cancellable,
1573 SoupMessage *message;
1574 xmlOutputBufferPtr buf;
1581 gconstpointer buf_content;
1583 gboolean result = FALSE;
1585 CalDAVObject *objs = NULL, *object;
1587 g_return_val_if_fail (cbdav != NULL, FALSE);
1588 g_return_val_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav), FALSE);
1589 g_return_val_if_fail (uid && *uid, FALSE);
1591 /* Allocate the soup message */
1592 message = soup_message_new ("REPORT", cbdav->priv->uri);
1593 if (message == NULL)
1596 /* Maybe we should just do a g_strdup_printf here? */
1597 /* Prepare request body */
1598 doc = xmlNewDoc ((xmlChar *) "1.0");
1599 root = xmlNewDocNode (doc, NULL, (xmlChar *) "calendar-query", NULL);
1600 nscd = xmlNewNs (root, (xmlChar *) "urn:ietf:params:xml:ns:caldav", (xmlChar *) "C");
1601 xmlSetNs (root, nscd);
1602 xmlDocSetRootElement (doc, root);
1604 /* Add webdav tags */
1605 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", (xmlChar *) "D");
1606 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1607 xmlNewTextChild (node, nsdav, (xmlChar *) "getetag", NULL);
1608 xmlNewTextChild (node, nscd, (xmlChar *) "calendar-data", NULL);
1610 node = xmlNewTextChild (root, nscd, (xmlChar *) "filter", NULL);
1611 node = xmlNewTextChild (node, nscd, (xmlChar *) "comp-filter", NULL);
1612 xmlSetProp (node, (xmlChar *) "name", (xmlChar *) "VCALENDAR");
1614 sn = xmlNewTextChild (node, nscd, (xmlChar *) "comp-filter", NULL);
1615 switch (e_cal_backend_get_kind (E_CAL_BACKEND (cbdav))) {
1617 case ICAL_VEVENT_COMPONENT:
1618 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VEVENT");
1620 case ICAL_VJOURNAL_COMPONENT:
1621 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VJOURNAL");
1623 case ICAL_VTODO_COMPONENT:
1624 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VTODO");
1628 node = xmlNewTextChild (sn, nscd, (xmlChar *) "prop-filter", NULL);
1629 xmlSetProp (node, (xmlChar *) "name", (xmlChar *) "UID");
1631 sn = xmlNewTextChild (node, nscd, (xmlChar *) "text-match", NULL);
1632 xmlSetProp (sn, (xmlChar *) "collation", (xmlChar *) "i;octet");
1633 xmlNodeSetContent (sn, (xmlChar *) uid);
1635 buf = xmlAllocOutputBuffer (NULL);
1636 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1637 xmlOutputBufferFlush (buf);
1639 /* Prepare the soup message */
1640 soup_message_headers_append (
1641 message->request_headers,
1642 "User-Agent", "Evolution/" VERSION);
1643 soup_message_headers_append (
1644 message->request_headers,
1647 buf_content = compat_libxml_output_buffer_get_content (buf, &buf_size);
1648 soup_message_set_request (
1652 buf_content, buf_size);
1654 /* Send the request now */
1655 send_and_handle_redirection (cbdav, message, NULL, cancellable, error);
1657 /* Clean up the memory */
1658 xmlOutputBufferClose (buf);
1661 /* Check the result */
1662 if (message->status_code != 207) {
1663 switch (message->status_code) {
1664 case SOUP_STATUS_CANT_CONNECT:
1665 case SOUP_STATUS_CANT_CONNECT_PROXY:
1666 cbdav->priv->opened = FALSE;
1667 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_SLEEP);
1668 e_cal_backend_set_writable (
1669 E_CAL_BACKEND (cbdav), FALSE);
1672 caldav_authenticate (cbdav, TRUE, NULL, NULL);
1675 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");
1679 g_object_unref (message);
1683 /* Parse the response body */
1684 if (parse_report_response (message, &objs, &len)) {
1687 for (ii = 0, object = objs; ii < len; ii++, object++) {
1688 if (object->status == 200 && object->href && object->etag && object->cdata && *object->cdata) {
1689 icalcomponent *icomp = icalparser_parse_string (object->cdata);
1692 put_server_comp_to_cache (cbdav, icomp, object->href, object->etag, NULL);
1693 icalcomponent_free (icomp);
1697 /* these free immediately */
1698 caldav_object_free (object, FALSE);
1701 /* cache update done for fetched items */
1705 g_object_unref (message);
1711 caldav_server_download_attachment (ECalBackendCalDAV *cbdav,
1712 const gchar *attachment_uri,
1717 SoupMessage *message;
1719 g_return_val_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav), FALSE);
1720 g_return_val_if_fail (attachment_uri != NULL, FALSE);
1721 g_return_val_if_fail (content != NULL, FALSE);
1722 g_return_val_if_fail (len != NULL, FALSE);
1724 message = soup_message_new (SOUP_METHOD_GET, attachment_uri);
1725 if (message == NULL) {
1726 g_propagate_error (error, EDC_ERROR (InvalidObject));
1730 soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1731 send_and_handle_redirection (cbdav, message, NULL, NULL, NULL);
1733 if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1734 status_code_to_result (message, cbdav, FALSE, error);
1736 if (message->status_code == 401)
1737 caldav_authenticate (cbdav, FALSE, NULL, NULL);
1739 g_object_unref (message);
1743 *len = message->response_body->length;
1744 *content = g_memdup (message->response_body->data, *len);
1746 g_object_unref (message);
1752 caldav_server_get_object (ECalBackendCalDAV *cbdav,
1753 CalDAVObject *object,
1754 GCancellable *cancellable,
1757 SoupMessage *message;
1761 g_assert (object != NULL && object->href != NULL);
1763 uri = caldav_generate_uri (cbdav, object->href);
1764 message = soup_message_new (SOUP_METHOD_GET, uri);
1765 if (message == NULL) {
1767 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
1771 soup_message_headers_append (
1772 message->request_headers,
1773 "User-Agent", "Evolution/" VERSION);
1775 send_and_handle_redirection (cbdav, message, NULL, cancellable, perror);
1777 if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1778 status_code_to_result (message, cbdav, FALSE, perror);
1780 if (message->status_code == 401)
1781 caldav_authenticate (cbdav, FALSE, NULL, NULL);
1783 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");
1784 g_object_unref (message);
1789 hdr = soup_message_headers_get_list (message->response_headers, "Content-Type");
1791 if (hdr == NULL || g_ascii_strncasecmp (hdr, "text/calendar", 13)) {
1792 g_propagate_error (perror, EDC_ERROR (InvalidObject));
1793 g_object_unref (message);
1794 g_warning ("Object to fetch '%s' not of type text/calendar", uri);
1799 hdr = soup_message_headers_get_list (message->response_headers, "ETag");
1802 g_free (object->etag);
1803 object->etag = quote_etag (hdr);
1804 } else if (!object->etag) {
1805 g_warning ("UUHH no ETag, now that's bad! (at '%s')", uri);
1809 g_free (object->cdata);
1810 object->cdata = g_strdup (message->response_body->data);
1812 g_object_unref (message);
1818 caldav_post_freebusy (ECalBackendCalDAV *cbdav,
1821 GCancellable *cancellable,
1824 SoupMessage *message;
1826 message = soup_message_new (SOUP_METHOD_POST, url);
1827 if (message == NULL) {
1828 g_propagate_error (error, EDC_ERROR (NoSuchCal));
1832 soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1833 soup_message_set_request (
1835 "text/calendar; charset=utf-8",
1837 *post_fb, strlen (*post_fb));
1839 send_and_handle_redirection (cbdav, message, NULL, cancellable, error);
1841 if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1842 status_code_to_result (message, cbdav, FALSE, error);
1843 if (message->status_code == 401)
1844 caldav_authenticate (cbdav, FALSE, NULL, NULL);
1846 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");
1848 g_object_unref (message);
1854 *post_fb = g_strdup (message->response_body->data);
1856 g_object_unref (message);
1860 caldav_gen_file_from_uid_cal (ECalBackendCalDAV *cbdav,
1861 icalcomponent *icalcomp)
1863 icalcomponent_kind my_kind;
1864 const gchar *uid = NULL;
1865 gchar *filename, *res;
1867 g_return_val_if_fail (cbdav != NULL, NULL);
1868 g_return_val_if_fail (icalcomp != NULL, NULL);
1870 my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
1871 if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
1872 icalcomponent *subcomp;
1874 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
1876 subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
1877 uid = icalcomponent_get_uid (subcomp);
1881 } else if (icalcomponent_isa (icalcomp) == my_kind) {
1882 uid = icalcomponent_get_uid (icalcomp);
1888 filename = g_strconcat (uid, ".ics", NULL);
1889 res = soup_uri_encode (filename, NULL);
1896 caldav_server_put_object (ECalBackendCalDAV *cbdav,
1897 CalDAVObject *object,
1898 icalcomponent *icalcomp,
1899 GCancellable *cancellable,
1902 SoupMessage *message;
1908 g_assert (object != NULL && object->cdata != NULL);
1910 uri = caldav_generate_uri (cbdav, object->href);
1911 message = soup_message_new (SOUP_METHOD_PUT, uri);
1913 if (message == NULL) {
1914 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
1918 soup_message_headers_append (
1919 message->request_headers,
1920 "User-Agent", "Evolution/" VERSION);
1922 /* For new items we use the If-None-Match so we don't
1923 * acidently override resources, for item updates we
1924 * use the If-Match header to avoid the Lost-update
1926 if (object->etag == NULL) {
1927 soup_message_headers_append (message->request_headers, "If-None-Match", "*");
1929 soup_message_headers_append (
1930 message->request_headers,
1931 "If-Match", object->etag);
1934 soup_message_set_request (
1936 "text/calendar; charset=utf-8",
1939 strlen (object->cdata));
1942 send_and_handle_redirection (cbdav, message, &uri, cancellable, perror);
1945 gchar *file = strrchr (uri, '/');
1947 /* there was a redirect, update href properly */
1951 g_free (object->href);
1953 decoded = soup_uri_decode (file + 1);
1954 object->href = soup_uri_encode (decoded ? decoded : (file + 1), NULL);
1962 if (status_code_to_result (message, cbdav, FALSE, perror)) {
1963 GError *local_error = NULL;
1965 hdr = soup_message_headers_get_list (message->response_headers, "ETag");
1967 g_free (object->etag);
1968 object->etag = quote_etag (hdr);
1971 /* "201 Created" can contain a Location with a link where the component was saved */
1972 hdr = soup_message_headers_get_list (message->response_headers, "Location");
1974 /* reflect possible href change */
1975 gchar *file = strrchr (hdr, '/');
1980 g_free (object->href);
1982 decoded = soup_uri_decode (file + 1);
1983 object->href = soup_uri_encode (decoded ? decoded : (file + 1), NULL);
1989 if (!caldav_server_get_object (cbdav, object, cancellable, &local_error)) {
1990 if (g_error_matches (local_error, E_DATA_CAL_ERROR, ObjectNotFound)) {
1993 /* OK, the event was properly created, but cannot be found on the place
1994 * where it was PUT - why didn't server tell us where it saved it? */
1995 g_clear_error (&local_error);
1997 /* try whether it's saved as its UID.ics file */
1998 file = caldav_gen_file_from_uid_cal (cbdav, icalcomp);
2000 g_free (object->href);
2001 object->href = file;
2003 if (!caldav_server_get_object (cbdav, object, cancellable, &local_error)) {
2004 if (g_error_matches (local_error, E_DATA_CAL_ERROR, ObjectNotFound)) {
2005 g_clear_error (&local_error);
2007 /* not sure what can happen, but do not need to guess for ever,
2008 * thus report success and update the calendar to get fresh info */
2009 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_WORK);
2010 g_cond_signal (&cbdav->priv->cond);
2018 icalcomponent *use_comp = NULL;
2020 if (object->cdata) {
2021 /* maybe server also modified component, thus rather store the server's */
2022 use_comp = icalparser_parse_string (object->cdata);
2026 use_comp = icalcomp;
2028 put_comp_to_cache (cbdav, use_comp, object->href, object->etag);
2030 if (use_comp != icalcomp)
2031 icalcomponent_free (use_comp);
2033 g_propagate_error (perror, local_error);
2035 } else if (message->status_code == 401) {
2036 caldav_authenticate (cbdav, FALSE, NULL, NULL);
2039 g_object_unref (message);
2045 caldav_server_delete_object (ECalBackendCalDAV *cbdav,
2046 CalDAVObject *object,
2047 GCancellable *cancellable,
2050 SoupMessage *message;
2053 g_assert (object != NULL && object->href != NULL);
2055 uri = caldav_generate_uri (cbdav, object->href);
2056 message = soup_message_new (SOUP_METHOD_DELETE, uri);
2058 if (message == NULL) {
2059 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
2063 soup_message_headers_append (
2064 message->request_headers,
2065 "User-Agent", "Evolution/" VERSION);
2067 if (object->etag != NULL) {
2068 soup_message_headers_append (
2069 message->request_headers,
2070 "If-Match", object->etag);
2073 send_and_handle_redirection (cbdav, message, NULL, cancellable, perror);
2075 status_code_to_result (message, cbdav, FALSE, perror);
2077 if (message->status_code == 401)
2078 caldav_authenticate (cbdav, FALSE, NULL, NULL);
2080 g_object_unref (message);
2084 caldav_receive_schedule_outbox_url (ECalBackendCalDAV *cbdav,
2085 GCancellable *cancellable,
2088 SoupMessage *message;
2089 xmlOutputBufferPtr buf;
2091 xmlNodePtr root, node;
2093 gconstpointer buf_content;
2095 gchar *owner = NULL;
2097 g_return_val_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav), FALSE);
2098 g_return_val_if_fail (cbdav->priv->schedule_outbox_url == NULL, TRUE);
2100 /* Prepare the soup message */
2101 message = soup_message_new ("PROPFIND", cbdav->priv->uri);
2102 if (message == NULL)
2105 doc = xmlNewDoc ((xmlChar *) "1.0");
2106 root = xmlNewDocNode (doc, NULL, (xmlChar *) "propfind", NULL);
2107 xmlDocSetRootElement (doc, root);
2108 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", NULL);
2110 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
2111 xmlNewTextChild (node, nsdav, (xmlChar *) "owner", NULL);
2113 buf = xmlAllocOutputBuffer (NULL);
2114 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
2115 xmlOutputBufferFlush (buf);
2117 soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
2118 soup_message_headers_append (message->request_headers, "Depth", "0");
2120 buf_content = compat_libxml_output_buffer_get_content (buf, &buf_size);
2121 soup_message_set_request (
2125 buf_content, buf_size);
2127 /* Send the request now */
2128 send_and_handle_redirection (cbdav, message, NULL, cancellable, error);
2130 /* Clean up the memory */
2131 xmlOutputBufferClose (buf);
2134 /* Check the result */
2135 if (message->status_code == 207 && parse_propfind_response (message, XPATH_OWNER_STATUS, XPATH_OWNER, &owner) && owner && *owner) {
2139 g_object_unref (message);
2141 /* owner is a full path to the user's URL, thus change it in
2142 * calendar's uri when asking for schedule-outbox-URL */
2143 suri = soup_uri_new (cbdav->priv->uri);
2144 soup_uri_set_path (suri, owner);
2146 owner = soup_uri_to_string (suri, FALSE);
2147 soup_uri_free (suri);
2149 message = soup_message_new ("PROPFIND", owner);
2150 if (message == NULL) {
2155 doc = xmlNewDoc ((xmlChar *) "1.0");
2156 root = xmlNewDocNode (doc, NULL, (xmlChar *) "propfind", NULL);
2157 xmlDocSetRootElement (doc, root);
2158 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", NULL);
2159 nscd = xmlNewNs (root, (xmlChar *) "urn:ietf:params:xml:ns:caldav", (xmlChar *) "C");
2161 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
2162 xmlNewTextChild (node, nscd, (xmlChar *) "schedule-outbox-URL", NULL);
2164 buf = xmlAllocOutputBuffer (NULL);
2165 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
2166 xmlOutputBufferFlush (buf);
2168 soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
2169 soup_message_headers_append (message->request_headers, "Depth", "0");
2171 buf_content = compat_libxml_output_buffer_get_content (buf, &buf_size);
2172 soup_message_set_request (
2176 buf_content, buf_size);
2178 /* Send the request now */
2179 send_and_handle_redirection (cbdav, message, NULL, cancellable, error);
2181 if (message->status_code == 207 && parse_propfind_response (message, XPATH_SCHEDULE_OUTBOX_URL_STATUS, XPATH_SCHEDULE_OUTBOX_URL, &cbdav->priv->schedule_outbox_url)) {
2182 if (!*cbdav->priv->schedule_outbox_url) {
2183 g_free (cbdav->priv->schedule_outbox_url);
2184 cbdav->priv->schedule_outbox_url = NULL;
2186 /* make it a full URI */
2187 suri = soup_uri_new (cbdav->priv->uri);
2188 soup_uri_set_path (suri, cbdav->priv->schedule_outbox_url);
2189 g_free (cbdav->priv->schedule_outbox_url);
2190 cbdav->priv->schedule_outbox_url = soup_uri_to_string (suri, FALSE);
2191 soup_uri_free (suri);
2195 /* Clean up the memory */
2196 xmlOutputBufferClose (buf);
2198 } else if (message->status_code == 401) {
2199 caldav_authenticate (cbdav, FALSE, NULL, NULL);
2203 g_object_unref (message);
2207 return cbdav->priv->schedule_outbox_url != NULL;
2210 /* ************************************************************************* */
2211 /* Synchronization foo */
2213 static gboolean extract_timezones (ECalBackendCalDAV *cbdav, icalcomponent *icomp);
2215 struct cache_comp_list
2221 remove_complist_from_cache_and_notify_cb (gpointer key,
2226 struct cache_comp_list *ccl = value;
2227 ECalBackendCalDAV *cbdav = data;
2229 for (l = ccl->slist; l; l = l->next) {
2230 ECalComponent *old_comp = l->data;
2231 ECalComponentId *id;
2233 id = e_cal_component_get_id (old_comp);
2238 if (e_cal_backend_store_remove_component (cbdav->priv->store, id->uid, id->rid)) {
2239 e_cal_backend_notify_component_removed ((ECalBackend *) cbdav, id, old_comp, NULL);
2242 e_cal_component_free_id (id);
2244 remove_cached_attachment (cbdav, (const gchar *) key);
2250 free_comp_list (gpointer cclist)
2252 struct cache_comp_list *ccl = cclist;
2254 g_return_if_fail (ccl != NULL);
2256 g_slist_foreach (ccl->slist, (GFunc) g_object_unref, NULL);
2257 g_slist_free (ccl->slist);
2261 #define etags_match(_tag1, _tag2) ((_tag1 == _tag2) ? TRUE : \
2262 g_str_equal (_tag1 != NULL ? _tag1 : "", \
2263 _tag2 != NULL ? _tag2 : ""))
2265 /* start_time/end_time is an interval for checking changes. If both greater than zero,
2266 * only the interval is checked and the removed items are not notified, as they can
2270 synchronize_cache (ECalBackendCalDAV *cbdav,
2273 gboolean can_check_ctag)
2275 CalDAVObject *sobjs, *object;
2276 GSList *c_objs, *c_iter; /* list of all items known from our cache */
2277 GTree *c_uid2complist; /* cache components list (with detached instances) sorted by (master's) uid */
2278 GHashTable *c_href2uid; /* connection between href and a (master's) uid */
2279 GSList *hrefs_to_update, *htu; /* list of href-s to update */
2282 /* intentionally do server-side checking first, and then the bool test,
2283 to store actual ctag value first, and then update the content, to not
2284 do it again the next time this function is called */
2285 if (!check_calendar_changed_on_server (cbdav, start_time == (time_t) 0) && can_check_ctag) {
2286 /* no changes on the server, no update required */
2293 /* get list of server objects */
2294 if (!caldav_server_list_objects (cbdav, &sobjs, &len, NULL, start_time, end_time))
2297 c_objs = e_cal_backend_store_get_components (cbdav->priv->store);
2299 if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2300 printf ("CalDAV - found %d objects on the server, locally stored %d objects\n", len, g_slist_length (c_objs)); fflush (stdout);
2303 /* do not store changes in cache immediately - makes things significantly quicker */
2304 e_cal_backend_store_freeze_changes (cbdav->priv->store);
2306 c_uid2complist = g_tree_new_full ((GCompareDataFunc) g_strcmp0, NULL, g_free, free_comp_list);
2307 c_href2uid = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
2309 /* fill indexed hash and tree with cached components */
2310 for (c_iter = c_objs; c_iter; c_iter = g_slist_next (c_iter)) {
2311 ECalComponent *ccomp = E_CAL_COMPONENT (c_iter->data);
2312 const gchar *uid = NULL;
2313 struct cache_comp_list *ccl;
2316 e_cal_component_get_uid (ccomp, &uid);
2318 g_warning ("broken component with NULL Id");
2322 href = ecalcomp_get_href (ccomp);
2325 g_warning ("href of object NULL :(");
2329 ccl = g_tree_lookup (c_uid2complist, uid);
2331 ccl->slist = g_slist_prepend (ccl->slist, g_object_ref (ccomp));
2333 ccl = g_new0 (struct cache_comp_list, 1);
2334 ccl->slist = g_slist_append (NULL, g_object_ref (ccomp));
2336 /* make a copy, which will be used in the c_href2uid too */
2337 uid = g_strdup (uid);
2339 g_tree_insert (c_uid2complist, (gpointer) uid, ccl);
2342 if (g_hash_table_lookup (c_href2uid, href) == NULL) {
2343 /* uid is from a component or c_uid2complist key, thus will not be
2344 * freed before a removal from c_uid2complist, thus do not duplicate it,
2345 * rather save memory */
2346 g_hash_table_insert (c_href2uid, href, (gpointer) uid);
2352 /* clear it now, we do not need it later */
2353 g_slist_foreach (c_objs, (GFunc) g_object_unref, NULL);
2354 g_slist_free (c_objs);
2357 hrefs_to_update = NULL;
2359 /* see if we have to update or add some objects */
2360 for (i = 0, object = sobjs; i < len && cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK; i++, object++) {
2361 ECalComponent *ccomp = NULL;
2364 struct cache_comp_list *ccl;
2366 if (object->status != 200) {
2367 /* just continue here, so that the object
2368 * doesnt get removed from the cobjs list
2369 * - therefore it will be removed */
2373 uid = g_hash_table_lookup (c_href2uid, object->href);
2375 ccl = g_tree_lookup (c_uid2complist, uid);
2378 for (sl = ccl->slist; sl && !etag; sl = sl->next) {
2381 etag = ecalcomp_get_etag (ccomp);
2389 if (!etag || !etags_match (etag, object->etag)) {
2390 hrefs_to_update = g_slist_prepend (hrefs_to_update, object->href);
2391 } else if (uid && ccl) {
2392 /* all components cover by this uid are up-to-date */
2395 for (p = ccl->slist; p; p = p->next) {
2396 g_object_unref (p->data);
2399 g_slist_free (ccl->slist);
2406 /* free hash table, as it is not used anymore */
2407 g_hash_table_destroy (c_href2uid);
2410 if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2411 printf ("CalDAV - recognized %d items to update\n", g_slist_length (hrefs_to_update)); fflush (stdout);
2414 htu = hrefs_to_update;
2415 while (htu && cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK) {
2417 GSList *to_fetch = NULL;
2419 while (count < CALDAV_MAX_MULTIGET_AMOUNT && htu) {
2420 to_fetch = g_slist_prepend (to_fetch, htu->data);
2425 if (to_fetch && cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK) {
2426 CalDAVObject *up_sobjs = NULL;
2428 if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2429 printf ("CalDAV - going to fetch %d items\n", g_slist_length (to_fetch)); fflush (stdout);
2433 if (!caldav_server_list_objects (cbdav, &up_sobjs, &count, to_fetch, 0, 0)) {
2434 fprintf (stderr, "CalDAV - failed to retrieve bunch of items\n"); fflush (stderr);
2438 if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2439 printf ("CalDAV - fetched bunch of %d items\n", count); fflush (stdout);
2442 /* we are going to update cache */
2443 /* they are downloaded, so process them */
2444 for (i = 0, object = up_sobjs; i < count /*&& cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK */; i++, object++) {
2445 if (object->status == 200 && object->href && object->etag && object->cdata && *object->cdata) {
2446 icalcomponent *icomp = icalparser_parse_string (object->cdata);
2449 put_server_comp_to_cache (cbdav, icomp, object->href, object->etag, c_uid2complist);
2450 icalcomponent_free (icomp);
2454 /* these free immediately */
2455 caldav_object_free (object, FALSE);
2458 /* cache update done for fetched items */
2462 /* do not free 'data' itself, it's part of 'sobjs' */
2463 g_slist_free (to_fetch);
2466 /* if not interrupted and not using the time range... */
2467 if (cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK && (!start_time || !end_time)) {
2468 /* ...remove old (not on server anymore) items from our cache and notify of a removal */
2469 g_tree_foreach (c_uid2complist, remove_complist_from_cache_and_notify_cb, cbdav);
2472 if (cbdav->priv->ctag_to_store) {
2473 /* store only when wasn't interrupted */
2474 if (cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK && start_time == 0 && end_time == 0) {
2475 e_cal_backend_store_put_key_value (cbdav->priv->store, CALDAV_CTAG_KEY, cbdav->priv->ctag_to_store);
2478 g_free (cbdav->priv->ctag_to_store);
2479 cbdav->priv->ctag_to_store = NULL;
2482 /* save cache changes to disk finally */
2483 e_cal_backend_store_thaw_changes (cbdav->priv->store);
2485 for (i = 0, object = sobjs; i < len; i++, object++) {
2486 caldav_object_free (object, FALSE);
2489 g_tree_destroy (c_uid2complist);
2490 g_slist_free (hrefs_to_update);
2495 is_google_uri (const gchar *uri)
2500 g_return_val_if_fail (uri != NULL, FALSE);
2502 suri = soup_uri_new (uri);
2503 g_return_val_if_fail (suri != NULL, FALSE);
2505 res = suri->host && g_ascii_strcasecmp (suri->host, "www.google.com") == 0;
2507 soup_uri_free (suri);
2513 time_to_refresh_caldav_calendar_cb (ESource *source,
2516 ECalBackendCalDAV *cbdav = user_data;
2518 g_return_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav));
2520 g_cond_signal (&cbdav->priv->cond);
2523 /* ************************************************************************* */
2526 caldav_synch_slave_loop (gpointer data)
2528 ECalBackendCalDAV *cbdav;
2530 icaltimezone *utc = icaltimezone_get_utc_timezone ();
2531 gboolean know_unreachable;
2533 cbdav = E_CAL_BACKEND_CALDAV (data);
2535 g_mutex_lock (&cbdav->priv->busy_lock);
2537 know_unreachable = !cbdav->priv->opened;
2539 while (cbdav->priv->slave_cmd != SLAVE_SHOULD_DIE) {
2540 gboolean can_check_ctag = TRUE;
2542 if (cbdav->priv->slave_cmd == SLAVE_SHOULD_SLEEP) {
2543 /* just sleep until we get woken up again */
2544 g_cond_wait (&cbdav->priv->cond, &cbdav->priv->busy_lock);
2546 /* check if we should die, work or sleep again */
2550 /* Ok here we go, do some real work
2551 * Synch it baby one more time ...
2553 cbdav->priv->slave_busy = TRUE;
2554 if (cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK_NO_CTAG_CHECK) {
2555 cbdav->priv->slave_cmd = SLAVE_SHOULD_WORK;
2556 can_check_ctag = FALSE;
2559 if (!cbdav->priv->opened) {
2560 gboolean server_unreachable = FALSE;
2561 GError *local_error = NULL;
2563 if (caldav_server_open_calendar (cbdav, &server_unreachable, NULL, &local_error)) {
2564 cbdav->priv->opened = TRUE;
2565 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_WORK);
2566 g_cond_signal (&cbdav->priv->cond);
2568 cbdav->priv->is_google = is_google_uri (cbdav->priv->uri);
2569 know_unreachable = FALSE;
2570 } else if (local_error) {
2571 cbdav->priv->opened = FALSE;
2572 e_cal_backend_set_writable (
2573 E_CAL_BACKEND (cbdav), FALSE);
2575 if (!know_unreachable) {
2578 know_unreachable = TRUE;
2580 msg = g_strdup_printf (_("Server is unreachable, calendar is opened in read-only mode.\nError message: %s"), local_error->message);
2581 e_cal_backend_notify_error (E_CAL_BACKEND (cbdav), msg);
2585 g_clear_error (&local_error);
2587 cbdav->priv->opened = FALSE;
2588 e_cal_backend_set_writable (
2589 E_CAL_BACKEND (cbdav), FALSE);
2590 know_unreachable = TRUE;
2594 if (cbdav->priv->opened) {
2596 /* check for events in the month before/after today first,
2597 * to show user actual data as soon as possible */
2598 synchronize_cache (cbdav, time_add_week_with_zone (now, -5, utc), time_add_week_with_zone (now, +5, utc), can_check_ctag);
2600 if (cbdav->priv->slave_cmd != SLAVE_SHOULD_SLEEP) {
2601 /* and then check for changes in a whole calendar */
2602 synchronize_cache (cbdav, 0, 0, can_check_ctag);
2605 if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2608 c_objs = e_cal_backend_store_get_components (cbdav->priv->store);
2610 printf ("CalDAV - finished syncing with %d items in a cache\n", g_slist_length (c_objs)); fflush (stdout);
2612 g_slist_foreach (c_objs, (GFunc) g_object_unref, NULL);
2613 g_slist_free (c_objs);
2617 cbdav->priv->slave_busy = FALSE;
2619 /* puhh that was hard, get some rest :) */
2620 g_cond_wait (&cbdav->priv->cond, &cbdav->priv->busy_lock);
2623 cbdav->priv->synch_slave = NULL;
2625 /* signal we are done */
2626 g_cond_signal (&cbdav->priv->slave_gone_cond);
2628 /* we got killed ... */
2629 g_mutex_unlock (&cbdav->priv->busy_lock);
2634 maybe_append_email_domain (const gchar *username,
2635 const gchar *may_append)
2637 if (!username || !*username)
2640 if (strchr (username, '@'))
2641 return g_strdup (username);
2643 return g_strconcat (username, may_append, NULL);
2647 get_usermail (ECalBackend *backend)
2649 ECalBackendCalDAV *cbdav;
2651 ESourceAuthentication *auth_extension;
2652 ESourceWebdav *webdav_extension;
2653 const gchar *extension_name;
2658 g_return_val_if_fail (backend != NULL, NULL);
2660 source = e_backend_get_source (E_BACKEND (backend));
2662 extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
2663 webdav_extension = e_source_get_extension (source, extension_name);
2665 /* This will never return an empty string. */
2666 usermail = e_source_webdav_dup_email_address (webdav_extension);
2668 if (usermail != NULL)
2671 cbdav = E_CAL_BACKEND_CALDAV (backend);
2673 extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
2674 auth_extension = e_source_get_extension (source, extension_name);
2675 username = e_source_authentication_dup_user (auth_extension);
2677 if (cbdav->priv && cbdav->priv->is_google)
2678 res = maybe_append_email_domain (username, "@gmail.com");
2685 /* ************************************************************************* */
2686 /* ********** ECalBackendSync virtual function implementation ************* */
2689 caldav_get_backend_property (ECalBackend *backend,
2690 const gchar *prop_name)
2692 g_return_val_if_fail (prop_name != NULL, FALSE);
2694 if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
2695 ESourceWebdav *extension;
2699 const gchar *extension_name;
2701 caps = g_string_new (
2702 CAL_STATIC_CAPABILITY_NO_THISANDFUTURE ","
2703 CAL_STATIC_CAPABILITY_NO_THISANDPRIOR ","
2704 CAL_STATIC_CAPABILITY_REFRESH_SUPPORTED);
2706 usermail = get_usermail (E_CAL_BACKEND (backend));
2707 if (!usermail || !*usermail)
2708 g_string_append (caps, "," CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS);
2711 source = e_backend_get_source (E_BACKEND (backend));
2713 extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
2714 extension = e_source_get_extension (source, extension_name);
2716 if (e_source_webdav_get_calendar_auto_schedule (extension)) {
2719 "," CAL_STATIC_CAPABILITY_CREATE_MESSAGES
2720 "," CAL_STATIC_CAPABILITY_SAVE_SCHEDULES);
2723 return g_string_free (caps, FALSE);
2725 } else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS) ||
2726 g_str_equal (prop_name, CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS)) {
2727 return get_usermail (E_CAL_BACKEND (backend));
2729 } else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_DEFAULT_OBJECT)) {
2730 ECalComponent *comp;
2733 comp = e_cal_component_new ();
2735 switch (e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
2736 case ICAL_VEVENT_COMPONENT:
2737 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
2739 case ICAL_VTODO_COMPONENT:
2740 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
2742 case ICAL_VJOURNAL_COMPONENT:
2743 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
2746 g_object_unref (comp);
2750 prop_value = e_cal_component_get_as_string (comp);
2752 g_object_unref (comp);
2757 /* Chain up to parent's get_backend_property() method. */
2758 return E_CAL_BACKEND_CLASS (e_cal_backend_caldav_parent_class)->
2759 get_backend_property (backend, prop_name);
2763 caldav_shutdown (ECalBackend *backend)
2765 ECalBackendCalDAVPrivate *priv;
2768 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (backend);
2770 /* Chain up to parent's shutdown() method. */
2771 E_CAL_BACKEND_CLASS (e_cal_backend_caldav_parent_class)->shutdown (backend);
2773 /* tell the slave to stop before acquiring a lock,
2774 * as it can work at the moment, and lock can be locked */
2775 update_slave_cmd (priv, SLAVE_SHOULD_DIE);
2777 g_mutex_lock (&priv->busy_lock);
2779 /* XXX Not sure if this really needs to be part of
2780 * shutdown or if we can just do it in dispose(). */
2781 source = e_backend_get_source (E_BACKEND (backend));
2783 g_signal_handlers_disconnect_by_func (G_OBJECT (source), caldav_source_changed_cb, backend);
2785 if (priv->refresh_id) {
2786 e_source_refresh_remove_timeout (source, priv->refresh_id);
2787 priv->refresh_id = 0;
2791 /* stop the slave */
2792 while (priv->synch_slave) {
2793 g_cond_signal (&priv->cond);
2795 /* wait until the slave died */
2796 g_cond_wait (&priv->slave_gone_cond, &priv->busy_lock);
2799 g_mutex_unlock (&priv->busy_lock);
2803 initialize_backend (ECalBackendCalDAV *cbdav,
2806 ESourceAuthentication *auth_extension;
2807 ESourceOffline *offline_extension;
2808 ESourceWebdav *webdav_extension;
2809 ECalBackend *backend;
2813 const gchar *cache_dir;
2814 const gchar *extension_name;
2816 backend = E_CAL_BACKEND (cbdav);
2817 cache_dir = e_cal_backend_get_cache_dir (backend);
2818 source = e_backend_get_source (E_BACKEND (backend));
2820 extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
2821 auth_extension = e_source_get_extension (source, extension_name);
2823 extension_name = E_SOURCE_EXTENSION_OFFLINE;
2824 offline_extension = e_source_get_extension (source, extension_name);
2826 extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
2827 webdav_extension = e_source_get_extension (source, extension_name);
2829 if (!g_signal_handler_find (G_OBJECT (source), G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, caldav_source_changed_cb, cbdav))
2830 g_signal_connect (G_OBJECT (source), "changed", G_CALLBACK (caldav_source_changed_cb), cbdav);
2832 cbdav->priv->do_offline = e_source_offline_get_stay_synchronized (offline_extension);
2834 cbdav->priv->auth_required = e_source_authentication_required (auth_extension);
2836 soup_uri = e_source_webdav_dup_soup_uri (webdav_extension);
2838 /* properly encode uri */
2839 if (soup_uri != NULL && soup_uri->path != NULL) {
2842 if (strchr (soup_uri->path, '%')) {
2843 /* If path contains anything already encoded, then
2844 * decode it first, thus it'll be managed properly.
2845 * For example, the '#' in a path is in URI shown as
2846 * %23 and not doing this decode makes it being like
2847 * %2523, which is not what is wanted here. */
2848 tmp = soup_uri_decode (soup_uri->path);
2849 soup_uri_set_path (soup_uri, tmp);
2853 tmp = soup_uri_encode (soup_uri->path, NULL);
2854 path = soup_uri_normalize (tmp, "/");
2856 soup_uri_set_path (soup_uri, path);
2862 g_free (cbdav->priv->uri);
2863 cbdav->priv->uri = soup_uri_to_string (soup_uri, FALSE);
2865 soup_uri_free (soup_uri);
2867 g_return_val_if_fail (cbdav->priv->uri != NULL, FALSE);
2869 /* remove trailing slashes... */
2870 if (cbdav->priv->uri != NULL) {
2871 len = strlen (cbdav->priv->uri);
2873 if (cbdav->priv->uri[len] == '/') {
2874 cbdav->priv->uri[len] = '\0';
2881 /* ...and append exactly one slash */
2882 if (cbdav->priv->uri && *cbdav->priv->uri) {
2883 gchar *tmp = cbdav->priv->uri;
2885 cbdav->priv->uri = g_strconcat (cbdav->priv->uri, "/", NULL);
2890 if (cbdav->priv->store == NULL) {
2891 /* remove the old cache while migrating to ECalBackendStore */
2892 e_cal_backend_cache_remove (cache_dir, "cache.xml");
2893 cbdav->priv->store = e_cal_backend_store_new (
2894 cache_dir, E_TIMEZONE_CACHE (cbdav));
2895 e_cal_backend_store_load (cbdav->priv->store);
2898 /* Set the local attachment store */
2899 if (g_mkdir_with_parents (cache_dir, 0700) < 0) {
2900 g_propagate_error (perror, e_data_cal_create_error_fmt (OtherError, _("Cannot create local cache folder '%s'"), cache_dir));
2904 if (!cbdav->priv->synch_slave) {
2907 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_SLEEP);
2908 slave = g_thread_new (NULL, caldav_synch_slave_loop, cbdav);
2910 cbdav->priv->synch_slave = slave;
2911 g_thread_unref (slave);
2914 if (cbdav->priv->refresh_id == 0) {
2915 cbdav->priv->refresh_id = e_source_refresh_add_timeout (
2916 source, NULL, time_to_refresh_caldav_calendar_cb, cbdav, NULL);
2923 open_calendar (ECalBackendCalDAV *cbdav,
2924 GCancellable *cancellable,
2927 gboolean server_unreachable = FALSE;
2929 GError *local_error = NULL;
2931 g_return_val_if_fail (cbdav != NULL, FALSE);
2933 success = caldav_server_open_calendar (
2934 cbdav, &server_unreachable, cancellable, &local_error);
2937 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_WORK);
2938 g_cond_signal (&cbdav->priv->cond);
2940 cbdav->priv->is_google = is_google_uri (cbdav->priv->uri);
2941 } else if (server_unreachable) {
2942 cbdav->priv->opened = FALSE;
2943 e_cal_backend_set_writable (E_CAL_BACKEND (cbdav), FALSE);
2945 gchar *msg = g_strdup_printf (_("Server is unreachable, calendar is opened in read-only mode.\nError message: %s"), local_error->message);
2946 e_cal_backend_notify_error (E_CAL_BACKEND (cbdav), msg);
2948 g_clear_error (&local_error);
2953 if (local_error != NULL)
2954 g_propagate_error (error, local_error);
2960 caldav_do_open (ECalBackendSync *backend,
2962 GCancellable *cancellable,
2963 gboolean only_if_exists,
2966 ECalBackendCalDAV *cbdav;
2969 cbdav = E_CAL_BACKEND_CALDAV (backend);
2971 g_mutex_lock (&cbdav->priv->busy_lock);
2973 /* let it decide the 'getctag' extension availability again */
2974 cbdav->priv->ctag_supported = TRUE;
2976 if (!cbdav->priv->loaded && !initialize_backend (cbdav, perror)) {
2977 g_mutex_unlock (&cbdav->priv->busy_lock);
2981 online = e_backend_get_online (E_BACKEND (backend));
2983 if (!cbdav->priv->do_offline && !online) {
2984 g_mutex_unlock (&cbdav->priv->busy_lock);
2985 g_propagate_error (perror, EDC_ERROR (RepositoryOffline));
2989 cbdav->priv->loaded = TRUE;
2990 cbdav->priv->opened = TRUE;
2991 cbdav->priv->is_google = FALSE;
2994 GError *local_error = NULL;
2996 open_calendar (cbdav, cancellable, &local_error);
2998 if (g_error_matches (local_error, E_DATA_CAL_ERROR, AuthenticationFailed)) {
2999 g_clear_error (&local_error);
3000 caldav_authenticate (
3001 cbdav, FALSE, cancellable, perror);
3004 if (local_error != NULL)
3005 g_propagate_error (perror, local_error);
3008 e_cal_backend_set_writable (E_CAL_BACKEND (cbdav), FALSE);
3011 g_mutex_unlock (&cbdav->priv->busy_lock);
3015 caldav_refresh (ECalBackendSync *backend,
3017 GCancellable *cancellable,
3020 ECalBackendCalDAV *cbdav;
3023 cbdav = E_CAL_BACKEND_CALDAV (backend);
3025 g_mutex_lock (&cbdav->priv->busy_lock);
3027 if (!cbdav->priv->loaded
3028 || cbdav->priv->slave_cmd == SLAVE_SHOULD_DIE
3029 || !check_state (cbdav, &online, NULL)
3031 g_mutex_unlock (&cbdav->priv->busy_lock);
3035 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_WORK_NO_CTAG_CHECK);
3038 g_cond_signal (&cbdav->priv->cond);
3039 g_mutex_unlock (&cbdav->priv->busy_lock);
3043 remove_comp_from_cache_cb (gpointer value,
3046 ECalComponent *comp = value;
3047 ECalBackendStore *store = user_data;
3048 ECalComponentId *id;
3050 g_return_if_fail (comp != NULL);
3051 g_return_if_fail (store != NULL);
3053 id = e_cal_component_get_id (comp);
3054 g_return_if_fail (id != NULL);
3056 e_cal_backend_store_remove_component (store, id->uid, id->rid);
3057 e_cal_component_free_id (id);
3061 remove_comp_from_cache (ECalBackendCalDAV *cbdav,
3065 gboolean res = FALSE;
3067 if (!rid || !*rid) {
3068 /* get with detached instances */
3069 GSList *objects = e_cal_backend_store_get_components_by_uid (cbdav->priv->store, uid);
3072 g_slist_foreach (objects, (GFunc) remove_comp_from_cache_cb, cbdav->priv->store);
3073 g_slist_foreach (objects, (GFunc) g_object_unref, NULL);
3074 g_slist_free (objects);
3079 res = e_cal_backend_store_remove_component (cbdav->priv->store, uid, rid);
3086 add_detached_recur_to_vcalendar_cb (gpointer value,
3089 icalcomponent *recurrence = e_cal_component_get_icalcomponent (value);
3090 icalcomponent *vcalendar = user_data;
3092 icalcomponent_add_component (
3094 icalcomponent_new_clone (recurrence));
3098 sort_master_first (gconstpointer a,
3101 icalcomponent *ca, *cb;
3103 ca = e_cal_component_get_icalcomponent ((ECalComponent *) a);
3104 cb = e_cal_component_get_icalcomponent ((ECalComponent *) b);
3115 return icaltime_compare (icalcomponent_get_recurrenceid (ca), icalcomponent_get_recurrenceid (cb));
3118 /* Returns new icalcomponent, with all detached instances stored in a cache.
3119 * The cache lock should be locked when called this function.
3121 static icalcomponent *
3122 get_comp_from_cache (ECalBackendCalDAV *cbdav,
3128 icalcomponent *icalcomp = NULL;
3130 if (rid == NULL || !*rid) {
3131 /* get with detached instances */
3132 GSList *objects = e_cal_backend_store_get_components_by_uid (cbdav->priv->store, uid);
3138 if (g_slist_length (objects) == 1) {
3139 ECalComponent *comp = objects->data;
3141 /* will be unreffed a bit later */
3143 icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
3145 /* if we have detached recurrences, return a VCALENDAR */
3146 icalcomp = e_cal_util_new_top_level ();
3148 objects = g_slist_sort (objects, sort_master_first);
3150 /* add all detached recurrences and the master object */
3151 g_slist_foreach (objects, add_detached_recur_to_vcalendar_cb, icalcomp);
3154 /* every component has set same href and etag, thus it doesn't matter where it will be read */
3156 *href = ecalcomp_get_href (objects->data);
3158 *etag = ecalcomp_get_etag (objects->data);
3160 g_slist_foreach (objects, (GFunc) g_object_unref, NULL);
3161 g_slist_free (objects);
3163 /* get the exact object */
3164 ECalComponent *comp = e_cal_backend_store_get_component (cbdav->priv->store, uid, rid);
3167 icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
3169 *href = ecalcomp_get_href (comp);
3171 *etag = ecalcomp_get_etag (comp);
3172 g_object_unref (comp);
3180 put_server_comp_to_cache (ECalBackendCalDAV *cbdav,
3181 icalcomponent *icomp,
3184 GTree *c_uid2complist)
3186 icalcomponent_kind kind;
3187 ECalBackend *cal_backend;
3189 g_return_if_fail (cbdav != NULL);
3190 g_return_if_fail (icomp != NULL);
3192 cal_backend = E_CAL_BACKEND (cbdav);
3193 kind = icalcomponent_isa (icomp);
3194 extract_timezones (cbdav, icomp);
3196 if (kind == ICAL_VCALENDAR_COMPONENT) {
3197 icalcomponent *subcomp;
3199 kind = e_cal_backend_get_kind (cal_backend);
3201 for (subcomp = icalcomponent_get_first_component (icomp, kind);
3203 subcomp = icalcomponent_get_next_component (icomp, kind)) {
3204 ECalComponent *new_comp, *old_comp;
3206 convert_to_url_attachment (cbdav, subcomp);
3207 new_comp = e_cal_component_new ();
3208 if (e_cal_component_set_icalcomponent (new_comp, icalcomponent_new_clone (subcomp))) {
3209 const gchar *uid = NULL;
3210 struct cache_comp_list *ccl;
3212 e_cal_component_get_uid (new_comp, &uid);
3214 g_warning ("%s: no UID on component!", G_STRFUNC);
3215 g_object_unref (new_comp);
3220 ecalcomp_set_href (new_comp, href);
3222 ecalcomp_set_etag (new_comp, etag);
3225 if (c_uid2complist) {
3226 ccl = g_tree_lookup (c_uid2complist, uid);
3228 gchar *nc_rid = e_cal_component_get_recurid_as_string (new_comp);
3231 for (p = ccl->slist; p && !old_comp; p = p->next) {
3236 oc_rid = e_cal_component_get_recurid_as_string (old_comp);
3237 if (g_strcmp0 (nc_rid, oc_rid) != 0) {
3248 put_component_to_store (cbdav, new_comp);
3250 if (old_comp == NULL) {
3251 e_cal_backend_notify_component_created (cal_backend, new_comp);
3253 e_cal_backend_notify_component_modified (cal_backend, old_comp, new_comp);
3256 ccl->slist = g_slist_remove (ccl->slist, old_comp);
3257 g_object_unref (old_comp);
3261 g_object_unref (new_comp);
3267 put_comp_to_cache (ECalBackendCalDAV *cbdav,
3268 icalcomponent *icalcomp,
3272 icalcomponent_kind my_kind;
3273 ECalComponent *comp;
3274 gboolean res = FALSE;
3276 g_return_val_if_fail (cbdav != NULL, FALSE);
3277 g_return_val_if_fail (icalcomp != NULL, FALSE);
3279 my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
3280 comp = e_cal_component_new ();
3282 if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
3283 icalcomponent *subcomp;
3285 /* remove all old components from the cache first */
3286 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
3288 subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
3289 remove_comp_from_cache (cbdav, icalcomponent_get_uid (subcomp), NULL);
3292 /* then put new. It's because some detached instances could be removed on the server. */
3293 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
3295 subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
3296 /* because reusing the same comp doesn't clear recur_id member properly */
3297 g_object_unref (comp);
3298 comp = e_cal_component_new ();
3300 if (e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (subcomp))) {
3302 ecalcomp_set_href (comp, href);
3304 ecalcomp_set_etag (comp, etag);
3306 if (put_component_to_store (cbdav, comp))
3310 } else if (icalcomponent_isa (icalcomp) == my_kind) {
3311 remove_comp_from_cache (cbdav, icalcomponent_get_uid (icalcomp), NULL);
3313 if (e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (icalcomp))) {
3315 ecalcomp_set_href (comp, href);
3317 ecalcomp_set_etag (comp, etag);
3319 res = put_component_to_store (cbdav, comp);
3323 g_object_unref (comp);
3329 remove_property (gpointer prop,
3332 icalcomponent_remove_property (icomp, prop);
3333 icalproperty_free (prop);
3337 strip_unneeded_x_props (icalcomponent *icomp)
3340 GSList *to_remove = NULL;
3342 g_return_if_fail (icomp != NULL);
3343 g_return_if_fail (icalcomponent_isa (icomp) != ICAL_VCALENDAR_COMPONENT);
3345 for (prop = icalcomponent_get_first_property (icomp, ICAL_X_PROPERTY);
3347 prop = icalcomponent_get_next_property (icomp, ICAL_X_PROPERTY)) {
3348 if (g_str_has_prefix (icalproperty_get_x_name (prop), X_E_CALDAV)) {
3349 to_remove = g_slist_prepend (to_remove, prop);
3353 for (prop = icalcomponent_get_first_property (icomp, ICAL_XLICERROR_PROPERTY);
3355 prop = icalcomponent_get_next_property (icomp, ICAL_XLICERROR_PROPERTY)) {
3356 to_remove = g_slist_prepend (to_remove, prop);
3359 g_slist_foreach (to_remove, remove_property, icomp);
3360 g_slist_free (to_remove);
3364 is_stored_on_server (ECalBackendCalDAV *cbdav,
3367 SoupURI *my_uri, *test_uri;
3370 g_return_val_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav), FALSE);
3371 g_return_val_if_fail (cbdav->priv->uri != NULL, FALSE);
3372 g_return_val_if_fail (uri != NULL, FALSE);
3374 my_uri = soup_uri_new (cbdav->priv->uri);
3375 g_return_val_if_fail (my_uri != NULL, FALSE);
3377 test_uri = soup_uri_new (uri);
3379 soup_uri_free (my_uri);
3383 res = my_uri->host && test_uri->host && g_ascii_strcasecmp (my_uri->host, test_uri->host) == 0;
3385 soup_uri_free (my_uri);
3386 soup_uri_free (test_uri);
3392 convert_to_inline_attachment (ECalBackendCalDAV *cbdav,
3393 icalcomponent *icalcomp)
3395 icalcomponent *cclone;
3397 GSList *to_remove = NULL;
3399 g_return_if_fail (icalcomp != NULL);
3401 cclone = icalcomponent_new_clone (icalcomp);
3403 /* Remove local url attachments first */
3404 for (p = icalcomponent_get_first_property (icalcomp, ICAL_ATTACH_PROPERTY);
3406 p = icalcomponent_get_next_property (icalcomp, ICAL_ATTACH_PROPERTY)) {
3409 attach = icalproperty_get_attach ((const icalproperty *) p);
3410 if (icalattach_get_is_url (attach)) {
3413 url = icalattach_get_url (attach);
3414 if (g_str_has_prefix (url, LOCAL_PREFIX))
3415 to_remove = g_slist_prepend (to_remove, p);
3418 g_slist_foreach (to_remove, remove_property, icalcomp);
3419 g_slist_free (to_remove);
3421 /* convert local url attachments to inline attachments now */
3422 for (p = icalcomponent_get_first_property (cclone, ICAL_ATTACH_PROPERTY);
3424 p = icalcomponent_get_next_property (cclone, ICAL_ATTACH_PROPERTY)) {
3427 GError *error = NULL;
3433 attach = icalproperty_get_attach ((const icalproperty *) p);
3434 if (!icalattach_get_is_url (attach))
3437 uri = icalattach_get_url (attach);
3438 if (!g_str_has_prefix (uri, LOCAL_PREFIX))
3441 file = g_file_new_for_uri (uri);
3442 basename = g_file_get_basename (file);
3443 if (g_file_load_contents (file, NULL, &content, &len, NULL, &error)) {
3445 icalparameter *param;
3449 * do a base64 encoding so it can
3450 * be embedded in a soap message
3452 encoded = g_base64_encode ((guchar *) content, len);
3453 attach = icalattach_new_from_data (encoded, NULL, NULL);
3457 prop = icalproperty_new_attach (attach);
3458 icalattach_unref (attach);
3460 param = icalparameter_new_value (ICAL_VALUE_BINARY);
3461 icalproperty_add_parameter (prop, param);
3463 param = icalparameter_new_encoding (ICAL_ENCODING_BASE64);
3464 icalproperty_add_parameter (prop, param);
3466 param = icalparameter_new_x (basename);
3467 icalparameter_set_xname (param, X_E_CALDAV_ATTACHMENT_NAME);
3468 icalproperty_add_parameter (prop, param);
3470 icalcomponent_add_property (icalcomp, prop);
3472 g_warning ("%s\n", error->message);
3473 g_clear_error (&error);
3476 g_object_unref (file);
3478 icalcomponent_free (cclone);
3482 convert_to_url_attachment (ECalBackendCalDAV *cbdav,
3483 icalcomponent *icalcomp)
3485 ECalBackend *backend;
3486 GSList *to_remove = NULL, *to_remove_after_download = NULL;
3487 icalcomponent *cclone;
3491 g_return_if_fail (cbdav != NULL);
3492 g_return_if_fail (icalcomp != NULL);
3494 backend = E_CAL_BACKEND (cbdav);
3495 cclone = icalcomponent_new_clone (icalcomp);
3497 /* Remove all inline attachments first */
3498 for (p = icalcomponent_get_first_property (icalcomp, ICAL_ATTACH_PROPERTY);
3500 p = icalcomponent_get_next_property (icalcomp, ICAL_ATTACH_PROPERTY)) {
3503 attach = icalproperty_get_attach ((const icalproperty *) p);
3504 if (!icalattach_get_is_url (attach))
3505 to_remove = g_slist_prepend (to_remove, p);
3506 else if (is_stored_on_server (cbdav, icalattach_get_url (attach)))
3507 to_remove_after_download = g_slist_prepend (to_remove_after_download, p);
3509 g_slist_foreach (to_remove, remove_property, icalcomp);
3510 g_slist_free (to_remove);
3512 /* convert inline attachments to url attachments now */
3513 for (p = icalcomponent_get_first_property (cclone, ICAL_ATTACH_PROPERTY), fileindex = 0;
3515 p = icalcomponent_get_next_property (cclone, ICAL_ATTACH_PROPERTY), fileindex++) {
3518 gchar *decoded = NULL;
3519 gchar *basename, *local_filename;
3521 attach = icalproperty_get_attach ((const icalproperty *) p);
3522 if (icalattach_get_is_url (attach)) {
3523 const gchar *attach_url = icalattach_get_url (attach);
3524 GError *error = NULL;
3526 if (!is_stored_on_server (cbdav, attach_url))
3529 if (!caldav_server_download_attachment (cbdav, attach_url, &decoded, &len, &error)) {
3530 if (caldav_debug_show (DEBUG_ATTACHMENTS))
3531 g_print ("CalDAV::%s: Failed to download from a server: %s\n", G_STRFUNC, error ? error->message : "Unknown error");
3536 basename = icalproperty_get_parameter_as_string_r (p, X_E_CALDAV_ATTACHMENT_NAME);
3537 local_filename = e_cal_backend_create_cache_filename (backend, icalcomponent_get_uid (icalcomp), basename, fileindex);
3540 if (local_filename) {
3541 GError *error = NULL;
3543 if (decoded == NULL) {
3546 content = (gchar *) icalattach_get_data (attach);
3547 decoded = (gchar *) g_base64_decode (content, &len);
3550 if (g_file_set_contents (local_filename, decoded, len, &error)) {
3554 url = g_filename_to_uri (local_filename, NULL, NULL);
3555 attach = icalattach_new_from_url (url);
3556 prop = icalproperty_new_attach (attach);
3557 icalattach_unref (attach);
3558 icalcomponent_add_property (icalcomp, prop);
3561 g_warning ("%s\n", error->message);
3562 g_clear_error (&error);
3565 g_free (local_filename);
3569 icalcomponent_free (cclone);
3571 g_slist_foreach (to_remove_after_download, remove_property, icalcomp);
3572 g_slist_free (to_remove_after_download);
3576 remove_files (const gchar *dir,
3577 const gchar *fileprefix)
3581 g_return_if_fail (dir != NULL);
3582 g_return_if_fail (fileprefix != NULL);
3583 g_return_if_fail (*fileprefix != '\0');
3585 d = g_dir_open (dir, 0, NULL);
3588 gint len = strlen (fileprefix);
3590 while ((entry = g_dir_read_name (d)) != NULL) {
3591 if (entry && strncmp (entry, fileprefix, len) == 0) {
3594 path = g_build_filename (dir, entry, NULL);
3595 if (!g_file_test (path, G_FILE_TEST_IS_DIR))
3605 remove_cached_attachment (ECalBackendCalDAV *cbdav,
3613 g_return_if_fail (cbdav != NULL);
3614 g_return_if_fail (uid != NULL);
3616 l = e_cal_backend_store_get_components_by_uid (cbdav->priv->store, uid);
3617 len = g_slist_length (l);
3618 g_slist_foreach (l, (GFunc) g_object_unref, NULL);
3623 dir = e_cal_backend_create_cache_filename (E_CAL_BACKEND (cbdav), uid, "a", 0);
3627 fileprefix = g_strrstr (dir, G_DIR_SEPARATOR_S);
3633 fileprefix[strlen (fileprefix) - 1] = '\0';
3635 remove_files (dir, fileprefix);
3641 /* callback for icalcomponent_foreach_tzid */
3643 ECalBackendStore *store;
3644 icalcomponent *vcal_comp;
3645 icalcomponent *icalcomp;
3649 add_timezone_cb (icalparameter *param,
3654 icalcomponent *vtz_comp;
3655 ForeachTzidData *f_data = (ForeachTzidData *) data;
3656 ETimezoneCache *cache;
3658 tzid = icalparameter_get_tzid (param);
3662 tz = icalcomponent_get_timezone (f_data->vcal_comp, tzid);
3666 cache = e_cal_backend_store_ref_timezone_cache (f_data->store);
3668 tz = icalcomponent_get_timezone (f_data->icalcomp, tzid);
3670 tz = icaltimezone_get_builtin_timezone_from_tzid (tzid);
3672 tz = e_timezone_cache_get_timezone (cache, tzid);
3674 vtz_comp = icaltimezone_get_component (tz);
3676 if (tz != NULL && vtz_comp != NULL)
3677 icalcomponent_add_component (
3679 icalcomponent_new_clone (vtz_comp));
3681 g_object_unref (cache);
3685 add_timezones_from_component (ECalBackendCalDAV *cbdav,
3686 icalcomponent *vcal_comp,
3687 icalcomponent *icalcomp)
3689 ForeachTzidData f_data;
3691 g_return_if_fail (cbdav != NULL);
3692 g_return_if_fail (vcal_comp != NULL);
3693 g_return_if_fail (icalcomp != NULL);
3695 f_data.store = cbdav->priv->store;
3696 f_data.vcal_comp = vcal_comp;
3697 f_data.icalcomp = icalcomp;
3699 icalcomponent_foreach_tzid (icalcomp, add_timezone_cb, &f_data);
3702 /* also removes X-EVOLUTION-CALDAV from all the components */
3704 pack_cobj (ECalBackendCalDAV *cbdav,
3705 icalcomponent *icomp)
3707 icalcomponent *calcomp;
3710 if (icalcomponent_isa (icomp) != ICAL_VCALENDAR_COMPONENT) {
3711 icalcomponent *cclone;
3713 calcomp = e_cal_util_new_top_level ();
3715 cclone = icalcomponent_new_clone (icomp);
3716 strip_unneeded_x_props (cclone);
3717 convert_to_inline_attachment (cbdav, cclone);
3718 icalcomponent_add_component (calcomp, cclone);
3719 add_timezones_from_component (cbdav, calcomp, cclone);
3721 icalcomponent *subcomp;
3722 icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
3724 calcomp = icalcomponent_new_clone (icomp);
3725 for (subcomp = icalcomponent_get_first_component (calcomp, my_kind);
3727 subcomp = icalcomponent_get_next_component (calcomp, my_kind)) {
3728 strip_unneeded_x_props (subcomp);
3729 convert_to_inline_attachment (cbdav, subcomp);
3730 add_timezones_from_component (cbdav, calcomp, subcomp);
3734 objstr = icalcomponent_as_ical_string_r (calcomp);
3735 icalcomponent_free (calcomp);
3744 sanitize_component (ECalBackend *cb,
3745 ECalComponent *comp)
3747 ECalComponentDateTime dt;
3750 /* Check dtstart, dtend and due's timezone, and convert it to local
3751 * default timezone if the timezone is not in our builtin timezone
3753 e_cal_component_get_dtstart (comp, &dt);
3754 if (dt.value && dt.tzid) {
3755 zone = e_timezone_cache_get_timezone (
3756 E_TIMEZONE_CACHE (cb), dt.tzid);
3758 g_free ((gchar *) dt.tzid);
3759 dt.tzid = g_strdup ("UTC");
3760 e_cal_component_set_dtstart (comp, &dt);
3763 e_cal_component_free_datetime (&dt);
3765 e_cal_component_get_dtend (comp, &dt);
3766 if (dt.value && dt.tzid) {
3767 zone = e_timezone_cache_get_timezone (
3768 E_TIMEZONE_CACHE (cb), dt.tzid);
3770 g_free ((gchar *) dt.tzid);
3771 dt.tzid = g_strdup ("UTC");
3772 e_cal_component_set_dtend (comp, &dt);
3775 e_cal_component_free_datetime (&dt);
3777 e_cal_component_get_due (comp, &dt);
3778 if (dt.value && dt.tzid) {
3779 zone = e_timezone_cache_get_timezone (
3780 E_TIMEZONE_CACHE (cb), dt.tzid);
3782 g_free ((gchar *) dt.tzid);
3783 dt.tzid = g_strdup ("UTC");
3784 e_cal_component_set_due (comp, &dt);
3787 e_cal_component_free_datetime (&dt);
3788 e_cal_component_abort_sequence (comp);
3792 cache_contains (ECalBackendCalDAV *cbdav,
3797 ECalComponent *comp;
3799 g_return_val_if_fail (cbdav != NULL, FALSE);
3800 g_return_val_if_fail (uid != NULL, FALSE);
3802 g_return_val_if_fail (cbdav->priv->store != NULL, FALSE);
3804 comp = e_cal_backend_store_get_component (cbdav->priv->store, uid, rid);
3808 g_object_unref (comp);
3813 /* Returns subcomponent of icalcomp, which is a master object, or icalcomp itself, if it's not a VCALENDAR;
3814 * Do not free returned pointer, it'll be freed together with the icalcomp.
3816 static icalcomponent *
3817 get_master_comp (ECalBackendCalDAV *cbdav,
3818 icalcomponent *icalcomp)
3820 icalcomponent *master = icalcomp;
3822 g_return_val_if_fail (cbdav != NULL, NULL);
3823 g_return_val_if_fail (icalcomp != NULL, NULL);
3825 if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
3826 icalcomponent *subcomp;
3827 icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
3831 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
3833 subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
3834 struct icaltimetype sub_rid = icalcomponent_get_recurrenceid (subcomp);
3836 if (icaltime_is_null_time (sub_rid)) {
3847 remove_instance (ECalBackendCalDAV *cbdav,
3848 icalcomponent *icalcomp,
3849 struct icaltimetype rid,
3851 gboolean also_exdate)
3853 icalcomponent *master = icalcomp;
3854 gboolean res = FALSE;
3856 g_return_val_if_fail (icalcomp != NULL, res);
3857 g_return_val_if_fail (!icaltime_is_null_time (rid), res);
3859 /* remove an instance only */
3860 if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
3861 icalcomponent *subcomp;
3862 icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
3864 gboolean start_first = FALSE;
3868 /* remove old instance first */
3869 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
3871 subcomp = start_first ? icalcomponent_get_first_component (icalcomp, my_kind) : icalcomponent_get_next_component (icalcomp, my_kind)) {
3872 struct icaltimetype sub_rid = icalcomponent_get_recurrenceid (subcomp);
3874 start_first = FALSE;
3876 if (icaltime_is_null_time (sub_rid)) {
3879 } else if (icaltime_compare (sub_rid, rid) == 0) {
3880 icalcomponent_remove_component (icalcomp, subcomp);
3881 icalcomponent_free (subcomp);
3885 /* either no master or master not as the first component, thus rescan */
3894 /* whether left at least one instance or a master object */
3900 if (master && also_exdate) {
3901 e_cal_util_remove_instances (master, rid, mod);
3907 static icalcomponent *
3908 replace_master (ECalBackendCalDAV *cbdav,
3909 icalcomponent *old_comp,
3910 icalcomponent *new_master)
3912 icalcomponent *old_master;
3913 if (icalcomponent_isa (old_comp) != ICAL_VCALENDAR_COMPONENT) {
3914 icalcomponent_free (old_comp);
3918 old_master = get_master_comp (cbdav, old_comp);
3920 /* no master, strange */
3921 icalcomponent_free (new_master);
3923 icalcomponent_remove_component (old_comp, old_master);
3924 icalcomponent_free (old_master);
3926 icalcomponent_add_component (old_comp, new_master);
3932 /* the resulting component should be unreffed when done with it;
3933 * the fallback_comp is cloned, if used */
3934 static ECalComponent *
3935 get_ecalcomp_master_from_cache_or_fallback (ECalBackendCalDAV *cbdav,
3938 ECalComponent *fallback_comp)
3940 ECalComponent *comp = NULL;
3941 icalcomponent *icalcomp;
3943 g_return_val_if_fail (cbdav != NULL, NULL);
3944 g_return_val_if_fail (uid != NULL, NULL);
3946 icalcomp = get_comp_from_cache (cbdav, uid, rid, NULL, NULL);
3948 icalcomponent *master = get_master_comp (cbdav, icalcomp);
3951 comp = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master));
3954 icalcomponent_free (icalcomp);
3957 if (!comp && fallback_comp)
3958 comp = e_cal_component_clone (fallback_comp);
3963 /* a busy_lock is supposed to be locked already, when calling this function */
3965 do_create_objects (ECalBackendCalDAV *cbdav,
3966 const GSList *in_calobjs,
3968 GSList **new_components,
3969 GCancellable *cancellable,
3972 ECalComponent *comp;
3973 gboolean online, did_put = FALSE;
3974 struct icaltimetype current;
3975 icalcomponent *icalcomp;
3976 const gchar *in_calobj = in_calobjs->data;
3977 const gchar *comp_uid;
3979 if (!check_state (cbdav, &online, perror))
3982 /* We make the assumption that the in_calobjs list we're passed is always exactly one element long, since we haven't specified "bulk-adds"
3983 * in our static capability list. This simplifies a lot of the logic, especially around asynchronous results. */
3984 if (in_calobjs->next != NULL) {
3985 g_propagate_error (perror, e_data_cal_create_error (UnsupportedMethod, _("CalDAV does not support bulk additions")));
3989 comp = e_cal_component_new_from_string (in_calobj);
3992 g_propagate_error (perror, EDC_ERROR (InvalidObject));
3996 icalcomp = e_cal_component_get_icalcomponent (comp);
3997 if (icalcomp == NULL) {
3998 g_object_unref (comp);
3999 g_propagate_error (perror, EDC_ERROR (InvalidObject));
4003 comp_uid = icalcomponent_get_uid (icalcomp);
4007 new_uid = e_cal_component_gen_uid ();
4009 g_object_unref (comp);
4010 g_propagate_error (perror, EDC_ERROR (InvalidObject));
4014 icalcomponent_set_uid (icalcomp, new_uid);
4015 comp_uid = icalcomponent_get_uid (icalcomp);
4020 /* check the object is not in our cache */
4021 if (cache_contains (cbdav, comp_uid, NULL)) {
4022 g_object_unref (comp);
4023 g_propagate_error (perror, EDC_ERROR (ObjectIdAlreadyExists));
4027 /* Set the created and last modified times on the component */
4028 current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
4029 e_cal_component_set_created (comp, ¤t);
4030 e_cal_component_set_last_modified (comp, ¤t);
4032 /* sanitize the component*/
4033 sanitize_component ((ECalBackend *) cbdav, comp);
4036 CalDAVObject object;
4038 object.href = ecalcomp_gen_href (comp);
4040 object.cdata = pack_cobj (cbdav, icalcomp);
4042 did_put = caldav_server_put_object (cbdav, &object, icalcomp, cancellable, perror);
4044 caldav_object_free (&object, FALSE);
4046 /* mark component as out of synch */
4047 /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_CREATED); */
4052 *uids = g_slist_prepend (*uids, g_strdup (comp_uid));
4055 *new_components = g_slist_prepend(*new_components, get_ecalcomp_master_from_cache_or_fallback (cbdav, comp_uid, NULL, comp));
4058 g_object_unref (comp);
4061 /* a busy_lock is supposed to be locked already, when calling this function */
4063 do_modify_objects (ECalBackendCalDAV *cbdav,
4064 const GSList *calobjs,
4066 GSList **old_components,
4067 GSList **new_components,
4068 GCancellable *cancellable,
4071 ECalComponent *comp;
4072 icalcomponent *cache_comp;
4073 gboolean online, did_put = FALSE;
4074 ECalComponentId *id;
4075 struct icaltimetype current;
4076 gchar *href = NULL, *etag = NULL;
4077 const gchar *calobj = calobjs->data;
4080 *new_components = NULL;
4082 if (!check_state (cbdav, &online, error))
4085 /* We make the assumption that the calobjs list we're passed is always exactly one element long, since we haven't specified "bulk-modifies"
4086 * in our static capability list. This simplifies a lot of the logic, especially around asynchronous results. */
4087 if (calobjs->next != NULL) {
4088 g_propagate_error (error, e_data_cal_create_error (UnsupportedMethod, _("CalDAV does not support bulk modifications")));
4092 comp = e_cal_component_new_from_string (calobj);
4095 g_propagate_error (error, EDC_ERROR (InvalidObject));
4099 if (!e_cal_component_get_icalcomponent (comp) ||
4100 icalcomponent_isa (e_cal_component_get_icalcomponent (comp)) != e_cal_backend_get_kind (E_CAL_BACKEND (cbdav))) {
4101 g_object_unref (comp);
4102 g_propagate_error (error, EDC_ERROR (InvalidObject));
4106 /* Set the last modified time on the component */
4107 current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
4108 e_cal_component_set_last_modified (comp, ¤t);
4110 /* sanitize the component */
4111 sanitize_component ((ECalBackend *) cbdav, comp);
4113 id = e_cal_component_get_id (comp);
4115 g_set_error_literal (
4116 error, E_CAL_CLIENT_ERROR,
4117 E_CAL_CLIENT_ERROR_INVALID_OBJECT,
4118 e_cal_client_error_to_string (
4119 E_CAL_CLIENT_ERROR_INVALID_OBJECT));
4123 /* fetch full component from cache, it will be pushed to the server */
4124 cache_comp = get_comp_from_cache (cbdav, id->uid, NULL, &href, &etag);
4126 if (cache_comp == NULL) {
4127 e_cal_component_free_id (id);
4128 g_object_unref (comp);
4131 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
4136 /* mark component as out of synch */
4137 /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_MODIFIED);*/
4140 if (old_components) {
4141 *old_components = NULL;
4143 if (e_cal_component_is_instance (comp)) {
4144 /* set detached instance as the old object, if any */
4145 ECalComponent *old_instance = e_cal_backend_store_get_component (cbdav->priv->store, id->uid, id->rid);
4147 /* This will give a reference to 'old_component' */
4149 *old_components = g_slist_prepend (*old_components, e_cal_component_clone (old_instance));
4150 g_object_unref (old_instance);
4154 if (!*old_components) {
4155 icalcomponent *master = get_master_comp (cbdav, cache_comp);
4158 /* set full component as the old object */
4159 *old_components = g_slist_prepend (*old_components, e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master)));
4165 case E_CAL_OBJ_MOD_ONLY_THIS:
4166 case E_CAL_OBJ_MOD_THIS:
4167 if (e_cal_component_is_instance (comp)) {
4168 icalcomponent *new_comp = e_cal_component_get_icalcomponent (comp);
4170 /* new object is only this instance */
4172 *new_components = g_slist_prepend (*new_components, e_cal_component_clone (comp));
4174 /* add the detached instance */
4175 if (icalcomponent_isa (cache_comp) == ICAL_VCALENDAR_COMPONENT) {
4176 /* do not modify the EXDATE, as the component will be put back */
4177 remove_instance (cbdav, cache_comp, icalcomponent_get_recurrenceid (new_comp), mod, FALSE);
4179 /* this is only a master object, thus make is a VCALENDAR component */
4180 icalcomponent *icomp;
4182 icomp = e_cal_util_new_top_level ();
4183 icalcomponent_add_component (icomp, cache_comp);
4185 /* no need to free the cache_comp, as it is inside icomp */
4189 if (cache_comp && cbdav->priv->is_google) {
4190 icalcomponent_set_sequence (cache_comp, icalcomponent_get_sequence (cache_comp) + 1);
4191 icalcomponent_set_sequence (new_comp, icalcomponent_get_sequence (new_comp) + 1);
4194 /* add the detached instance finally */
4195 icalcomponent_add_component (cache_comp, icalcomponent_new_clone (new_comp));
4197 cache_comp = replace_master (cbdav, cache_comp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
4200 case E_CAL_OBJ_MOD_ALL:
4201 cache_comp = replace_master (cbdav, cache_comp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
4203 case E_CAL_OBJ_MOD_THIS_AND_PRIOR:
4204 case E_CAL_OBJ_MOD_THIS_AND_FUTURE:
4209 CalDAVObject object;
4213 object.cdata = pack_cobj (cbdav, cache_comp);
4215 did_put = caldav_server_put_object (cbdav, &object, cache_comp, cancellable, error);
4217 caldav_object_free (&object, FALSE);
4221 /* mark component as out of synch */
4222 /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_MODIFIED);*/
4226 if (new_components && !*new_components) {
4227 /* read the comp from cache again, as some servers can modify it on put */
4228 *new_components = g_slist_prepend (*new_components, get_ecalcomp_master_from_cache_or_fallback (cbdav, id->uid, id->rid, NULL));
4232 e_cal_component_free_id (id);
4233 icalcomponent_free (cache_comp);
4234 g_object_unref (comp);
4239 /* a busy_lock is supposed to be locked already, when calling this function */
4241 do_remove_objects (ECalBackendCalDAV *cbdav,
4244 GSList **old_components,
4245 GSList **new_components,
4246 GCancellable *cancellable,
4249 icalcomponent *cache_comp;
4251 gchar *href = NULL, *etag = NULL;
4252 const gchar *uid = ((ECalComponentId *) ids->data)->uid;
4253 const gchar *rid = ((ECalComponentId *) ids->data)->rid;
4256 *new_components = NULL;
4258 if (!check_state (cbdav, &online, perror))
4261 /* We make the assumption that the ids list we're passed is always exactly one element long, since we haven't specified "bulk-removes"
4262 * in our static capability list. This simplifies a lot of the logic, especially around asynchronous results. */
4263 if (ids->next != NULL) {
4264 g_propagate_error (perror, e_data_cal_create_error (UnsupportedMethod, _("CalDAV does not support bulk removals")));
4268 cache_comp = get_comp_from_cache (cbdav, uid, NULL, &href, &etag);
4270 if (cache_comp == NULL) {
4271 g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
4275 if (old_components) {
4276 ECalComponent *old = e_cal_backend_store_get_component (cbdav->priv->store, uid, rid);
4279 *old_components = g_slist_prepend (*old_components, e_cal_component_clone (old));
4280 g_object_unref (old);
4282 icalcomponent *master = get_master_comp (cbdav, cache_comp);
4284 *old_components = g_slist_prepend (*old_components, e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master)));
4290 case E_CAL_OBJ_MOD_ONLY_THIS:
4291 case E_CAL_OBJ_MOD_THIS:
4293 /* remove one instance from the component */
4294 if (remove_instance (cbdav, cache_comp, icaltime_from_string (rid), mod, mod != E_CAL_OBJ_MOD_ONLY_THIS)) {
4295 if (new_components) {
4296 icalcomponent *master = get_master_comp (cbdav, cache_comp);
4298 *new_components = g_slist_prepend (*new_components, e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master)));
4302 /* this was the last instance, thus delete whole component */
4304 remove_comp_from_cache (cbdav, uid, NULL);
4307 /* remove whole object */
4308 remove_comp_from_cache (cbdav, uid, NULL);
4311 case E_CAL_OBJ_MOD_ALL:
4312 remove_comp_from_cache (cbdav, uid, NULL);
4314 case E_CAL_OBJ_MOD_THIS_AND_PRIOR:
4315 case E_CAL_OBJ_MOD_THIS_AND_FUTURE:
4320 CalDAVObject caldav_object;
4322 caldav_object.href = href;
4323 caldav_object.etag = etag;
4324 caldav_object.cdata = NULL;
4326 if (mod == E_CAL_OBJ_MOD_THIS && rid && *rid) {
4327 caldav_object.cdata = pack_cobj (cbdav, cache_comp);
4329 caldav_server_put_object (cbdav, &caldav_object, cache_comp, cancellable, perror);
4331 caldav_server_delete_object (cbdav, &caldav_object, cancellable, perror);
4333 caldav_object_free (&caldav_object, FALSE);
4337 /* mark component as out of synch */
4338 /*if (mod == E_CAL_OBJ_MOD_THIS && rid && *rid)
4339 ecalcomp_set_synch_state (cache_comp_master, ECALCOMP_LOCALLY_MODIFIED);
4341 ecalcomp_set_synch_state (cache_comp_master, ECALCOMP_LOCALLY_DELETED);*/
4343 remove_cached_attachment (cbdav, uid);
4345 icalcomponent_free (cache_comp);
4351 extract_objects (icalcomponent *icomp,
4352 icalcomponent_kind ekind,
4356 icalcomponent *scomp;
4357 icalcomponent_kind kind;
4359 kind = icalcomponent_isa (icomp);
4361 if (kind == ekind) {
4362 *objects = g_slist_prepend (NULL, icomp);
4366 if (kind != ICAL_VCALENDAR_COMPONENT) {
4367 g_propagate_error (error, EDC_ERROR (InvalidObject));
4372 scomp = icalcomponent_get_first_component (icomp, ekind);
4375 /* Remove components from toplevel here */
4376 *objects = g_slist_prepend (*objects, scomp);
4377 icalcomponent_remove_component (icomp, scomp);
4379 scomp = icalcomponent_get_next_component (icomp, ekind);
4384 extract_timezones (ECalBackendCalDAV *cbdav,
4385 icalcomponent *icomp)
4387 ETimezoneCache *timezone_cache;
4388 GSList *timezones = NULL, *iter;
4392 g_return_val_if_fail (cbdav != NULL, FALSE);
4393 g_return_val_if_fail (icomp != NULL, FALSE);
4395 timezone_cache = E_TIMEZONE_CACHE (cbdav);
4397 extract_objects (icomp, ICAL_VTIMEZONE_COMPONENT, &timezones, &err);
4403 zone = icaltimezone_new ();
4404 for (iter = timezones; iter; iter = iter->next) {
4405 if (icaltimezone_set_component (zone, iter->data)) {
4406 e_timezone_cache_add_timezone (timezone_cache, zone);
4408 icalcomponent_free (iter->data);
4412 icaltimezone_free (zone, TRUE);
4413 g_slist_free (timezones);
4419 process_object (ECalBackendCalDAV *cbdav,
4420 ECalComponent *ecomp,
4422 icalproperty_method method,
4423 GCancellable *cancellable,
4426 ESourceRegistry *registry;
4427 ECalBackend *backend;
4428 struct icaltimetype now;
4430 gboolean is_declined, is_in_cache;
4432 ECalComponentId *id = e_cal_component_get_id (ecomp);
4435 backend = E_CAL_BACKEND (cbdav);
4438 g_set_error_literal (
4439 error, E_CAL_CLIENT_ERROR,
4440 E_CAL_CLIENT_ERROR_INVALID_OBJECT,
4441 e_cal_client_error_to_string (
4442 E_CAL_CLIENT_ERROR_INVALID_OBJECT));
4446 registry = e_cal_backend_get_registry (E_CAL_BACKEND (cbdav));
4449 now = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
4450 e_cal_component_set_created (ecomp, &now);
4451 e_cal_component_set_last_modified (ecomp, &now);
4453 /* just to check whether component exists in a cache */
4454 is_in_cache = cache_contains (cbdav, id->uid, NULL) || cache_contains (cbdav, id->uid, id->rid);
4456 new_obj_str = e_cal_component_get_as_string (ecomp);
4457 mod = e_cal_component_is_instance (ecomp) ? E_CAL_OBJ_MOD_THIS : E_CAL_OBJ_MOD_ALL;
4460 case ICAL_METHOD_PUBLISH:
4461 case ICAL_METHOD_REQUEST:
4462 case ICAL_METHOD_REPLY:
4463 is_declined = e_cal_backend_user_declined (
4464 registry, e_cal_component_get_icalcomponent (ecomp));
4467 GSList *new_components = NULL, *old_components = NULL;
4468 GSList new_obj_strs = {0,};
4470 new_obj_strs.data = new_obj_str;
4471 do_modify_objects (cbdav, &new_obj_strs, mod,
4472 &old_components, &new_components, cancellable, &err);
4473 if (!err && new_components && new_components->data) {
4474 if (!old_components || !old_components->data) {
4475 e_cal_backend_notify_component_created (backend, new_components->data);
4477 e_cal_backend_notify_component_modified (backend, old_components->data, new_components->data);
4481 e_util_free_nullable_object_slist (old_components);
4482 e_util_free_nullable_object_slist (new_components);
4484 GSList *new_components = NULL, *old_components = NULL;
4488 do_remove_objects (cbdav, &ids, mod, &old_components, &new_components, cancellable, &err);
4489 if (!err && old_components && old_components->data) {
4490 if (new_components && new_components->data) {
4491 e_cal_backend_notify_component_modified (backend, old_components->data, new_components->data);
4493 e_cal_backend_notify_component_removed (backend, id, old_components->data, NULL);
4497 e_util_free_nullable_object_slist (old_components);
4498 e_util_free_nullable_object_slist (new_components);
4500 } else if (!is_declined) {
4501 GSList *new_components = NULL;
4502 GSList new_objs = {0,};
4504 new_objs.data = new_obj_str;
4506 do_create_objects (cbdav, &new_objs, NULL, &new_components, cancellable, &err);
4509 if (new_components && new_components->data)
4510 e_cal_backend_notify_component_created (backend, new_components->data);
4513 e_util_free_nullable_object_slist (new_components);
4516 case ICAL_METHOD_CANCEL:
4518 GSList *new_components = NULL, *old_components = NULL;
4522 do_remove_objects (cbdav, &ids, E_CAL_OBJ_MOD_THIS, &old_components, &new_components, cancellable, &err);
4523 if (!err && old_components && old_components->data) {
4524 if (new_components && new_components->data) {
4525 e_cal_backend_notify_component_modified (backend, old_components->data, new_components->data);
4527 e_cal_backend_notify_component_removed (backend, id, old_components->data, NULL);
4531 e_util_free_nullable_object_slist (old_components);
4532 e_util_free_nullable_object_slist (new_components);
4534 err = EDC_ERROR (ObjectNotFound);
4539 err = EDC_ERROR (UnsupportedMethod);
4543 e_cal_component_free_id (id);
4544 g_free (new_obj_str);
4547 g_propagate_error (error, err);
4551 do_receive_objects (ECalBackendSync *backend,
4553 GCancellable *cancellable,
4554 const gchar *calobj,
4557 ECalBackendCalDAV *cbdav;
4558 icalcomponent *icomp;
4559 icalcomponent_kind kind;
4560 icalproperty_method tmethod;
4562 GSList *objects, *iter;
4565 cbdav = E_CAL_BACKEND_CALDAV (backend);
4567 if (!check_state (cbdav, &online, perror))
4570 icomp = icalparser_parse_string (calobj);
4572 /* Try to parse cal object string */
4573 if (icomp == NULL) {
4574 g_propagate_error (perror, EDC_ERROR (InvalidObject));
4578 kind = e_cal_backend_get_kind (E_CAL_BACKEND (backend));
4579 extract_objects (icomp, kind, &objects, &err);
4582 icalcomponent_free (icomp);
4583 g_propagate_error (perror, err);
4587 /* Extract optional timezone compnents */
4588 extract_timezones (cbdav, icomp);
4590 tmethod = icalcomponent_get_method (icomp);
4592 for (iter = objects; iter && !err; iter = iter->next) {
4593 icalcomponent *scomp;
4594 ECalComponent *ecomp;
4595 icalproperty_method method;
4597 scomp = (icalcomponent *) iter->data;
4598 ecomp = e_cal_component_new ();
4600 e_cal_component_set_icalcomponent (ecomp, scomp);
4602 if (icalcomponent_get_first_property (scomp, ICAL_METHOD_PROPERTY)) {
4603 method = icalcomponent_get_method (scomp);
4608 process_object (cbdav, ecomp, online, method, cancellable, &err);
4609 g_object_unref (ecomp);
4612 g_slist_free (objects);
4614 icalcomponent_free (icomp);
4617 g_propagate_error (perror, err);
4620 #define caldav_busy_stub(_func_name, _params, _call_func, _call_params) \
4622 _func_name _params \
4624 ECalBackendCalDAV *cbdav; \
4625 SlaveCommand old_slave_cmd; \
4626 gboolean was_slave_busy; \
4628 cbdav = E_CAL_BACKEND_CALDAV (backend); \
4630 /* this is done before locking */ \
4631 old_slave_cmd = cbdav->priv->slave_cmd; \
4632 was_slave_busy = cbdav->priv->slave_busy; \
4633 if (was_slave_busy) { \
4634 /* let it pause its work and do our job */ \
4635 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_SLEEP); \
4638 g_mutex_lock (&cbdav->priv->busy_lock); \
4639 _call_func _call_params; \
4641 /* this is done before unlocking */ \
4642 if (was_slave_busy) { \
4643 update_slave_cmd (cbdav->priv, old_slave_cmd); \
4644 g_cond_signal (&cbdav->priv->cond); \
4647 g_mutex_unlock (&cbdav->priv->busy_lock); \
4651 caldav_create_objects,
4652 (ECalBackendSync *backend,
4654 GCancellable *cancellable,
4655 const GSList *in_calobjs,
4657 GSList **new_components,
4668 caldav_modify_objects,
4669 (ECalBackendSync *backend,
4671 GCancellable *cancellable,
4672 const GSList *calobjs,
4674 GSList **old_components,
4675 GSList **new_components,
4687 caldav_remove_objects,
4688 (ECalBackendSync *backend,
4690 GCancellable *cancellable,
4693 GSList **old_components,
4694 GSList **new_components,
4706 caldav_receive_objects,
4707 (ECalBackendSync *backend,
4709 GCancellable *cancellable,
4710 const gchar *calobj,
4720 caldav_send_objects (ECalBackendSync *backend,
4722 GCancellable *cancellable,
4723 const gchar *calobj,
4725 gchar **modified_calobj,
4729 *modified_calobj = g_strdup (calobj);
4733 caldav_get_object (ECalBackendSync *backend,
4735 GCancellable *cancellable,
4741 ECalBackendCalDAV *cbdav;
4742 icalcomponent *icalcomp;
4744 cbdav = E_CAL_BACKEND_CALDAV (backend);
4747 icalcomp = get_comp_from_cache (cbdav, uid, rid, NULL, NULL);
4749 if (!icalcomp && e_backend_get_online (E_BACKEND (backend))) {
4750 /* try to fetch from the server, maybe the event was received only recently */
4751 if (caldav_server_query_for_uid (cbdav, uid, cancellable, NULL)) {
4752 icalcomp = get_comp_from_cache (cbdav, uid, rid, NULL, NULL);
4757 g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
4761 *object = icalcomponent_as_ical_string_r (icalcomp);
4762 icalcomponent_free (icalcomp);
4766 caldav_add_timezone (ECalBackendSync *backend,
4768 GCancellable *cancellable,
4772 ETimezoneCache *timezone_cache;
4773 icalcomponent *tz_comp;
4775 timezone_cache = E_TIMEZONE_CACHE (backend);
4777 tz_comp = icalparser_parse_string (tzobj);
4779 g_propagate_error (error, EDC_ERROR (InvalidObject));
4783 if (icalcomponent_isa (tz_comp) == ICAL_VTIMEZONE_COMPONENT) {
4786 zone = icaltimezone_new ();
4787 icaltimezone_set_component (zone, tz_comp);
4789 e_timezone_cache_add_timezone (timezone_cache, zone);
4791 icaltimezone_free (zone, TRUE);
4793 icalcomponent_free (tz_comp);
4798 caldav_get_object_list (ECalBackendSync *backend,
4800 GCancellable *cancellable,
4801 const gchar *sexp_string,
4805 ECalBackendCalDAV *cbdav;
4806 ECalBackendSExp *sexp;
4807 ETimezoneCache *cache;
4809 GSList *list, *iter;
4810 time_t occur_start = -1, occur_end = -1;
4811 gboolean prunning_by_time;
4813 cbdav = E_CAL_BACKEND_CALDAV (backend);
4815 sexp = e_cal_backend_sexp_new (sexp_string);
4818 g_propagate_error (perror, EDC_ERROR (InvalidQuery));
4822 if (g_str_equal (sexp_string, "#t")) {
4830 prunning_by_time = e_cal_backend_sexp_evaluate_occur_times (sexp, &occur_start, &occur_end);
4832 cache = E_TIMEZONE_CACHE (backend);
4834 list = prunning_by_time ?
4835 e_cal_backend_store_get_components_occuring_in_range (cbdav->priv->store, occur_start, occur_end)
4836 : e_cal_backend_store_get_components (cbdav->priv->store);
4838 for (iter = list; iter; iter = g_slist_next (iter)) {
4839 ECalComponent *comp = E_CAL_COMPONENT (iter->data);
4842 e_cal_backend_sexp_match_comp (sexp, comp, cache)) {
4843 *objects = g_slist_prepend (*objects, e_cal_component_get_as_string (comp));
4846 g_object_unref (comp);
4849 g_object_unref (sexp);
4850 g_slist_free (list);
4854 caldav_start_view (ECalBackend *backend,
4855 EDataCalView *query)
4857 ECalBackendCalDAV *cbdav;
4858 ECalBackendSExp *sexp;
4859 ETimezoneCache *cache;
4861 GSList *list, *iter;
4862 const gchar *sexp_string;
4863 time_t occur_start = -1, occur_end = -1;
4864 gboolean prunning_by_time;
4865 cbdav = E_CAL_BACKEND_CALDAV (backend);
4867 sexp = e_data_cal_view_get_sexp (query);
4868 sexp_string = e_cal_backend_sexp_text (sexp);
4870 if (g_str_equal (sexp_string, "#t")) {
4875 prunning_by_time = e_cal_backend_sexp_evaluate_occur_times (
4880 cache = E_TIMEZONE_CACHE (backend);
4882 list = prunning_by_time ?
4883 e_cal_backend_store_get_components_occuring_in_range (cbdav->priv->store, occur_start, occur_end)
4884 : e_cal_backend_store_get_components (cbdav->priv->store);
4886 for (iter = list; iter; iter = g_slist_next (iter)) {
4887 ECalComponent *comp = E_CAL_COMPONENT (iter->data);
4890 e_cal_backend_sexp_match_comp (sexp, comp, cache)) {
4891 e_data_cal_view_notify_components_added_1 (query, comp);
4894 g_object_unref (comp);
4897 g_slist_free (list);
4899 e_data_cal_view_notify_complete (query, NULL /* Success */);
4903 caldav_get_free_busy (ECalBackendSync *backend,
4905 GCancellable *cancellable,
4906 const GSList *users,
4912 ECalBackendCalDAV *cbdav;
4913 icalcomponent *icalcomp;
4914 ECalComponent *comp;
4915 ECalComponentDateTime dt;
4916 ECalComponentOrganizer organizer = {NULL};
4917 ESourceAuthentication *auth_extension;
4919 struct icaltimetype dtvalue;
4923 GSList *attendees = NULL, *to_free = NULL;
4924 const gchar *extension_name;
4928 cbdav = E_CAL_BACKEND_CALDAV (backend);
4930 if (!cbdav->priv->calendar_schedule) {
4931 g_propagate_error (error, EDC_ERROR_EX (OtherError, _("Calendar doesn't support Free/Busy")));
4935 if (!cbdav->priv->schedule_outbox_url) {
4936 caldav_receive_schedule_outbox_url (cbdav, cancellable, error);
4937 if (!cbdav->priv->schedule_outbox_url) {
4938 cbdav->priv->calendar_schedule = FALSE;
4939 if (error && !*error)
4940 g_propagate_error (error, EDC_ERROR_EX (OtherError, _("Schedule outbox url not found")));
4945 comp = e_cal_component_new ();
4946 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_FREEBUSY);
4948 str = e_cal_component_gen_uid ();
4949 e_cal_component_set_uid (comp, str);
4952 utc = icaltimezone_get_utc_timezone ();
4953 dt.value = &dtvalue;
4954 dt.tzid = icaltimezone_get_tzid (utc);
4956 dtvalue = icaltime_current_time_with_zone (utc);
4957 e_cal_component_set_dtstamp (comp, &dtvalue);
4959 dtvalue = icaltime_from_timet_with_zone (start, FALSE, utc);
4960 e_cal_component_set_dtstart (comp, &dt);
4962 dtvalue = icaltime_from_timet_with_zone (end, FALSE, utc);
4963 e_cal_component_set_dtend (comp, &dt);
4965 usermail = get_usermail (E_CAL_BACKEND (backend));
4966 if (usermail != NULL && *usermail == '\0') {
4971 source = e_backend_get_source (E_BACKEND (backend));
4972 extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
4973 auth_extension = e_source_get_extension (source, extension_name);
4975 if (usermail == NULL)
4976 usermail = e_source_authentication_dup_user (auth_extension);
4978 organizer.value = g_strconcat ("mailto:", usermail, NULL);
4979 e_cal_component_set_organizer (comp, &organizer);
4980 g_free ((gchar *) organizer.value);
4984 for (u = users; u; u = u->next) {
4985 ECalComponentAttendee *ca;
4986 gchar *temp = g_strconcat ("mailto:", (const gchar *) u->data, NULL);
4988 ca = g_new0 (ECalComponentAttendee, 1);
4991 ca->cutype = ICAL_CUTYPE_INDIVIDUAL;
4992 ca->status = ICAL_PARTSTAT_NEEDSACTION;
4993 ca->role = ICAL_ROLE_CHAIR;
4995 to_free = g_slist_prepend (to_free, temp);
4996 attendees = g_slist_append (attendees, ca);
4999 e_cal_component_set_attendee_list (comp, attendees);
5001 g_slist_foreach (attendees, (GFunc) g_free, NULL);
5002 g_slist_free (attendees);
5004 g_slist_foreach (to_free, (GFunc) g_free, NULL);
5005 g_slist_free (to_free);
5007 e_cal_component_abort_sequence (comp);
5009 /* put the free/busy request to a VCALENDAR */
5010 icalcomp = e_cal_util_new_top_level ();
5011 icalcomponent_set_method (icalcomp, ICAL_METHOD_REQUEST);
5012 icalcomponent_add_component (icalcomp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
5014 str = icalcomponent_as_ical_string_r (icalcomp);
5016 icalcomponent_free (icalcomp);
5017 g_object_unref (comp);
5019 caldav_post_freebusy (cbdav, cbdav->priv->schedule_outbox_url, &str, cancellable, &err);
5022 /* parse returned xml */
5025 doc = xmlReadMemory (str, strlen (str), "response.xml", NULL, 0);
5027 xmlXPathContextPtr xpctx;
5028 xmlXPathObjectPtr result;
5030 xpctx = xmlXPathNewContext (doc);
5031 xmlXPathRegisterNs (xpctx, (xmlChar *) "D", (xmlChar *) "DAV:");
5032 xmlXPathRegisterNs (xpctx, (xmlChar *) "C", (xmlChar *) "urn:ietf:params:xml:ns:caldav");
5034 result = xpath_eval (xpctx, "/C:schedule-response/C:response");
5036 if (result == NULL || result->type != XPATH_NODESET) {
5037 err = EDC_ERROR_EX (OtherError, _("Unexpected result in schedule-response"));
5041 n = xmlXPathNodeSetGetLength (result->nodesetval);
5042 for (i = 0; i < n; i++) {
5045 tmp = xp_object_get_string (xpath_eval (xpctx, "string(/C:schedule-response/C:response[%d]/C:calendar-data)", i + 1));
5047 GSList *objects = NULL, *o;
5049 icalcomp = icalparser_parse_string (tmp);
5051 extract_objects (icalcomp, ICAL_VFREEBUSY_COMPONENT, &objects, &err);
5052 if (icalcomp && !err) {
5053 for (o = objects; o; o = o->next) {
5054 gchar *obj_str = icalcomponent_as_ical_string_r (o->data);
5056 if (obj_str && *obj_str)
5057 *freebusy = g_slist_append (*freebusy, obj_str);
5063 g_slist_foreach (objects, (GFunc) icalcomponent_free, NULL);
5064 g_slist_free (objects);
5067 icalcomponent_free (icalcomp);
5078 xmlXPathFreeObject (result);
5079 xmlXPathFreeContext (xpctx);
5087 g_propagate_error (error, err);
5091 caldav_notify_online_cb (ECalBackend *backend,
5094 ECalBackendCalDAV *cbdav;
5097 cbdav = E_CAL_BACKEND_CALDAV (backend);
5099 /*g_mutex_lock (&cbdav->priv->busy_lock);*/
5101 online = e_backend_get_online (E_BACKEND (backend));
5103 if (!cbdav->priv->loaded) {
5104 /*g_mutex_unlock (&cbdav->priv->busy_lock);*/
5109 /* Wake up the slave thread */
5110 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_WORK);
5111 g_cond_signal (&cbdav->priv->cond);
5113 soup_session_abort (cbdav->priv->session);
5114 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_SLEEP);
5117 /*g_mutex_unlock (&cbdav->priv->busy_lock);*/
5121 caldav_source_changed_thread (gpointer data)
5123 ECalBackendCalDAV *cbdav = data;
5124 SlaveCommand old_slave_cmd;
5125 gboolean old_slave_busy;
5127 g_return_val_if_fail (cbdav != NULL, NULL);
5129 old_slave_cmd = cbdav->priv->slave_cmd;
5130 old_slave_busy = cbdav->priv->slave_busy;
5132 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_SLEEP);
5134 g_mutex_lock (&cbdav->priv->busy_lock);
5136 /* guard the call with busy_lock, thus the two threads (this 'source changed'
5137 * thread and the 'backend open' thread) will not clash on internal data
5138 * when they are called in once */
5139 initialize_backend (cbdav, NULL);
5141 /* always wakeup thread, even when it was sleeping */
5142 g_cond_signal (&cbdav->priv->cond);
5145 update_slave_cmd (cbdav->priv, old_slave_cmd);
5147 g_mutex_unlock (&cbdav->priv->busy_lock);
5149 cbdav->priv->updating_source = FALSE;
5151 g_object_unref (cbdav);
5157 caldav_source_changed_cb (ESource *source,
5158 ECalBackendCalDAV *cbdav)
5162 g_return_if_fail (source != NULL);
5163 g_return_if_fail (cbdav != NULL);
5165 if (cbdav->priv->updating_source ||
5166 !cbdav->priv->loaded ||
5167 !e_cal_backend_is_opened (E_CAL_BACKEND (cbdav)))
5170 cbdav->priv->updating_source = TRUE;
5172 thread = g_thread_new (NULL, caldav_source_changed_thread, g_object_ref (cbdav));
5173 g_thread_unref (thread);
5176 static ESourceAuthenticationResult
5177 caldav_try_password_sync (ESourceAuthenticator *authenticator,
5178 const GString *password,
5179 GCancellable *cancellable,
5182 ECalBackendCalDAV *cbdav;
5183 ESourceAuthenticationResult result;
5184 GError *local_error = NULL;
5186 cbdav = E_CAL_BACKEND_CALDAV (authenticator);
5188 /* Busy lock is already acquired by caldav_do_open(). */
5190 if (cbdav->priv->force_ask_password) {
5191 cbdav->priv->force_ask_password = FALSE;
5192 return E_SOURCE_AUTHENTICATION_REJECTED;
5195 g_free (cbdav->priv->password);
5196 cbdav->priv->password = g_strdup (password->str);
5198 open_calendar (cbdav, cancellable, &local_error);
5200 if (local_error == NULL) {
5201 result = E_SOURCE_AUTHENTICATION_ACCEPTED;
5202 } else if (g_error_matches (local_error, E_DATA_CAL_ERROR, AuthenticationFailed)) {
5203 result = E_SOURCE_AUTHENTICATION_REJECTED;
5204 g_clear_error (&local_error);
5206 result = E_SOURCE_AUTHENTICATION_ERROR;
5207 g_propagate_error (error, local_error);
5214 caldav_source_authenticator_init (ESourceAuthenticatorInterface *iface)
5216 iface->try_password_sync = caldav_try_password_sync;
5219 /* ************************************************************************* */
5220 /* ***************************** GObject Foo ******************************* */
5223 e_cal_backend_caldav_dispose (GObject *object)
5225 ECalBackendCalDAVPrivate *priv;
5227 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (object);
5229 g_clear_object (&priv->store);
5230 g_clear_object (&priv->session);
5232 /* Chain up to parent's dispose() method. */
5233 G_OBJECT_CLASS (parent_class)->dispose (object);
5237 e_cal_backend_caldav_finalize (GObject *object)
5239 ECalBackendCalDAVPrivate *priv;
5241 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (object);
5243 g_mutex_clear (&priv->busy_lock);
5244 g_cond_clear (&priv->cond);
5245 g_cond_clear (&priv->slave_gone_cond);
5248 g_free (priv->password);
5249 g_free (priv->schedule_outbox_url);
5251 g_clear_error (&priv->bearer_auth_error);
5252 g_mutex_clear (&priv->bearer_auth_error_lock);
5254 /* Chain up to parent's finalize() method. */
5255 G_OBJECT_CLASS (parent_class)->finalize (object);
5259 cal_backend_caldav_constructed (GObject *object)
5261 /* Chain up to parent's constructed() method. */
5262 G_OBJECT_CLASS (e_cal_backend_caldav_parent_class)->
5263 constructed (object);
5267 e_cal_backend_caldav_init (ECalBackendCalDAV *cbdav)
5269 SoupSessionFeature *feature;
5271 cbdav->priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
5272 cbdav->priv->session = soup_session_sync_new ();
5274 cbdav->priv->session,
5275 SOUP_SESSION_TIMEOUT, 90,
5276 SOUP_SESSION_SSL_STRICT, TRUE,
5277 SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE,
5280 g_object_bind_property (
5281 cbdav, "proxy-resolver",
5282 cbdav->priv->session, "proxy-resolver",
5283 G_BINDING_SYNC_CREATE);
5285 /* XXX SoupAuthManager is public API as of libsoup 2.42, but
5286 * this isn't worth bumping our libsoup requirement over.
5287 * So get the SoupAuthManager GType by its type name. */
5288 feature = soup_session_get_feature (
5289 cbdav->priv->session,
5290 g_type_from_name ("SoupAuthManager"));
5292 /* Add the "Bearer" auth type to support OAuth 2.0. */
5293 soup_session_feature_add_feature (feature, E_TYPE_SOUP_AUTH_BEARER);
5294 g_mutex_init (&cbdav->priv->bearer_auth_error_lock);
5296 if (G_UNLIKELY (caldav_debug_show (DEBUG_MESSAGE)))
5297 caldav_debug_setup (cbdav->priv->session);
5299 cbdav->priv->loaded = FALSE;
5300 cbdav->priv->opened = FALSE;
5302 /* Thinks the 'getctag' extension is available the first time, but unset it when realizes it isn't. */
5303 cbdav->priv->ctag_supported = TRUE;
5304 cbdav->priv->ctag_to_store = NULL;
5306 cbdav->priv->schedule_outbox_url = NULL;
5308 cbdav->priv->is_google = FALSE;
5310 g_mutex_init (&cbdav->priv->busy_lock);
5311 g_cond_init (&cbdav->priv->cond);
5312 g_cond_init (&cbdav->priv->slave_gone_cond);
5314 /* Slave control ... */
5315 cbdav->priv->slave_cmd = SLAVE_SHOULD_SLEEP;
5316 cbdav->priv->slave_busy = FALSE;
5319 cbdav->priv->session, "authenticate",
5320 G_CALLBACK (soup_authenticate), cbdav);
5323 cbdav, "notify::online",
5324 G_CALLBACK (caldav_notify_online_cb), NULL);
5328 e_cal_backend_caldav_class_init (ECalBackendCalDAVClass *class)
5330 GObjectClass *object_class;
5331 ECalBackendClass *backend_class;
5332 ECalBackendSyncClass *sync_class;
5334 object_class = (GObjectClass *) class;
5335 backend_class = (ECalBackendClass *) class;
5336 sync_class = (ECalBackendSyncClass *) class;
5338 caldav_debug_init ();
5340 parent_class = (ECalBackendSyncClass *) g_type_class_peek_parent (class);
5341 g_type_class_add_private (class, sizeof (ECalBackendCalDAVPrivate));
5343 object_class->dispose = e_cal_backend_caldav_dispose;
5344 object_class->finalize = e_cal_backend_caldav_finalize;
5345 object_class->constructed = cal_backend_caldav_constructed;
5347 backend_class->get_backend_property = caldav_get_backend_property;
5348 backend_class->shutdown = caldav_shutdown;
5350 sync_class->open_sync = caldav_do_open;
5351 sync_class->refresh_sync = caldav_refresh;
5353 sync_class->create_objects_sync = caldav_create_objects;
5354 sync_class->modify_objects_sync = caldav_modify_objects;
5355 sync_class->remove_objects_sync = caldav_remove_objects;
5357 sync_class->receive_objects_sync = caldav_receive_objects;
5358 sync_class->send_objects_sync = caldav_send_objects;
5359 sync_class->get_object_sync = caldav_get_object;
5360 sync_class->get_object_list_sync = caldav_get_object_list;
5361 sync_class->add_timezone_sync = caldav_add_timezone;
5362 sync_class->get_free_busy_sync = caldav_get_free_busy;
5364 backend_class->start_view = caldav_start_view;