2 * Evolution calendar - caldav backend
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) version 3.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with the program; if not, see <http://www.gnu.org/licenses/>
19 * Christian Kellner <gicmo@gnome.org>
21 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
28 #include <glib/gstdio.h>
29 #include <glib/gi18n-lib.h>
31 /* LibXML2 includes */
32 #include <libxml/parser.h>
33 #include <libxml/tree.h>
34 #include <libxml/xpath.h>
35 #include <libxml/xpathInternals.h>
37 /* LibSoup includes */
38 #include <libsoup/soup.h>
40 #include "e-cal-backend-caldav.h"
44 #define E_CAL_BACKEND_CALDAV_GET_PRIVATE(obj) \
45 (G_TYPE_INSTANCE_GET_PRIVATE \
46 ((obj), E_TYPE_CAL_BACKEND_CALDAV, ECalBackendCalDAVPrivate))
48 #define CALDAV_CTAG_KEY "CALDAV_CTAG"
49 #define CALDAV_MAX_MULTIGET_AMOUNT 100 /* what's the maximum count of items to fetch within a multiget request */
50 #define LOCAL_PREFIX "file://"
53 #define DEFAULT_REFRESH_TIME 60
55 #define EDC_ERROR(_code) e_data_cal_create_error (_code, NULL)
56 #define EDC_ERROR_EX(_code, _msg) e_data_cal_create_error (_code, _msg)
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 */
93 GTimeVal refresh_time;
95 /* The main soup session */
99 /* well, guess what */
105 /* Authentication info */
107 gboolean auth_required;
112 /* support for 'getctag' extension */
113 gboolean ctag_supported;
114 gchar *ctag_to_store;
116 /* TRUE when 'calendar-schedule' supported on the server */
117 gboolean calendar_schedule;
118 /* with 'calendar-schedule' supported, here's an outbox url
119 * for queries of free/busy information */
120 gchar *schedule_outbox_url;
122 /* "Temporary hack" to indicate it's talking to a google calendar.
123 * The proper solution should be to subclass whole backend and change only
124 * necessary parts in it, but this will give us more freedom, as also direct
125 * caldav calendars can profit from this. */
128 /* set to true if thread for ESource::changed is invoked */
129 gboolean updating_source;
132 /* ************************************************************************* */
135 #define DEBUG_MESSAGE "message"
136 #define DEBUG_MESSAGE_HEADER "message:header"
137 #define DEBUG_MESSAGE_BODY "message:body"
138 #define DEBUG_SERVER_ITEMS "items"
139 #define DEBUG_ATTACHMENTS "attachments"
141 static void convert_to_inline_attachment (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp);
142 static void convert_to_url_attachment (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp);
143 static void remove_cached_attachment (ECalBackendCalDAV *cbdav, const gchar *uid);
145 static gboolean caldav_debug_all = FALSE;
146 static GHashTable *caldav_debug_table = NULL;
149 add_debug_key (const gchar *start,
159 debug_key = debug_value = g_strndup (start, end - start);
161 debug_key = g_strchug (debug_key);
162 debug_key = g_strchomp (debug_key);
164 if (strlen (debug_key) == 0) {
165 g_free (debug_value);
169 g_hash_table_insert (caldav_debug_table,
173 d(g_debug ("Adding %s to enabled debugging keys", debug_key));
177 caldav_debug_init_once (gpointer data)
181 dbg = g_getenv ("CALDAV_DEBUG");
186 d(g_debug ("Got debug env variable: [%s]", dbg));
188 caldav_debug_table = g_hash_table_new (g_str_hash,
193 while (*ptr != '\0') {
194 if (*ptr == ',' || *ptr == ':') {
196 add_debug_key (dbg, ptr);
207 add_debug_key (dbg, ptr);
210 if (g_hash_table_lookup (caldav_debug_table, "all")) {
211 caldav_debug_all = TRUE;
212 g_hash_table_destroy (caldav_debug_table);
213 caldav_debug_table = NULL;
221 caldav_debug_init (void)
223 static GOnce debug_once = G_ONCE_INIT;
226 caldav_debug_init_once,
231 caldav_debug_show (const gchar *component)
233 if (G_UNLIKELY (caldav_debug_all)) {
235 } else if (G_UNLIKELY (caldav_debug_table != NULL) &&
236 g_hash_table_lookup (caldav_debug_table, component)) {
243 #define DEBUG_MAX_BODY_SIZE (100 * 1024 * 1024)
246 caldav_debug_setup (SoupSession *session)
249 SoupLoggerLogLevel level;
251 if (caldav_debug_show (DEBUG_MESSAGE_BODY))
252 level = SOUP_LOGGER_LOG_BODY;
253 else if (caldav_debug_show (DEBUG_MESSAGE_HEADER))
254 level = SOUP_LOGGER_LOG_HEADERS;
256 level = SOUP_LOGGER_LOG_MINIMAL;
258 logger = soup_logger_new (level, DEBUG_MAX_BODY_SIZE);
259 soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
260 g_object_unref (logger);
263 /* TODO Do not replicate this in every backend */
264 static icaltimezone *
265 resolve_tzid (const gchar *tzid,
270 zone = (!strcmp (tzid, "UTC"))
271 ? icaltimezone_get_utc_timezone ()
272 : icaltimezone_get_builtin_timezone_from_tzid (tzid);
275 zone = e_cal_backend_internal_get_timezone (E_CAL_BACKEND (user_data), tzid);
281 put_component_to_store (ECalBackendCalDAV *cbdav,
284 time_t time_start, time_end;
286 e_cal_util_get_component_occur_times (
287 comp, &time_start, &time_end,
288 resolve_tzid, cbdav, icaltimezone_get_utc_timezone (),
289 e_cal_backend_get_kind (E_CAL_BACKEND (cbdav)));
291 return e_cal_backend_store_put_component_with_time_range (
292 cbdav->priv->store, comp, time_start, time_end);
295 static ECalBackendSyncClass *parent_class = NULL;
297 static icaltimezone *caldav_internal_get_timezone (ECalBackend *backend, const gchar *tzid);
298 static void caldav_source_changed_cb (ESource *source, ECalBackendCalDAV *cbdav);
300 static gboolean remove_comp_from_cache (ECalBackendCalDAV *cbdav, const gchar *uid, const gchar *rid);
301 static gboolean put_comp_to_cache (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp, const gchar *href, const gchar *etag);
303 /* ************************************************************************* */
304 /* Misc. utility functions */
307 update_slave_cmd (ECalBackendCalDAVPrivate *priv,
308 SlaveCommand slave_cmd)
310 g_return_if_fail (priv != NULL);
312 if (priv->slave_cmd == SLAVE_SHOULD_DIE)
315 priv->slave_cmd = slave_cmd;
318 #define X_E_CALDAV "X-EVOLUTION-CALDAV-"
319 #define X_E_CALDAV_ATTACHMENT_NAME X_E_CALDAV "ATTACHMENT-NAME"
322 icomp_x_prop_set (icalcomponent *comp,
328 /* Find the old one first */
329 xprop = icalcomponent_get_first_property (comp, ICAL_X_PROPERTY);
332 const gchar *str = icalproperty_get_x_name (xprop);
334 if (!strcmp (str, key)) {
336 icalproperty_set_value_from_string (xprop, value, "NO");
338 icalcomponent_remove_property (comp, xprop);
339 icalproperty_free (xprop);
344 xprop = icalcomponent_get_next_property (comp, ICAL_X_PROPERTY);
347 if (!xprop && value) {
348 xprop = icalproperty_new_x (value);
349 icalproperty_set_x_name (xprop, key);
350 icalcomponent_add_property (comp, xprop);
355 icomp_x_prop_get (icalcomponent *comp,
360 /* Find the old one first */
361 xprop = icalcomponent_get_first_property (comp, ICAL_X_PROPERTY);
364 const gchar *str = icalproperty_get_x_name (xprop);
366 if (!strcmp (str, key)) {
370 xprop = icalcomponent_get_next_property (comp, ICAL_X_PROPERTY);
374 return icalproperty_get_value_as_string_r (xprop);
380 /* passing NULL as 'href' removes the property */
382 ecalcomp_set_href (ECalComponent *comp,
385 icalcomponent *icomp;
387 icomp = e_cal_component_get_icalcomponent (comp);
388 g_return_if_fail (icomp != NULL);
390 icomp_x_prop_set (icomp, X_E_CALDAV "HREF", href);
394 ecalcomp_get_href (ECalComponent *comp)
396 icalcomponent *icomp;
400 icomp = e_cal_component_get_icalcomponent (comp);
401 g_return_val_if_fail (icomp != NULL, NULL);
403 str = icomp_x_prop_get (icomp, X_E_CALDAV "HREF");
408 /* passing NULL as 'etag' removes the property */
410 ecalcomp_set_etag (ECalComponent *comp,
413 icalcomponent *icomp;
415 icomp = e_cal_component_get_icalcomponent (comp);
416 g_return_if_fail (icomp != NULL);
418 icomp_x_prop_set (icomp, X_E_CALDAV "ETAG", etag);
422 ecalcomp_get_etag (ECalComponent *comp)
424 icalcomponent *icomp;
428 icomp = e_cal_component_get_icalcomponent (comp);
429 g_return_val_if_fail (icomp != NULL, NULL);
431 str = icomp_x_prop_get (icomp, X_E_CALDAV "ETAG");
433 /* libical 0.48 escapes quotes, thus unescape them */
434 if (str && strchr (str, '\\')) {
437 for (ii = 0, jj = 0; str[ii]; ii++) {
438 if (str[ii] == '\\') {
456 / * object is in synch,
457 * now isnt that ironic? :) * /
458 ECALCOMP_IN_SYNCH = 0,
460 / * local changes * /
461 ECALCOMP_LOCALLY_CREATED,
462 ECALCOMP_LOCALLY_DELETED,
463 ECALCOMP_LOCALLY_MODIFIED
467 / * oos = out of synch * /
469 ecalcomp_set_synch_state (ECalComponent *comp,
470 * ECalCompSyncState state)
472 icalcomponent *icomp;
475 icomp = e_cal_component_get_icalcomponent (comp);
477 state_string = g_strdup_printf ("%d", state);
479 icomp_x_prop_set (icomp, X_E_CALDAV "ETAG", state_string);
481 g_free (state_string);
485 ecalcomp_gen_href (ECalComponent *comp)
487 gchar *href, *uid, *tmp;
488 icalcomponent *icomp;
490 icomp = e_cal_component_get_icalcomponent (comp);
491 g_return_val_if_fail (icomp != NULL, NULL);
493 uid = g_strdup (icalcomponent_get_uid (icomp));
496 uid = e_cal_component_gen_uid ();
498 tmp = uid ? strchr (uid, '@') : NULL;
504 tmp = isodate_from_time_t (time (NULL));
506 /* quite long, but ensures uniqueness quite well, without using UUIDs */
507 href = g_strconcat (uid ? uid : "no-uid", tmp ? "-" : "", tmp ? tmp : "", ".ics", NULL);
512 icomp_x_prop_set (icomp, X_E_CALDAV "HREF", href);
514 return g_strdelimit (href, " /'\"`&();|<>$%{}!\\:*?#@", '_');
517 /* ensure etag is quoted (to workaround potential server bugs) */
519 quote_etag (const gchar *etag)
523 if (etag && (strlen (etag) < 2 || etag[strlen (etag) - 1] != '\"')) {
524 ret = g_strdup_printf ("\"%s\"", etag);
526 ret = g_strdup (etag);
532 /* ************************************************************************* */
535 status_code_to_result (SoupMessage *message,
536 ECalBackendCalDAV *cbdav,
540 ECalBackendCalDAVPrivate *priv;
542 ESourceWebdav *extension;
543 const gchar *extension_name;
544 gboolean ignore_invalid_cert;
546 g_return_val_if_fail (cbdav != NULL, FALSE);
547 g_return_val_if_fail (message != NULL, FALSE);
551 if (SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
555 source = e_backend_get_source (E_BACKEND (cbdav));
557 extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
558 extension = e_source_get_extension (source, extension_name);
559 ignore_invalid_cert = e_source_webdav_get_ignore_invalid_cert (extension);
561 switch (message->status_code) {
562 case SOUP_STATUS_CANT_CONNECT:
563 case SOUP_STATUS_CANT_CONNECT_PROXY:
564 g_propagate_error (perror,
565 e_data_cal_create_error_fmt (
567 _("Server is unreachable (%s)"),
568 message->reason_phrase && *message->reason_phrase ? message->reason_phrase :
569 (soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : _("Unknown error"))));
571 priv->opened = FALSE;
572 priv->read_only = TRUE;
577 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
579 g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
583 g_propagate_error (perror, EDC_ERROR (AuthenticationFailed));
587 if (priv && priv->auth_required)
588 g_propagate_error (perror, EDC_ERROR (AuthenticationFailed));
590 g_propagate_error (perror, EDC_ERROR (AuthenticationRequired));
593 case SOUP_STATUS_SSL_FAILED:
594 if (ignore_invalid_cert) {
595 g_propagate_error (perror,
596 e_data_cal_create_error_fmt ( OtherError,
597 _("Failed to connect to a server using SSL: %s"),
598 message->reason_phrase && *message->reason_phrase ? message->reason_phrase :
599 (soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : _("Unknown error"))));
601 g_propagate_error (perror, EDC_ERROR_EX (OtherError,
602 _("Failed to connect to a server using SSL. "
603 "One possible reason is an invalid certificate being used by the server. "
604 "If this is expected, like self-signed certificate being used on the server, "
605 "then disable certificate validity tests by selecting 'Ignore invalid SSL certificate' option "
611 d(g_debug ("CalDAV:%s: Unhandled status code %d\n", G_STRFUNC, status_code));
612 g_propagate_error (perror,
613 e_data_cal_create_error_fmt (
615 _("Unexpected HTTP status code %d returned (%s)"),
616 message->status_code,
617 message->reason_phrase && *message->reason_phrase ? message->reason_phrase :
618 (soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : _("Unknown error"))));
625 /* !TS, call with lock held */
627 check_state (ECalBackendCalDAV *cbdav,
633 if (!cbdav->priv->loaded) {
634 g_propagate_error (perror, EDC_ERROR_EX (OtherError, "Not loaded"));
638 if (!e_backend_get_online (E_BACKEND (cbdav))) {
640 if (!cbdav->priv->do_offline) {
641 g_propagate_error (perror, EDC_ERROR (RepositoryOffline));
652 /* ************************************************************************* */
653 /* XML Parsing code */
655 static xmlXPathObjectPtr
656 xpath_eval (xmlXPathContextPtr ctx,
660 xmlXPathObjectPtr result;
668 va_start (args, format);
669 expr = g_strdup_vprintf (format, args);
672 result = xmlXPathEvalExpression ((xmlChar *) expr, ctx);
675 if (result == NULL) {
679 if (result->type == XPATH_NODESET &&
680 xmlXPathNodeSetIsEmpty (result->nodesetval)) {
681 xmlXPathFreeObject (result);
690 parse_status_node (xmlNodePtr node,
696 content = xmlNodeGetContent (node);
698 res = soup_headers_parse_status_line ((gchar *) content,
709 xp_object_get_string (xmlXPathObjectPtr result)
716 if (result->type == XPATH_STRING) {
717 ret = g_strdup ((gchar *) result->stringval);
720 xmlXPathFreeObject (result);
724 /* like get_string but will quote the etag if necessary */
726 xp_object_get_etag (xmlXPathObjectPtr result)
734 if (result->type == XPATH_STRING) {
735 str = (gchar *) result->stringval;
737 ret = quote_etag (str);
740 xmlXPathFreeObject (result);
745 xp_object_get_status (xmlXPathObjectPtr result)
753 if (result->type == XPATH_STRING) {
754 res = soup_headers_parse_status_line ((gchar *) result->stringval,
764 xmlXPathFreeObject (result);
770 xp_object_get_number (xmlXPathObjectPtr result)
777 if (result->type == XPATH_STRING) {
778 ret = result->boolval;
781 xmlXPathFreeObject (result);
786 /*** *** *** *** *** *** */
787 #define XPATH_HREF "string(/D:multistatus/D:response[%d]/D:href)"
788 #define XPATH_STATUS "string(/D:multistatus/D:response[%d]/D:propstat/D:status)"
789 #define XPATH_GETETAG_STATUS "string(/D:multistatus/D:response[%d]/D:propstat/D:prop/D:getetag/../../D:status)"
790 #define XPATH_GETETAG "string(/D:multistatus/D:response[%d]/D:propstat/D:prop/D:getetag)"
791 #define XPATH_CALENDAR_DATA "string(/D:multistatus/D:response[%d]/D:propstat/D:prop/C:calendar-data)"
792 #define XPATH_GETCTAG_STATUS "string(/D:multistatus/D:response/D:propstat/D:prop/CS:getctag/../../D:status)"
793 #define XPATH_GETCTAG "string(/D:multistatus/D:response/D:propstat/D:prop/CS:getctag)"
794 #define XPATH_OWNER_STATUS "string(/D:multistatus/D:response/D:propstat/D:prop/D:owner/D:href/../../../D:status)"
795 #define XPATH_OWNER "string(/D:multistatus/D:response/D:propstat/D:prop/D:owner/D:href)"
796 #define XPATH_SCHEDULE_OUTBOX_URL_STATUS "string(/D:multistatus/D:response/D:propstat/D:prop/C:schedule-outbox-URL/D:href/../../../D:status)"
797 #define XPATH_SCHEDULE_OUTBOX_URL "string(/D:multistatus/D:response/D:propstat/D:prop/C:schedule-outbox-URL/D:href)"
799 typedef struct _CalDAVObject CalDAVObject;
801 struct _CalDAVObject {
812 caldav_object_free (CalDAVObject *object,
813 gboolean free_object_itself)
815 g_free (object->href);
816 g_free (object->etag);
817 g_free (object->cdata);
819 if (free_object_itself) {
825 parse_report_response (SoupMessage *soup_message,
829 xmlXPathContextPtr xpctx;
830 xmlXPathObjectPtr result;
835 g_return_val_if_fail (soup_message != NULL, FALSE);
836 g_return_val_if_fail (objs != NULL || len != NULL, FALSE);
839 doc = xmlReadMemory (soup_message->response_body->data,
840 soup_message->response_body->length,
849 xpctx = xmlXPathNewContext (doc);
851 xmlXPathRegisterNs (xpctx, (xmlChar *) "D",
854 xmlXPathRegisterNs (xpctx, (xmlChar *) "C",
855 (xmlChar *) "urn:ietf:params:xml:ns:caldav");
857 result = xpath_eval (xpctx, "/D:multistatus/D:response");
859 if (result == NULL || result->type != XPATH_NODESET) {
865 n = xmlXPathNodeSetGetLength (result->nodesetval);
868 *objs = g_new0 (CalDAVObject, n);
870 for (i = 0; i < n; i++) {
871 CalDAVObject *object;
872 xmlXPathObjectPtr xpres;
875 /* see if we got a status child in the response element */
877 xpres = xpath_eval (xpctx, XPATH_HREF, i + 1);
878 /* use full path from a href, to let calendar-multiget work properly */
879 object->href = xp_object_get_string (xpres);
881 xpres = xpath_eval (xpctx,XPATH_STATUS , i + 1);
882 object->status = xp_object_get_status (xpres);
884 if (object->status && object->status != 200) {
888 xpres = xpath_eval (xpctx, XPATH_GETETAG_STATUS, i + 1);
889 object->status = xp_object_get_status (xpres);
891 if (object->status != 200) {
895 xpres = xpath_eval (xpctx, XPATH_GETETAG, i + 1);
896 object->etag = xp_object_get_etag (xpres);
898 xpres = xpath_eval (xpctx, XPATH_CALENDAR_DATA, i + 1);
899 object->cdata = xp_object_get_string (xpres);
904 xmlXPathFreeObject (result);
905 xmlXPathFreeContext (xpctx);
910 /* returns whether was able to read the xpath_value from the server's response; *value contains the result */
912 parse_propfind_response (SoupMessage *message,
913 const gchar *xpath_status,
914 const gchar *xpath_value,
917 xmlXPathContextPtr xpctx;
919 gboolean res = FALSE;
921 g_return_val_if_fail (message != NULL, FALSE);
922 g_return_val_if_fail (value != NULL, FALSE);
924 doc = xmlReadMemory (message->response_body->data,
925 message->response_body->length,
934 xpctx = xmlXPathNewContext (doc);
935 xmlXPathRegisterNs (xpctx, (xmlChar *) "D", (xmlChar *) "DAV:");
936 xmlXPathRegisterNs (xpctx, (xmlChar *) "C", (xmlChar *) "urn:ietf:params:xml:ns:caldav");
937 xmlXPathRegisterNs (xpctx, (xmlChar *) "CS", (xmlChar *) "http://calendarserver.org/ns/");
939 if (xpath_status == NULL || xp_object_get_status (xpath_eval (xpctx, xpath_status)) == 200) {
940 gchar *txt = xp_object_get_string (xpath_eval (xpctx, xpath_value));
943 gint len = strlen (txt);
945 if (*txt == '\"' && len > 2 && txt [len - 1] == '\"') {
947 *value = g_strndup (txt + 1, len - 2);
953 res = (*value) != NULL;
959 xmlXPathFreeContext (xpctx);
965 /* ************************************************************************* */
966 /* Authentication helpers for libsoup */
969 soup_authenticate (SoupSession *session,
975 ECalBackendCalDAV *cbdav;
976 ESourceAuthentication *auth_extension;
978 const gchar *extension_name;
980 cbdav = E_CAL_BACKEND_CALDAV (data);
982 source = e_backend_get_source (E_BACKEND (data));
983 extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
984 auth_extension = e_source_get_extension (source, extension_name);
986 /* do not send same password twice, but keep it for later use */
987 if (!retrying && cbdav->priv->password != NULL) {
990 user = e_source_authentication_dup_user (auth_extension);
991 soup_auth_authenticate (auth, user, cbdav->priv->password);
996 /* ************************************************************************* */
997 /* direct CalDAV server access functions */
1000 redirect_handler (SoupMessage *msg,
1003 if (SOUP_STATUS_IS_REDIRECTION (msg->status_code)) {
1004 SoupSession *soup_session = user_data;
1006 const gchar *new_loc;
1008 new_loc = soup_message_headers_get (msg->response_headers, "Location");
1012 new_uri = soup_uri_new_with_base (soup_message_get_uri (msg), new_loc);
1014 soup_message_set_status_full (msg,
1015 SOUP_STATUS_MALFORMED,
1016 "Invalid Redirect URL");
1020 if (new_uri->host && g_str_has_suffix (new_uri->host, "yahoo.com")) {
1021 /* yahoo! returns port 7070, which is unreachable;
1022 * it also requires https being used (below call resets port as well) */
1023 soup_uri_set_scheme (new_uri, SOUP_URI_SCHEME_HTTPS);
1026 soup_message_set_uri (msg, new_uri);
1027 soup_session_requeue_message (soup_session, msg);
1029 soup_uri_free (new_uri);
1034 send_and_handle_redirection (SoupSession *soup_session,
1036 gchar **new_location)
1038 gchar *old_uri = NULL;
1040 g_return_if_fail (msg != NULL);
1043 old_uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
1045 soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT);
1046 soup_message_add_header_handler (msg, "got_body", "Location", G_CALLBACK (redirect_handler), soup_session);
1047 soup_message_headers_append (msg->request_headers, "Connection", "close");
1048 soup_session_send_message (soup_session, msg);
1051 gchar *new_loc = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
1053 if (new_loc && old_uri && !g_str_equal (new_loc, old_uri))
1054 *new_location = new_loc;
1063 caldav_generate_uri (ECalBackendCalDAV *cbdav,
1064 const gchar *target)
1069 slash = strrchr (target, '/');
1073 /* uri *have* trailing slash already */
1074 uri = g_strconcat (cbdav->priv->uri, target, NULL);
1080 caldav_server_open_calendar (ECalBackendCalDAV *cbdav,
1081 gboolean *server_unreachable,
1084 SoupMessage *message;
1085 const gchar *header;
1086 gboolean calendar_access;
1087 gboolean put_allowed;
1088 gboolean delete_allowed;
1090 g_return_val_if_fail (cbdav != NULL, FALSE);
1091 g_return_val_if_fail (server_unreachable != NULL, FALSE);
1093 /* FIXME: setup text_uri */
1095 message = soup_message_new (SOUP_METHOD_OPTIONS, cbdav->priv->uri);
1096 if (message == NULL) {
1097 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
1100 soup_message_headers_append (message->request_headers,
1101 "User-Agent", "Evolution/" VERSION);
1103 send_and_handle_redirection (cbdav->priv->session, message, NULL);
1105 if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1106 switch (message->status_code) {
1107 case SOUP_STATUS_CANT_CONNECT:
1108 case SOUP_STATUS_CANT_CONNECT_PROXY:
1109 *server_unreachable = TRUE;
1113 status_code_to_result (message, cbdav, TRUE, perror);
1115 g_object_unref (message);
1119 /* parse the dav header, we are intreseted in the
1120 * calendar-access bit only at the moment */
1121 header = soup_message_headers_get (message->response_headers, "DAV");
1123 calendar_access = soup_header_contains (header, "calendar-access");
1124 cbdav->priv->calendar_schedule = soup_header_contains (header, "calendar-schedule");
1126 calendar_access = FALSE;
1127 cbdav->priv->calendar_schedule = FALSE;
1130 /* parse the Allow header and look for PUT, DELETE at the
1131 * moment (maybe we should check more here, for REPORT eg) */
1132 header = soup_message_headers_get (message->response_headers, "Allow");
1134 put_allowed = soup_header_contains (header, "PUT");
1135 delete_allowed = soup_header_contains (header, "DELETE");
1137 put_allowed = delete_allowed = FALSE;
1139 g_object_unref (message);
1141 if (calendar_access) {
1142 cbdav->priv->read_only = !(put_allowed && delete_allowed);
1146 g_propagate_error (perror, EDC_ERROR (PermissionDenied));
1151 caldav_unref_thread (gpointer cbdav)
1153 g_object_unref (cbdav);
1159 caldav_unref_in_thread (ECalBackendCalDAV *cbdav)
1163 g_return_if_fail (cbdav != NULL);
1165 thread = g_thread_new (NULL, caldav_unref_thread, cbdav);
1166 g_thread_unref (thread);
1170 caldav_authenticate (ECalBackendCalDAV *cbdav,
1172 GCancellable *cancellable,
1178 g_object_ref (cbdav);
1180 success = e_backend_authenticate_sync (
1182 E_SOURCE_AUTHENTICATOR (cbdav),
1183 cancellable, error);
1186 caldav_unref_in_thread (cbdav);
1191 /* Returns whether calendar changed on the server. This works only when server
1192 * supports 'getctag' extension. */
1194 check_calendar_changed_on_server (ECalBackendCalDAV *cbdav)
1196 xmlOutputBufferPtr buf;
1197 SoupMessage *message;
1199 xmlNodePtr root, node;
1201 gboolean result = TRUE;
1203 g_return_val_if_fail (cbdav != NULL, TRUE);
1205 /* no support for 'getctag', thus update cache */
1206 if (!cbdav->priv->ctag_supported)
1209 /* Prepare the soup message */
1210 message = soup_message_new ("PROPFIND", cbdav->priv->uri);
1211 if (message == NULL)
1214 doc = xmlNewDoc ((xmlChar *) "1.0");
1215 root = xmlNewDocNode (doc, NULL, (xmlChar *) "propfind", NULL);
1216 xmlDocSetRootElement (doc, root);
1217 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", NULL);
1218 ns = xmlNewNs (root, (xmlChar *) "http://calendarserver.org/ns/", (xmlChar *) "CS");
1220 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1221 node = xmlNewTextChild (node, nsdav, (xmlChar *) "getctag", NULL);
1222 xmlSetNs (node, ns);
1224 buf = xmlAllocOutputBuffer (NULL);
1225 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1226 xmlOutputBufferFlush (buf);
1228 soup_message_headers_append (message->request_headers,
1229 "User-Agent", "Evolution/" VERSION);
1230 soup_message_headers_append (message->request_headers,
1233 soup_message_set_request (message,
1236 (gchar *) buf->buffer->content,
1239 /* Send the request now */
1240 send_and_handle_redirection (cbdav->priv->session, message, NULL);
1242 /* Clean up the memory */
1243 xmlOutputBufferClose (buf);
1246 /* Check the result */
1247 if (message->status_code == 401) {
1248 caldav_authenticate (cbdav, TRUE, NULL, NULL);
1249 } else if (message->status_code != 207) {
1250 /* does not support it, but report calendar changed to update cache */
1251 cbdav->priv->ctag_supported = FALSE;
1255 if (parse_propfind_response (message, XPATH_GETCTAG_STATUS, XPATH_GETCTAG, &ctag)) {
1256 const gchar *my_ctag;
1258 my_ctag = e_cal_backend_store_get_key_value (
1259 cbdav->priv->store, CALDAV_CTAG_KEY);
1261 if (ctag && my_ctag && g_str_equal (ctag, my_ctag)) {
1262 /* ctag is same, no change in the calendar */
1265 /* do not store ctag now, do it rather after complete sync */
1266 g_free (cbdav->priv->ctag_to_store);
1267 cbdav->priv->ctag_to_store = ctag;
1273 cbdav->priv->ctag_supported = FALSE;
1277 g_object_unref (message);
1282 /* only_hrefs is a list of requested objects to fetch; it has precedence from
1283 * start_time/end_time, which are used only when both positive.
1284 * Times are supposed to be in UTC, if set.
1287 caldav_server_list_objects (ECalBackendCalDAV *cbdav,
1288 CalDAVObject **objs,
1294 xmlOutputBufferPtr buf;
1295 SoupMessage *message;
1304 /* Allocate the soup message */
1305 message = soup_message_new ("REPORT", cbdav->priv->uri);
1306 if (message == NULL)
1309 /* Maybe we should just do a g_strdup_printf here? */
1310 /* Prepare request body */
1311 doc = xmlNewDoc ((xmlChar *) "1.0");
1313 root = xmlNewDocNode (doc, NULL, (xmlChar *) "calendar-query", NULL);
1315 root = xmlNewDocNode (doc, NULL, (xmlChar *) "calendar-multiget", NULL);
1316 nscd = xmlNewNs (root, (xmlChar *) "urn:ietf:params:xml:ns:caldav", (xmlChar *) "C");
1317 xmlSetNs (root, nscd);
1318 xmlDocSetRootElement (doc, root);
1320 /* Add webdav tags */
1321 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", (xmlChar *) "D");
1322 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1323 xmlNewTextChild (node, nsdav, (xmlChar *) "getetag", NULL);
1327 xmlNewTextChild (node, nscd, (xmlChar *) "calendar-data", NULL);
1328 for (l = only_hrefs; l; l = l->next) {
1330 xmlNewTextChild (root, nsdav, (xmlChar *) "href", (xmlChar *) l->data);
1334 node = xmlNewTextChild (root, nscd, (xmlChar *) "filter", NULL);
1335 node = xmlNewTextChild (node, nscd, (xmlChar *) "comp-filter", NULL);
1336 xmlSetProp (node, (xmlChar *) "name", (xmlChar *) "VCALENDAR");
1338 sn = xmlNewTextChild (node, nscd, (xmlChar *) "comp-filter", NULL);
1339 switch (e_cal_backend_get_kind (E_CAL_BACKEND (cbdav))) {
1341 case ICAL_VEVENT_COMPONENT:
1342 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VEVENT");
1344 case ICAL_VJOURNAL_COMPONENT:
1345 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VJOURNAL");
1347 case ICAL_VTODO_COMPONENT:
1348 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VTODO");
1352 if (start_time > 0 || end_time > 0) {
1355 sn = xmlNewTextChild (sn, nscd, (xmlChar *) "time-range", NULL);
1357 if (start_time > 0) {
1358 tmp = isodate_from_time_t (start_time);
1359 xmlSetProp (sn, (xmlChar *) "start", (xmlChar *) tmp);
1364 tmp = isodate_from_time_t (end_time);
1365 xmlSetProp (sn, (xmlChar *) "end", (xmlChar *) tmp);
1371 buf = xmlAllocOutputBuffer (NULL);
1372 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1373 xmlOutputBufferFlush (buf);
1375 /* Prepare the soup message */
1376 soup_message_headers_append (message->request_headers,
1377 "User-Agent", "Evolution/" VERSION);
1378 soup_message_headers_append (message->request_headers,
1381 soup_message_set_request (message,
1384 (gchar *) buf->buffer->content,
1387 /* Send the request now */
1388 send_and_handle_redirection (cbdav->priv->session, message, NULL);
1390 /* Clean up the memory */
1391 xmlOutputBufferClose (buf);
1394 /* Check the result */
1395 if (message->status_code != 207) {
1396 switch (message->status_code) {
1397 case SOUP_STATUS_CANT_CONNECT:
1398 case SOUP_STATUS_CANT_CONNECT_PROXY:
1399 cbdav->priv->opened = FALSE;
1400 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_SLEEP);
1401 cbdav->priv->read_only = TRUE;
1402 e_cal_backend_notify_readonly (
1403 E_CAL_BACKEND (cbdav), cbdav->priv->read_only);
1406 caldav_authenticate (cbdav, TRUE, NULL, NULL);
1409 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");
1413 g_object_unref (message);
1417 /* Parse the response body */
1418 result = parse_report_response (message, objs, len);
1420 g_object_unref (message);
1425 caldav_server_download_attachment (ECalBackendCalDAV *cbdav,
1426 const gchar *attachment_uri,
1431 SoupMessage *message;
1433 g_return_val_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav), FALSE);
1434 g_return_val_if_fail (attachment_uri != NULL, FALSE);
1435 g_return_val_if_fail (content != NULL, FALSE);
1436 g_return_val_if_fail (len != NULL, FALSE);
1438 message = soup_message_new (SOUP_METHOD_GET, attachment_uri);
1439 if (message == NULL) {
1440 g_propagate_error (error, EDC_ERROR (InvalidObject));
1444 soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1445 send_and_handle_redirection (cbdav->priv->session, message, NULL);
1447 if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1448 status_code_to_result (message, cbdav, FALSE, error);
1450 if (message->status_code == 401)
1451 caldav_authenticate (cbdav, FALSE, NULL, NULL);
1453 g_object_unref (message);
1457 *len = message->response_body->length;
1458 *content = g_memdup (message->response_body->data, *len);
1460 g_object_unref (message);
1466 caldav_server_get_object (ECalBackendCalDAV *cbdav,
1467 CalDAVObject *object,
1470 SoupMessage *message;
1474 g_assert (object != NULL && object->href != NULL);
1476 uri = caldav_generate_uri (cbdav, object->href);
1477 message = soup_message_new (SOUP_METHOD_GET, uri);
1478 if (message == NULL) {
1480 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
1484 soup_message_headers_append (message->request_headers,
1485 "User-Agent", "Evolution/" VERSION);
1487 send_and_handle_redirection (cbdav->priv->session, message, NULL);
1489 if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1490 status_code_to_result (message, cbdav, FALSE, perror);
1492 if (message->status_code == 401)
1493 caldav_authenticate (cbdav, FALSE, NULL, NULL);
1495 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");
1496 g_object_unref (message);
1501 hdr = soup_message_headers_get (message->response_headers, "Content-Type");
1503 if (hdr == NULL || g_ascii_strncasecmp (hdr, "text/calendar", 13)) {
1504 g_propagate_error (perror, EDC_ERROR (InvalidObject));
1505 g_object_unref (message);
1506 g_warning ("Object to fetch '%s' not of type text/calendar", uri);
1511 hdr = soup_message_headers_get (message->response_headers, "ETag");
1514 g_free (object->etag);
1515 object->etag = quote_etag (hdr);
1516 } else if (!object->etag) {
1517 g_warning ("UUHH no ETag, now that's bad! (at '%s')", uri);
1521 g_free (object->cdata);
1522 object->cdata = g_strdup (message->response_body->data);
1524 g_object_unref (message);
1530 caldav_post_freebusy (ECalBackendCalDAV *cbdav,
1535 SoupMessage *message;
1537 e_return_data_cal_error_if_fail (cbdav != NULL, InvalidArg);
1538 e_return_data_cal_error_if_fail (url != NULL, InvalidArg);
1539 e_return_data_cal_error_if_fail (post_fb != NULL, InvalidArg);
1540 e_return_data_cal_error_if_fail (*post_fb != NULL, InvalidArg);
1542 message = soup_message_new (SOUP_METHOD_POST, url);
1543 if (message == NULL) {
1544 g_propagate_error (error, EDC_ERROR (NoSuchCal));
1548 soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1549 soup_message_set_request (message,
1550 "text/calendar; charset=utf-8",
1552 *post_fb, strlen (*post_fb));
1554 send_and_handle_redirection (cbdav->priv->session, message, NULL);
1556 if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1557 status_code_to_result (message, cbdav, FALSE, error);
1558 if (message->status_code == 401)
1559 caldav_authenticate (cbdav, FALSE, NULL, NULL);
1561 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");
1563 g_object_unref (message);
1569 *post_fb = g_strdup (message->response_body->data);
1571 g_object_unref (message);
1575 caldav_gen_file_from_uid_cal (ECalBackendCalDAV *cbdav,
1576 icalcomponent *icalcomp)
1578 icalcomponent_kind my_kind;
1579 const gchar *uid = NULL;
1580 gchar *filename, *res;
1582 g_return_val_if_fail (cbdav != NULL, NULL);
1583 g_return_val_if_fail (icalcomp != NULL, NULL);
1585 my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
1586 if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
1587 icalcomponent *subcomp;
1589 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
1591 subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
1592 uid = icalcomponent_get_uid (subcomp);
1595 } else if (icalcomponent_isa (icalcomp) == my_kind) {
1596 uid = icalcomponent_get_uid (icalcomp);
1602 filename = g_strconcat (uid, ".ics", NULL);
1603 res = soup_uri_encode (filename, NULL);
1610 caldav_server_put_object (ECalBackendCalDAV *cbdav,
1611 CalDAVObject *object,
1612 icalcomponent *icalcomp,
1615 SoupMessage *message;
1621 g_assert (object != NULL && object->cdata != NULL);
1623 uri = caldav_generate_uri (cbdav, object->href);
1624 message = soup_message_new (SOUP_METHOD_PUT, uri);
1626 if (message == NULL) {
1627 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
1631 soup_message_headers_append (message->request_headers,
1632 "User-Agent", "Evolution/" VERSION);
1634 /* For new items we use the If-None-Match so we don't
1635 * acidently override resources, for item updates we
1636 * use the If-Match header to avoid the Lost-update
1638 if (object->etag == NULL) {
1639 soup_message_headers_append (message->request_headers, "If-None-Match", "*");
1641 soup_message_headers_append (message->request_headers,
1642 "If-Match", object->etag);
1645 soup_message_set_request (message,
1646 "text/calendar; charset=utf-8",
1649 strlen (object->cdata));
1652 send_and_handle_redirection (cbdav->priv->session, message, &uri);
1655 gchar *file = strrchr (uri, '/');
1657 /* there was a redirect, update href properly */
1661 g_free (object->href);
1663 decoded = soup_uri_decode (file + 1);
1664 object->href = soup_uri_encode (decoded ? decoded : (file + 1), NULL);
1672 if (status_code_to_result (message, cbdav, FALSE, perror)) {
1673 GError *local_error = NULL;
1675 hdr = soup_message_headers_get (message->response_headers, "ETag");
1677 g_free (object->etag);
1678 object->etag = quote_etag (hdr);
1680 /* no ETag header returned, check for it with a GET */
1681 hdr = soup_message_headers_get (message->response_headers, "Location");
1683 /* reflect possible href change first */
1684 gchar *file = strrchr (hdr, '/');
1689 g_free (object->href);
1691 decoded = soup_uri_decode (file + 1);
1692 object->href = soup_uri_encode (decoded ? decoded : (file + 1), NULL);
1699 if (!caldav_server_get_object (cbdav, object, &local_error)) {
1700 if (g_error_matches (local_error, E_DATA_CAL_ERROR, ObjectNotFound)) {
1703 /* OK, the event was properly created, but cannot be found on the place
1704 * where it was PUT - why didn't server tell us where it saved it? */
1705 g_clear_error (&local_error);
1707 /* try whether it's saved as its UID.ics file */
1708 file = caldav_gen_file_from_uid_cal (cbdav, icalcomp);
1710 g_free (object->href);
1711 object->href = file;
1713 if (!caldav_server_get_object (cbdav, object, &local_error)) {
1714 if (g_error_matches (local_error, E_DATA_CAL_ERROR, ObjectNotFound)) {
1715 g_clear_error (&local_error);
1717 /* not sure what can happen, but do not need to guess for ever,
1718 * thus report success and update the calendar to get fresh info */
1719 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_WORK);
1720 g_cond_signal (cbdav->priv->cond);
1728 icalcomponent *use_comp = NULL;
1730 if (object->cdata) {
1731 /* maybe server also modified component, thus rather store the server's */
1732 use_comp = icalparser_parse_string (object->cdata);
1736 use_comp = icalcomp;
1738 put_comp_to_cache (cbdav, use_comp, object->href, object->etag);
1740 if (use_comp != icalcomp)
1741 icalcomponent_free (use_comp);
1743 g_propagate_error (perror, local_error);
1745 } else if (message->status_code == 401) {
1746 caldav_authenticate (cbdav, FALSE, NULL, NULL);
1749 g_object_unref (message);
1755 caldav_server_delete_object (ECalBackendCalDAV *cbdav,
1756 CalDAVObject *object,
1759 SoupMessage *message;
1762 g_assert (object != NULL && object->href != NULL);
1764 uri = caldav_generate_uri (cbdav, object->href);
1765 message = soup_message_new (SOUP_METHOD_DELETE, uri);
1767 if (message == NULL) {
1768 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
1772 soup_message_headers_append (message->request_headers,
1773 "User-Agent", "Evolution/" VERSION);
1775 if (object->etag != NULL) {
1776 soup_message_headers_append (message->request_headers,
1777 "If-Match", object->etag);
1780 send_and_handle_redirection (cbdav->priv->session, message, NULL);
1782 status_code_to_result (message, cbdav, FALSE, perror);
1784 if (message->status_code == 401)
1785 caldav_authenticate (cbdav, FALSE, NULL, NULL);
1787 g_object_unref (message);
1791 caldav_receive_schedule_outbox_url (ECalBackendCalDAV *cbdav)
1793 SoupMessage *message;
1794 xmlOutputBufferPtr buf;
1796 xmlNodePtr root, node;
1798 gchar *owner = NULL;
1800 g_return_val_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav), FALSE);
1801 g_return_val_if_fail (cbdav->priv->schedule_outbox_url == NULL, TRUE);
1803 /* Prepare the soup message */
1804 message = soup_message_new ("PROPFIND", cbdav->priv->uri);
1805 if (message == NULL)
1808 doc = xmlNewDoc ((xmlChar *) "1.0");
1809 root = xmlNewDocNode (doc, NULL, (xmlChar *) "propfind", NULL);
1810 xmlDocSetRootElement (doc, root);
1811 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", NULL);
1813 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1814 xmlNewTextChild (node, nsdav, (xmlChar *) "owner", NULL);
1816 buf = xmlAllocOutputBuffer (NULL);
1817 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1818 xmlOutputBufferFlush (buf);
1820 soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1821 soup_message_headers_append (message->request_headers, "Depth", "0");
1823 soup_message_set_request (message,
1826 (gchar *) buf->buffer->content,
1829 /* Send the request now */
1830 send_and_handle_redirection (cbdav->priv->session, message, NULL);
1832 /* Clean up the memory */
1833 xmlOutputBufferClose (buf);
1836 /* Check the result */
1837 if (message->status_code == 207 && parse_propfind_response (message, XPATH_OWNER_STATUS, XPATH_OWNER, &owner) && owner && *owner) {
1841 g_object_unref (message);
1843 /* owner is a full path to the user's URL, thus change it in
1844 * calendar's uri when asking for schedule-outbox-URL */
1845 suri = soup_uri_new (cbdav->priv->uri);
1846 soup_uri_set_path (suri, owner);
1848 owner = soup_uri_to_string (suri, FALSE);
1849 soup_uri_free (suri);
1851 message = soup_message_new ("PROPFIND", owner);
1852 if (message == NULL) {
1857 doc = xmlNewDoc ((xmlChar *) "1.0");
1858 root = xmlNewDocNode (doc, NULL, (xmlChar *) "propfind", NULL);
1859 xmlDocSetRootElement (doc, root);
1860 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", NULL);
1861 nscd = xmlNewNs (root, (xmlChar *) "urn:ietf:params:xml:ns:caldav", (xmlChar *) "C");
1863 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1864 xmlNewTextChild (node, nscd, (xmlChar *) "schedule-outbox-URL", NULL);
1866 buf = xmlAllocOutputBuffer (NULL);
1867 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1868 xmlOutputBufferFlush (buf);
1870 soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1871 soup_message_headers_append (message->request_headers, "Depth", "0");
1873 soup_message_set_request (message,
1876 (gchar *) buf->buffer->content,
1879 /* Send the request now */
1880 send_and_handle_redirection (cbdav->priv->session, message, NULL);
1882 if (message->status_code == 207 && parse_propfind_response (message, XPATH_SCHEDULE_OUTBOX_URL_STATUS, XPATH_SCHEDULE_OUTBOX_URL, &cbdav->priv->schedule_outbox_url)) {
1883 if (!*cbdav->priv->schedule_outbox_url) {
1884 g_free (cbdav->priv->schedule_outbox_url);
1885 cbdav->priv->schedule_outbox_url = NULL;
1887 /* make it a full URI */
1888 suri = soup_uri_new (cbdav->priv->uri);
1889 soup_uri_set_path (suri, cbdav->priv->schedule_outbox_url);
1890 g_free (cbdav->priv->schedule_outbox_url);
1891 cbdav->priv->schedule_outbox_url = soup_uri_to_string (suri, FALSE);
1892 soup_uri_free (suri);
1896 /* Clean up the memory */
1897 xmlOutputBufferClose (buf);
1899 } else if (message->status_code == 401) {
1900 caldav_authenticate (cbdav, FALSE, NULL, NULL);
1904 g_object_unref (message);
1908 return cbdav->priv->schedule_outbox_url != NULL;
1911 /* ************************************************************************* */
1912 /* Synchronization foo */
1914 static gboolean extract_timezones (ECalBackendCalDAV *cbdav, icalcomponent *icomp);
1916 struct cache_comp_list
1922 remove_complist_from_cache_and_notify_cb (gpointer key,
1927 struct cache_comp_list *ccl = value;
1928 ECalBackendCalDAV *cbdav = data;
1930 for (l = ccl->slist; l; l = l->next) {
1931 ECalComponent *old_comp = l->data;
1932 ECalComponentId *id;
1934 id = e_cal_component_get_id (old_comp);
1939 if (e_cal_backend_store_remove_component (cbdav->priv->store, id->uid, id->rid)) {
1940 e_cal_backend_notify_component_removed ((ECalBackend *) cbdav, id, old_comp, NULL);
1943 e_cal_component_free_id (id);
1945 remove_cached_attachment (cbdav, (const gchar *) key);
1951 free_comp_list (gpointer cclist)
1953 struct cache_comp_list *ccl = cclist;
1955 g_return_if_fail (ccl != NULL);
1957 g_slist_foreach (ccl->slist, (GFunc) g_object_unref, NULL);
1958 g_slist_free (ccl->slist);
1962 #define etags_match(_tag1, _tag2) ((_tag1 == _tag2) ? TRUE : \
1963 g_str_equal (_tag1 != NULL ? _tag1 : "", \
1964 _tag2 != NULL ? _tag2 : ""))
1966 /* start_time/end_time is an interval for checking changes. If both greater than zero,
1967 * only the interval is checked and the removed items are not notified, as they can
1971 synchronize_cache (ECalBackendCalDAV *cbdav,
1976 CalDAVObject *sobjs, *object;
1977 GSList *c_objs, *c_iter; /* list of all items known from our cache */
1978 GTree *c_uid2complist; /* cache components list (with detached instances) sorted by (master's) uid */
1979 GHashTable *c_href2uid; /* connection between href and a (master's) uid */
1980 GSList *hrefs_to_update, *htu; /* list of href-s to update */
1983 if (!check_calendar_changed_on_server (cbdav)) {
1984 /* no changes on the server, no update required */
1988 bkend = E_CAL_BACKEND (cbdav);
1992 /* get list of server objects */
1993 if (!caldav_server_list_objects (cbdav, &sobjs, &len, NULL, start_time, end_time))
1996 c_objs = e_cal_backend_store_get_components (cbdav->priv->store);
1998 if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
1999 printf ("CalDAV - found %d objects on the server, locally stored %d objects\n", len, g_slist_length (c_objs)); fflush (stdout);
2002 /* do not store changes in cache immediately - makes things significantly quicker */
2003 e_cal_backend_store_freeze_changes (cbdav->priv->store);
2005 c_uid2complist = g_tree_new_full ((GCompareDataFunc) g_strcmp0, NULL, g_free, free_comp_list);
2006 c_href2uid = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
2008 /* fill indexed hash and tree with cached components */
2009 for (c_iter = c_objs; c_iter; c_iter = g_slist_next (c_iter)) {
2010 ECalComponent *ccomp = E_CAL_COMPONENT (c_iter->data);
2011 const gchar *uid = NULL;
2012 struct cache_comp_list *ccl;
2015 e_cal_component_get_uid (ccomp, &uid);
2017 g_warning ("broken component with NULL Id");
2021 href = ecalcomp_get_href (ccomp);
2024 g_warning ("href of object NULL :(");
2028 ccl = g_tree_lookup (c_uid2complist, uid);
2030 ccl->slist = g_slist_prepend (ccl->slist, g_object_ref (ccomp));
2032 ccl = g_new0 (struct cache_comp_list, 1);
2033 ccl->slist = g_slist_append (NULL, g_object_ref (ccomp));
2035 /* make a copy, which will be used in the c_href2uid too */
2036 uid = g_strdup (uid);
2038 g_tree_insert (c_uid2complist, (gpointer) uid, ccl);
2041 if (g_hash_table_lookup (c_href2uid, href) == NULL) {
2042 /* uid is from a component or c_uid2complist key, thus will not be
2043 * freed before a removal from c_uid2complist, thus do not duplicate it,
2044 * rather save memory */
2045 g_hash_table_insert (c_href2uid, href, (gpointer) uid);
2051 /* clear it now, we do not need it later */
2052 g_slist_foreach (c_objs, (GFunc) g_object_unref, NULL);
2053 g_slist_free (c_objs);
2056 hrefs_to_update = NULL;
2058 /* see if we have to update or add some objects */
2059 for (i = 0, object = sobjs; i < len && cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK; i++, object++) {
2060 ECalComponent *ccomp = NULL;
2063 struct cache_comp_list *ccl;
2065 if (object->status != 200) {
2066 /* just continue here, so that the object
2067 * doesnt get removed from the cobjs list
2068 * - therefore it will be removed */
2072 uid = g_hash_table_lookup (c_href2uid, object->href);
2074 ccl = g_tree_lookup (c_uid2complist, uid);
2077 for (sl = ccl->slist; sl && !etag; sl = sl->next) {
2080 etag = ecalcomp_get_etag (ccomp);
2088 if (!etag || !etags_match (etag, object->etag)) {
2089 hrefs_to_update = g_slist_prepend (hrefs_to_update, object->href);
2090 } else if (uid && ccl) {
2091 /* all components cover by this uid are up-to-date */
2094 for (p = ccl->slist; p; p = p->next) {
2095 g_object_unref (p->data);
2098 g_slist_free (ccl->slist);
2105 /* free hash table, as it is not used anymore */
2106 g_hash_table_destroy (c_href2uid);
2109 if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2110 printf ("CalDAV - recognized %d items to update\n", g_slist_length (hrefs_to_update)); fflush (stdout);
2113 htu = hrefs_to_update;
2114 while (htu && cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK) {
2116 GSList *to_fetch = NULL;
2118 while (count < CALDAV_MAX_MULTIGET_AMOUNT && htu) {
2119 to_fetch = g_slist_prepend (to_fetch, htu->data);
2124 if (to_fetch && cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK) {
2125 CalDAVObject *up_sobjs = NULL;
2127 if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2128 printf ("CalDAV - going to fetch %d items\n", g_slist_length (to_fetch)); fflush (stdout);
2132 if (!caldav_server_list_objects (cbdav, &up_sobjs, &count, to_fetch, 0, 0)) {
2133 fprintf (stderr, "CalDAV - failed to retrieve bunch of items\n"); fflush (stderr);
2137 if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2138 printf ("CalDAV - fetched bunch of %d items\n", count); fflush (stdout);
2141 /* we are going to update cache */
2142 /* they are downloaded, so process them */
2143 for (i = 0, object = up_sobjs; i < count /*&& cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK */; i++, object++) {
2144 if (object->status == 200 && object->href && object->etag && object->cdata && *object->cdata) {
2145 icalcomponent *icomp = icalparser_parse_string (object->cdata);
2148 icalcomponent_kind kind = icalcomponent_isa (icomp);
2150 extract_timezones (cbdav, icomp);
2152 if (kind == ICAL_VCALENDAR_COMPONENT) {
2153 icalcomponent *subcomp;
2155 kind = e_cal_backend_get_kind (bkend);
2157 for (subcomp = icalcomponent_get_first_component (icomp, kind);
2159 subcomp = icalcomponent_get_next_component (icomp, kind)) {
2160 ECalComponent *new_comp, *old_comp;
2162 convert_to_url_attachment (cbdav, subcomp);
2163 new_comp = e_cal_component_new ();
2164 if (e_cal_component_set_icalcomponent (new_comp, icalcomponent_new_clone (subcomp))) {
2165 const gchar *uid = NULL;
2166 struct cache_comp_list *ccl;
2168 e_cal_component_get_uid (new_comp, &uid);
2170 g_warning ("%s: no UID on component!", G_STRFUNC);
2171 g_object_unref (new_comp);
2175 ecalcomp_set_href (new_comp, object->href);
2176 ecalcomp_set_etag (new_comp, object->etag);
2179 ccl = g_tree_lookup (c_uid2complist, uid);
2181 gchar *nc_rid = e_cal_component_get_recurid_as_string (new_comp);
2184 for (p = ccl->slist; p && !old_comp; p = p->next) {
2189 oc_rid = e_cal_component_get_recurid_as_string (old_comp);
2190 if (g_strcmp0 (nc_rid, oc_rid) != 0) {
2200 put_component_to_store (cbdav, new_comp);
2202 if (old_comp == NULL) {
2203 e_cal_backend_notify_component_created (bkend, new_comp);
2205 e_cal_backend_notify_component_modified (bkend, old_comp, new_comp);
2207 ccl->slist = g_slist_remove (ccl->slist, old_comp);
2208 g_object_unref (old_comp);
2212 g_object_unref (new_comp);
2216 icalcomponent_free (icomp);
2220 /* these free immediately */
2221 caldav_object_free (object, FALSE);
2224 /* cache update done for fetched items */
2228 /* do not free 'data' itself, it's part of 'sobjs' */
2229 g_slist_free (to_fetch);
2232 /* if not interrupted and not using the time range... */
2233 if (cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK && (!start_time || !end_time)) {
2234 /* ...remove old (not on server anymore) items from our cache and notify of a removal */
2235 g_tree_foreach (c_uid2complist, remove_complist_from_cache_and_notify_cb, cbdav);
2238 if (cbdav->priv->ctag_to_store) {
2239 /* store only when wasn't interrupted */
2240 if (cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK && start_time == 0 && end_time == 0) {
2241 e_cal_backend_store_put_key_value (cbdav->priv->store, CALDAV_CTAG_KEY, cbdav->priv->ctag_to_store);
2244 g_free (cbdav->priv->ctag_to_store);
2245 cbdav->priv->ctag_to_store = NULL;
2248 /* save cache changes to disk finally */
2249 e_cal_backend_store_thaw_changes (cbdav->priv->store);
2251 for (i = 0, object = sobjs; i < len; i++, object++) {
2252 caldav_object_free (object, FALSE);
2255 g_tree_destroy (c_uid2complist);
2256 g_slist_free (hrefs_to_update);
2261 is_google_uri (const gchar *uri)
2266 g_return_val_if_fail (uri != NULL, FALSE);
2268 suri = soup_uri_new (uri);
2269 g_return_val_if_fail (suri != NULL, FALSE);
2271 res = suri->host && g_ascii_strcasecmp (suri->host, "www.google.com") == 0;
2273 soup_uri_free (suri);
2278 /* ************************************************************************* */
2281 caldav_synch_slave_loop (gpointer data)
2283 ECalBackendCalDAV *cbdav;
2285 icaltimezone *utc = icaltimezone_get_utc_timezone ();
2286 gboolean know_unreachable;
2288 cbdav = E_CAL_BACKEND_CALDAV (data);
2290 g_mutex_lock (cbdav->priv->busy_lock);
2292 know_unreachable = !cbdav->priv->opened;
2294 while (cbdav->priv->slave_cmd != SLAVE_SHOULD_DIE) {
2295 GTimeVal alarm_clock;
2296 if (cbdav->priv->slave_cmd == SLAVE_SHOULD_SLEEP) {
2297 /* just sleep until we get woken up again */
2298 g_cond_wait (cbdav->priv->cond, cbdav->priv->busy_lock);
2300 /* check if we should die, work or sleep again */
2304 /* Ok here we go, do some real work
2305 * Synch it baby one more time ...
2307 cbdav->priv->slave_busy = TRUE;
2309 if (!cbdav->priv->opened) {
2310 gboolean server_unreachable = FALSE;
2311 GError *local_error = NULL;
2314 if (caldav_server_open_calendar (cbdav, &server_unreachable, &local_error)) {
2315 cbdav->priv->opened = TRUE;
2316 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_WORK);
2317 g_cond_signal (cbdav->priv->cond);
2319 cbdav->priv->is_google = is_google_uri (cbdav->priv->uri);
2320 know_unreachable = FALSE;
2321 } else if (local_error) {
2322 cbdav->priv->opened = FALSE;
2323 cbdav->priv->read_only = TRUE;
2325 if (!know_unreachable) {
2328 know_unreachable = TRUE;
2330 msg = g_strdup_printf (_("Server is unreachable, calendar is opened in read-only mode.\nError message: %s"), local_error->message);
2331 e_cal_backend_notify_error (E_CAL_BACKEND (cbdav), msg);
2335 g_clear_error (&local_error);
2337 cbdav->priv->opened = FALSE;
2338 cbdav->priv->read_only = TRUE;
2339 know_unreachable = TRUE;
2342 e_cal_backend_notify_readonly (E_CAL_BACKEND (cbdav), cbdav->priv->read_only);
2344 online = e_backend_get_online (E_BACKEND (cbdav));
2345 e_cal_backend_notify_online (E_CAL_BACKEND (cbdav), online);
2348 if (cbdav->priv->opened) {
2350 /* check for events in the month before/after today first,
2351 * to show user actual data as soon as possible */
2352 synchronize_cache (cbdav, time_add_week_with_zone (now, -5, utc), time_add_week_with_zone (now, +5, utc));
2354 if (cbdav->priv->slave_cmd != SLAVE_SHOULD_SLEEP) {
2355 /* and then check for changes in a whole calendar */
2356 synchronize_cache (cbdav, 0, 0);
2359 if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2362 c_objs = e_cal_backend_store_get_components (cbdav->priv->store);
2364 printf ("CalDAV - finished syncing with %d items in a cache\n", g_slist_length (c_objs)); fflush (stdout);
2366 g_slist_foreach (c_objs, (GFunc) g_object_unref, NULL);
2367 g_slist_free (c_objs);
2371 cbdav->priv->slave_busy = FALSE;
2373 /* puhh that was hard, get some rest :) */
2374 g_get_current_time (&alarm_clock);
2375 alarm_clock.tv_sec += cbdav->priv->refresh_time.tv_sec;
2376 g_cond_timed_wait (cbdav->priv->cond,
2377 cbdav->priv->busy_lock,
2382 /* signal we are done */
2383 g_cond_signal (cbdav->priv->slave_gone_cond);
2385 cbdav->priv->synch_slave = NULL;
2387 /* we got killed ... */
2388 g_mutex_unlock (cbdav->priv->busy_lock);
2393 maybe_append_email_domain (const gchar *username,
2394 const gchar *may_append)
2396 if (!username || !*username)
2399 if (strchr (username, '@'))
2400 return g_strdup (username);
2402 return g_strconcat (username, may_append, NULL);
2406 get_usermail (ECalBackend *backend)
2408 ECalBackendCalDAV *cbdav;
2410 ESourceAuthentication *auth_extension;
2411 ESourceWebdav *webdav_extension;
2412 const gchar *extension_name;
2417 g_return_val_if_fail (backend != NULL, NULL);
2419 source = e_backend_get_source (E_BACKEND (backend));
2421 extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
2422 webdav_extension = e_source_get_extension (source, extension_name);
2424 /* This will never return an empty string. */
2425 usermail = e_source_webdav_dup_email_address (webdav_extension);
2427 if (usermail != NULL)
2430 cbdav = E_CAL_BACKEND_CALDAV (backend);
2432 extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
2433 auth_extension = e_source_get_extension (source, extension_name);
2434 username = e_source_authentication_dup_user (auth_extension);
2436 if (cbdav->priv && cbdav->priv->is_google)
2437 res = maybe_append_email_domain (username, "@gmail.com");
2444 /* ************************************************************************* */
2445 /* ********** ECalBackendSync virtual function implementation ************* */
2448 caldav_get_backend_property (ECalBackendSync *backend,
2450 GCancellable *cancellable,
2451 const gchar *prop_name,
2455 gboolean processed = TRUE;
2457 g_return_val_if_fail (prop_name != NULL, FALSE);
2458 g_return_val_if_fail (prop_value != NULL, FALSE);
2460 if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
2461 ESourceWebdav *extension;
2465 const gchar *extension_name;
2467 caps = g_string_new (CAL_STATIC_CAPABILITY_NO_THISANDFUTURE ","
2468 CAL_STATIC_CAPABILITY_NO_THISANDPRIOR ","
2469 CAL_STATIC_CAPABILITY_REFRESH_SUPPORTED);
2471 usermail = get_usermail (E_CAL_BACKEND (backend));
2472 if (!usermail || !*usermail)
2473 g_string_append (caps, "," CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS);
2476 source = e_backend_get_source (E_BACKEND (backend));
2478 extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
2479 extension = e_source_get_extension (source, extension_name);
2481 if (e_source_webdav_get_calendar_auto_schedule (extension)) {
2482 g_string_append (caps, "," CAL_STATIC_CAPABILITY_CREATE_MESSAGES
2483 "," CAL_STATIC_CAPABILITY_SAVE_SCHEDULES);
2486 *prop_value = g_string_free (caps, FALSE);
2487 } else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS) ||
2488 g_str_equal (prop_name, CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS)) {
2489 *prop_value = get_usermail (E_CAL_BACKEND (backend));
2490 } else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_DEFAULT_OBJECT)) {
2491 ECalComponent *comp;
2493 comp = e_cal_component_new ();
2495 switch (e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
2496 case ICAL_VEVENT_COMPONENT:
2497 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
2499 case ICAL_VTODO_COMPONENT:
2500 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
2502 case ICAL_VJOURNAL_COMPONENT:
2503 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
2506 g_object_unref (comp);
2507 g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
2511 *prop_value = e_cal_component_get_as_string (comp);
2512 g_object_unref (comp);
2521 initialize_backend (ECalBackendCalDAV *cbdav,
2524 ESourceAuthentication *auth_extension;
2525 ESourceOffline *offline_extension;
2526 ESourceRefresh *refresh_extension;
2527 ESourceWebdav *webdav_extension;
2528 ECalBackend *backend;
2532 const gchar *cache_dir;
2533 const gchar *extension_name;
2534 guint interval_in_minutes;
2536 backend = E_CAL_BACKEND (cbdav);
2537 cache_dir = e_cal_backend_get_cache_dir (backend);
2538 source = e_backend_get_source (E_BACKEND (backend));
2540 extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
2541 auth_extension = e_source_get_extension (source, extension_name);
2543 extension_name = E_SOURCE_EXTENSION_OFFLINE;
2544 offline_extension = e_source_get_extension (source, extension_name);
2546 extension_name = E_SOURCE_EXTENSION_REFRESH;
2547 refresh_extension = e_source_get_extension (source, extension_name);
2549 extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
2550 webdav_extension = e_source_get_extension (source, extension_name);
2552 if (!g_signal_handler_find (G_OBJECT (source), G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, caldav_source_changed_cb, cbdav))
2553 g_signal_connect (G_OBJECT (source), "changed", G_CALLBACK (caldav_source_changed_cb), cbdav);
2555 cbdav->priv->do_offline = e_source_offline_get_stay_synchronized (offline_extension);
2557 cbdav->priv->auth_required = e_source_authentication_required (auth_extension);
2559 soup_uri = e_source_webdav_dup_soup_uri (webdav_extension);
2561 /* properly encode uri */
2562 if (soup_uri != NULL && soup_uri->path != NULL) {
2565 if (strchr (soup_uri->path, '%')) {
2566 /* If path contains anything already encoded, then
2567 * decode it first, thus it'll be managed properly.
2568 * For example, the '#' in a path is in URI shown as
2569 * %23 and not doing this decode makes it being like
2570 * %2523, which is not what is wanted here. */
2571 tmp = soup_uri_decode (soup_uri->path);
2572 soup_uri_set_path (soup_uri, tmp);
2576 tmp = soup_uri_encode (soup_uri->path, NULL);
2577 path = soup_uri_normalize (tmp, "/");
2579 soup_uri_set_path (soup_uri, path);
2585 g_free (cbdav->priv->uri);
2586 cbdav->priv->uri = soup_uri_to_string (soup_uri, FALSE);
2588 soup_uri_free (soup_uri);
2590 g_return_val_if_fail (cbdav->priv->uri != NULL, FALSE);
2592 /* remove trailing slashes... */
2593 if (cbdav->priv->uri != NULL) {
2594 len = strlen (cbdav->priv->uri);
2596 if (cbdav->priv->uri[len] == '/') {
2597 cbdav->priv->uri[len] = '\0';
2604 /* ...and append exactly one slash */
2605 if (cbdav->priv->uri && *cbdav->priv->uri) {
2606 gchar *tmp = cbdav->priv->uri;
2608 cbdav->priv->uri = g_strconcat (cbdav->priv->uri, "/", NULL);
2613 if (cbdav->priv->store == NULL) {
2614 /* remove the old cache while migrating to ECalBackendStore */
2615 e_cal_backend_cache_remove (cache_dir, "cache.xml");
2616 cbdav->priv->store = e_cal_backend_file_store_new (cache_dir);
2618 if (cbdav->priv->store == NULL) {
2619 g_propagate_error (perror, EDC_ERROR_EX (OtherError, "Cannot create local store"));
2623 e_cal_backend_store_load (cbdav->priv->store);
2626 /* Set the local attachment store */
2627 if (g_mkdir_with_parents (cache_dir, 0700) < 0) {
2628 g_propagate_error (perror, EDC_ERROR_EX (OtherError, "mkdir failed"));
2632 /* FIXME Not honoring ESourceRefresh:enabled. */
2633 interval_in_minutes =
2634 e_source_refresh_get_interval_minutes (refresh_extension);
2636 if (interval_in_minutes == 0)
2637 cbdav->priv->refresh_time.tv_sec = DEFAULT_REFRESH_TIME;
2639 cbdav->priv->refresh_time.tv_sec = interval_in_minutes * 60;
2641 if (!cbdav->priv->synch_slave) {
2644 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_SLEEP);
2645 slave = g_thread_create (caldav_synch_slave_loop, cbdav, FALSE, NULL);
2647 if (slave == NULL) {
2648 g_propagate_error (perror, EDC_ERROR_EX (OtherError, "Could not create synch slave"));
2651 cbdav->priv->synch_slave = slave;
2658 proxy_settings_changed (EProxy *proxy,
2661 SoupURI *proxy_uri = NULL;
2662 ECalBackendCalDAVPrivate *priv = (ECalBackendCalDAVPrivate *) user_data;
2664 if (!priv || !priv->uri || !priv->session)
2667 /* use proxy if necessary */
2668 if (e_proxy_require_proxy_for_uri (proxy, priv->uri)) {
2669 proxy_uri = e_proxy_peek_uri_for (proxy, priv->uri);
2672 g_object_set (priv->session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL);
2676 open_calendar (ECalBackendCalDAV *cbdav,
2679 gboolean server_unreachable = FALSE;
2681 GError *local_error = NULL;
2683 g_return_val_if_fail (cbdav != NULL, FALSE);
2685 /* set forward proxy */
2686 proxy_settings_changed (cbdav->priv->proxy, cbdav->priv);
2688 success = caldav_server_open_calendar (
2689 cbdav, &server_unreachable, &local_error);
2692 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_WORK);
2693 g_cond_signal (cbdav->priv->cond);
2695 cbdav->priv->is_google = is_google_uri (cbdav->priv->uri);
2696 } else if (server_unreachable) {
2697 cbdav->priv->opened = FALSE;
2698 cbdav->priv->read_only = TRUE;
2700 gchar *msg = g_strdup_printf (_("Server is unreachable, calendar is opened in read-only mode.\nError message: %s"), local_error->message);
2701 e_cal_backend_notify_error (E_CAL_BACKEND (cbdav), msg);
2703 g_clear_error (&local_error);
2708 if (local_error != NULL)
2709 g_propagate_error (error, local_error);
2715 caldav_do_open (ECalBackendSync *backend,
2717 GCancellable *cancellable,
2718 gboolean only_if_exists,
2721 ECalBackendCalDAV *cbdav;
2722 gboolean opened = TRUE;
2725 cbdav = E_CAL_BACKEND_CALDAV (backend);
2727 g_mutex_lock (cbdav->priv->busy_lock);
2729 /* let it decide the 'getctag' extension availability again */
2730 cbdav->priv->ctag_supported = TRUE;
2732 if (!cbdav->priv->loaded && !initialize_backend (cbdav, perror)) {
2733 g_mutex_unlock (cbdav->priv->busy_lock);
2737 online = e_backend_get_online (E_BACKEND (backend));
2739 if (!cbdav->priv->do_offline && !online) {
2740 g_mutex_unlock (cbdav->priv->busy_lock);
2741 g_propagate_error (perror, EDC_ERROR (RepositoryOffline));
2745 cbdav->priv->loaded = TRUE;
2746 cbdav->priv->opened = TRUE;
2747 cbdav->priv->is_google = FALSE;
2750 GError *local_error = NULL;
2752 opened = open_calendar (cbdav, &local_error);
2754 if (g_error_matches (local_error, E_DATA_CAL_ERROR, AuthenticationRequired) || g_error_matches (local_error, E_DATA_CAL_ERROR, AuthenticationFailed)) {
2755 g_clear_error (&local_error);
2756 opened = caldav_authenticate (
2757 cbdav, FALSE, cancellable, perror);
2760 if (local_error != NULL)
2761 g_propagate_error (perror, local_error);
2764 cbdav->priv->read_only = TRUE;
2768 e_cal_backend_notify_opened (E_CAL_BACKEND (backend), NULL);
2770 e_cal_backend_notify_readonly (
2771 E_CAL_BACKEND (backend), cbdav->priv->read_only);
2772 e_cal_backend_notify_online (E_CAL_BACKEND (backend), online);
2774 g_mutex_unlock (cbdav->priv->busy_lock);
2778 caldav_refresh (ECalBackendSync *backend,
2780 GCancellable *cancellable,
2783 ECalBackendCalDAV *cbdav;
2786 cbdav = E_CAL_BACKEND_CALDAV (backend);
2788 g_mutex_lock (cbdav->priv->busy_lock);
2790 if (!cbdav->priv->loaded
2791 || cbdav->priv->slave_cmd == SLAVE_SHOULD_DIE
2792 || !check_state (cbdav, &online, NULL)
2794 g_mutex_unlock (cbdav->priv->busy_lock);
2798 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_WORK);
2801 g_cond_signal (cbdav->priv->cond);
2802 g_mutex_unlock (cbdav->priv->busy_lock);
2806 caldav_remove (ECalBackendSync *backend,
2808 GCancellable *cancellable,
2811 ECalBackendCalDAV *cbdav;
2814 cbdav = E_CAL_BACKEND_CALDAV (backend);
2816 /* first tell it to die, then wait for its lock */
2817 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_DIE);
2819 g_mutex_lock (cbdav->priv->busy_lock);
2821 if (!cbdav->priv->loaded) {
2822 g_mutex_unlock (cbdav->priv->busy_lock);
2826 if (!check_state (cbdav, &online, NULL)) {
2827 /* lie here a bit, but otherwise the calendar will not be removed, even it should */
2828 g_print (G_STRLOC ": Failed to check state");
2831 e_cal_backend_store_remove (cbdav->priv->store);
2832 cbdav->priv->store = NULL;
2833 cbdav->priv->loaded = FALSE;
2834 cbdav->priv->opened = FALSE;
2836 if (cbdav->priv->synch_slave) {
2837 g_cond_signal (cbdav->priv->cond);
2839 /* wait until the slave died */
2840 g_cond_wait (cbdav->priv->slave_gone_cond, cbdav->priv->busy_lock);
2843 g_mutex_unlock (cbdav->priv->busy_lock);
2847 remove_comp_from_cache_cb (gpointer value,
2850 ECalComponent *comp = value;
2851 ECalBackendStore *store = user_data;
2852 ECalComponentId *id;
2854 g_return_if_fail (comp != NULL);
2855 g_return_if_fail (store != NULL);
2857 id = e_cal_component_get_id (comp);
2858 g_return_if_fail (id != NULL);
2860 e_cal_backend_store_remove_component (store, id->uid, id->rid);
2861 e_cal_component_free_id (id);
2865 remove_comp_from_cache (ECalBackendCalDAV *cbdav,
2869 gboolean res = FALSE;
2871 if (!rid || !*rid) {
2872 /* get with detached instances */
2873 GSList *objects = e_cal_backend_store_get_components_by_uid (cbdav->priv->store, uid);
2876 g_slist_foreach (objects, (GFunc) remove_comp_from_cache_cb, cbdav->priv->store);
2877 g_slist_foreach (objects, (GFunc) g_object_unref, NULL);
2878 g_slist_free (objects);
2883 res = e_cal_backend_store_remove_component (cbdav->priv->store, uid, rid);
2890 add_detached_recur_to_vcalendar_cb (gpointer value,
2893 icalcomponent *recurrence = e_cal_component_get_icalcomponent (value);
2894 icalcomponent *vcalendar = user_data;
2896 icalcomponent_add_component (
2898 icalcomponent_new_clone (recurrence));
2902 sort_master_first (gconstpointer a,
2905 icalcomponent *ca, *cb;
2907 ca = e_cal_component_get_icalcomponent ((ECalComponent *) a);
2908 cb = e_cal_component_get_icalcomponent ((ECalComponent *) b);
2919 return icaltime_compare (icalcomponent_get_recurrenceid (ca), icalcomponent_get_recurrenceid (cb));
2922 /* Returns new icalcomponent, with all detached instances stored in a cache.
2923 * The cache lock should be locked when called this function.
2925 static icalcomponent *
2926 get_comp_from_cache (ECalBackendCalDAV *cbdav,
2932 icalcomponent *icalcomp = NULL;
2934 if (rid == NULL || !*rid) {
2935 /* get with detached instances */
2936 GSList *objects = e_cal_backend_store_get_components_by_uid (cbdav->priv->store, uid);
2942 if (g_slist_length (objects) == 1) {
2943 ECalComponent *comp = objects->data;
2945 /* will be unreffed a bit later */
2947 icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
2949 /* if we have detached recurrences, return a VCALENDAR */
2950 icalcomp = e_cal_util_new_top_level ();
2952 objects = g_slist_sort (objects, sort_master_first);
2954 /* add all detached recurrences and the master object */
2955 g_slist_foreach (objects, add_detached_recur_to_vcalendar_cb, icalcomp);
2958 /* every component has set same href and etag, thus it doesn't matter where it will be read */
2960 *href = ecalcomp_get_href (objects->data);
2962 *etag = ecalcomp_get_etag (objects->data);
2964 g_slist_foreach (objects, (GFunc) g_object_unref, NULL);
2965 g_slist_free (objects);
2967 /* get the exact object */
2968 ECalComponent *comp = e_cal_backend_store_get_component (cbdav->priv->store, uid, rid);
2971 icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
2973 *href = ecalcomp_get_href (comp);
2975 *etag = ecalcomp_get_etag (comp);
2976 g_object_unref (comp);
2984 put_comp_to_cache (ECalBackendCalDAV *cbdav,
2985 icalcomponent *icalcomp,
2989 icalcomponent_kind my_kind;
2990 ECalComponent *comp;
2991 gboolean res = FALSE;
2993 g_return_val_if_fail (cbdav != NULL, FALSE);
2994 g_return_val_if_fail (icalcomp != NULL, FALSE);
2996 my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
2997 comp = e_cal_component_new ();
2999 if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
3000 icalcomponent *subcomp;
3002 /* remove all old components from the cache first */
3003 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
3005 subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
3006 remove_comp_from_cache (cbdav, icalcomponent_get_uid (subcomp), NULL);
3009 /* then put new. It's because some detached instances could be removed on the server. */
3010 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
3012 subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
3013 /* because reusing the same comp doesn't clear recur_id member properly */
3014 g_object_unref (comp);
3015 comp = e_cal_component_new ();
3017 if (e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (subcomp))) {
3019 ecalcomp_set_href (comp, href);
3021 ecalcomp_set_etag (comp, etag);
3023 if (put_component_to_store (cbdav, comp))
3027 } else if (icalcomponent_isa (icalcomp) == my_kind) {
3028 remove_comp_from_cache (cbdav, icalcomponent_get_uid (icalcomp), NULL);
3030 if (e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (icalcomp))) {
3032 ecalcomp_set_href (comp, href);
3034 ecalcomp_set_etag (comp, etag);
3036 res = put_component_to_store (cbdav, comp);
3040 g_object_unref (comp);
3046 remove_property (gpointer prop,
3049 icalcomponent_remove_property (icomp, prop);
3050 icalproperty_free (prop);
3054 strip_unneeded_x_props (icalcomponent *icomp)
3057 GSList *to_remove = NULL;
3059 g_return_if_fail (icomp != NULL);
3060 g_return_if_fail (icalcomponent_isa (icomp) != ICAL_VCALENDAR_COMPONENT);
3062 for (prop = icalcomponent_get_first_property (icomp, ICAL_X_PROPERTY);
3064 prop = icalcomponent_get_next_property (icomp, ICAL_X_PROPERTY)) {
3065 if (g_str_has_prefix (icalproperty_get_x_name (prop), X_E_CALDAV)) {
3066 to_remove = g_slist_prepend (to_remove, prop);
3070 for (prop = icalcomponent_get_first_property (icomp, ICAL_XLICERROR_PROPERTY);
3072 prop = icalcomponent_get_next_property (icomp, ICAL_XLICERROR_PROPERTY)) {
3073 to_remove = g_slist_prepend (to_remove, prop);
3076 g_slist_foreach (to_remove, remove_property, icomp);
3077 g_slist_free (to_remove);
3081 is_stored_on_server (ECalBackendCalDAV *cbdav,
3084 SoupURI *my_uri, *test_uri;
3087 g_return_val_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav), FALSE);
3088 g_return_val_if_fail (cbdav->priv->uri != NULL, FALSE);
3089 g_return_val_if_fail (uri != NULL, FALSE);
3091 my_uri = soup_uri_new (cbdav->priv->uri);
3092 g_return_val_if_fail (my_uri != NULL, FALSE);
3094 test_uri = soup_uri_new (uri);
3096 soup_uri_free (my_uri);
3100 res = my_uri->host && test_uri->host && g_ascii_strcasecmp (my_uri->host, test_uri->host) == 0;
3102 soup_uri_free (my_uri);
3103 soup_uri_free (test_uri);
3109 convert_to_inline_attachment (ECalBackendCalDAV *cbdav,
3110 icalcomponent *icalcomp)
3112 icalcomponent *cclone;
3114 GSList *to_remove = NULL;
3116 g_return_if_fail (icalcomp != NULL);
3118 cclone = icalcomponent_new_clone (icalcomp);
3120 /* Remove local url attachments first */
3121 for (p = icalcomponent_get_first_property (icalcomp, ICAL_ATTACH_PROPERTY);
3123 p = icalcomponent_get_next_property (icalcomp, ICAL_ATTACH_PROPERTY)) {
3126 attach = icalproperty_get_attach ((const icalproperty *) p);
3127 if (icalattach_get_is_url (attach)) {
3130 url = icalattach_get_url (attach);
3131 if (g_str_has_prefix (url, LOCAL_PREFIX))
3132 to_remove = g_slist_prepend (to_remove, p);
3135 g_slist_foreach (to_remove, remove_property, icalcomp);
3136 g_slist_free (to_remove);
3138 /* convert local url attachments to inline attachments now */
3139 for (p = icalcomponent_get_first_property (cclone, ICAL_ATTACH_PROPERTY);
3141 p = icalcomponent_get_next_property (cclone, ICAL_ATTACH_PROPERTY)) {
3144 GError *error = NULL;
3150 attach = icalproperty_get_attach ((const icalproperty *) p);
3151 if (!icalattach_get_is_url (attach))
3154 uri = icalattach_get_url (attach);
3155 if (!g_str_has_prefix (uri, LOCAL_PREFIX))
3158 file = g_file_new_for_uri (uri);
3159 basename = g_file_get_basename (file);
3160 if (g_file_load_contents (file, NULL, &content, &len, NULL, &error)) {
3162 icalparameter *param;
3166 * do a base64 encoding so it can
3167 * be embedded in a soap message
3169 encoded = g_base64_encode ((guchar *) content, len);
3170 attach = icalattach_new_from_data (encoded, NULL, NULL);
3174 prop = icalproperty_new_attach (attach);
3175 icalattach_unref (attach);
3177 param = icalparameter_new_value (ICAL_VALUE_BINARY);
3178 icalproperty_add_parameter (prop, param);
3180 param = icalparameter_new_encoding (ICAL_ENCODING_BASE64);
3181 icalproperty_add_parameter (prop, param);
3183 param = icalparameter_new_x (basename);
3184 icalparameter_set_xname (param, X_E_CALDAV_ATTACHMENT_NAME);
3185 icalproperty_add_parameter (prop, param);
3187 icalcomponent_add_property (icalcomp, prop);
3189 g_warning ("%s\n", error->message);
3190 g_clear_error (&error);
3193 g_object_unref (file);
3195 icalcomponent_free (cclone);
3199 convert_to_url_attachment (ECalBackendCalDAV *cbdav,
3200 icalcomponent *icalcomp)
3202 ECalBackend *backend;
3203 GSList *to_remove = NULL, *to_remove_after_download = NULL;
3204 icalcomponent *cclone;
3208 g_return_if_fail (cbdav != NULL);
3209 g_return_if_fail (icalcomp != NULL);
3211 backend = E_CAL_BACKEND (cbdav);
3212 cclone = icalcomponent_new_clone (icalcomp);
3214 /* Remove all inline attachments first */
3215 for (p = icalcomponent_get_first_property (icalcomp, ICAL_ATTACH_PROPERTY);
3217 p = icalcomponent_get_next_property (icalcomp, ICAL_ATTACH_PROPERTY)) {
3220 attach = icalproperty_get_attach ((const icalproperty *) p);
3221 if (!icalattach_get_is_url (attach))
3222 to_remove = g_slist_prepend (to_remove, p);
3223 else if (is_stored_on_server (cbdav, icalattach_get_url (attach)))
3224 to_remove_after_download = g_slist_prepend (to_remove_after_download, p);
3226 g_slist_foreach (to_remove, remove_property, icalcomp);
3227 g_slist_free (to_remove);
3229 /* convert inline attachments to url attachments now */
3230 for (p = icalcomponent_get_first_property (cclone, ICAL_ATTACH_PROPERTY), fileindex = 0;
3232 p = icalcomponent_get_next_property (cclone, ICAL_ATTACH_PROPERTY), fileindex++) {
3235 gchar *decoded = NULL;
3236 gchar *basename, *local_filename;
3238 attach = icalproperty_get_attach ((const icalproperty *) p);
3239 if (icalattach_get_is_url (attach)) {
3240 const gchar *attach_url = icalattach_get_url (attach);
3241 GError *error = NULL;
3243 if (!is_stored_on_server (cbdav, attach_url))
3246 if (!caldav_server_download_attachment (cbdav, attach_url, &decoded, &len, &error)) {
3247 if (caldav_debug_show (DEBUG_ATTACHMENTS))
3248 g_print ("CalDAV::%s: Failed to download from a server: %s\n", G_STRFUNC, error ? error->message : "Unknown error");
3253 basename = icalproperty_get_parameter_as_string_r (p, X_E_CALDAV_ATTACHMENT_NAME);
3254 local_filename = e_cal_backend_create_cache_filename (backend, icalcomponent_get_uid (icalcomp), basename, fileindex);
3257 if (local_filename) {
3258 GError *error = NULL;
3260 if (decoded == NULL) {
3263 content = (gchar *) icalattach_get_data (attach);
3264 decoded = (gchar *) g_base64_decode (content, &len);
3267 if (g_file_set_contents (local_filename, decoded, len, &error)) {
3271 url = g_filename_to_uri (local_filename, NULL, NULL);
3272 attach = icalattach_new_from_url (url);
3273 prop = icalproperty_new_attach (attach);
3274 icalattach_unref (attach);
3275 icalcomponent_add_property (icalcomp, prop);
3278 g_warning ("%s\n", error->message);
3279 g_clear_error (&error);
3282 g_free (local_filename);
3286 icalcomponent_free (cclone);
3288 g_slist_foreach (to_remove_after_download, remove_property, icalcomp);
3289 g_slist_free (to_remove_after_download);
3293 remove_files (const gchar *dir,
3294 const gchar *fileprefix)
3298 g_return_if_fail (dir != NULL);
3299 g_return_if_fail (fileprefix != NULL);
3300 g_return_if_fail (*fileprefix != '\0');
3302 d = g_dir_open (dir, 0, NULL);
3305 gint len = strlen (fileprefix);
3307 while ((entry = g_dir_read_name (d)) != NULL) {
3308 if (entry && strncmp (entry, fileprefix, len) == 0) {
3311 path = g_build_filename (dir, entry, NULL);
3312 if (!g_file_test (path, G_FILE_TEST_IS_DIR))
3322 remove_cached_attachment (ECalBackendCalDAV *cbdav,
3330 g_return_if_fail (cbdav != NULL);
3331 g_return_if_fail (uid != NULL);
3333 l = e_cal_backend_store_get_components_by_uid (cbdav->priv->store, uid);
3334 len = g_slist_length (l);
3335 g_slist_foreach (l, (GFunc) g_object_unref, NULL);
3340 dir = e_cal_backend_create_cache_filename (E_CAL_BACKEND (cbdav), uid, "a", 0);
3344 fileprefix = g_strrstr (dir, G_DIR_SEPARATOR_S);
3350 fileprefix[strlen (fileprefix) - 1] = '\0';
3352 remove_files (dir, fileprefix);
3358 /* callback for icalcomponent_foreach_tzid */
3360 ECalBackendStore *store;
3361 icalcomponent *vcal_comp;
3362 icalcomponent *icalcomp;
3366 add_timezone_cb (icalparameter *param,
3371 icalcomponent *vtz_comp;
3372 ForeachTzidData *f_data = (ForeachTzidData *) data;
3374 tzid = icalparameter_get_tzid (param);
3378 tz = icalcomponent_get_timezone (f_data->vcal_comp, tzid);
3382 tz = icalcomponent_get_timezone (f_data->icalcomp, tzid);
3384 tz = icaltimezone_get_builtin_timezone_from_tzid (tzid);
3386 tz = (icaltimezone *) e_cal_backend_store_get_timezone (f_data->store, tzid);
3391 vtz_comp = icaltimezone_get_component (tz);
3395 icalcomponent_add_component (f_data->vcal_comp,
3396 icalcomponent_new_clone (vtz_comp));
3400 add_timezones_from_component (ECalBackendCalDAV *cbdav,
3401 icalcomponent *vcal_comp,
3402 icalcomponent *icalcomp)
3404 ForeachTzidData f_data;
3406 g_return_if_fail (cbdav != NULL);
3407 g_return_if_fail (vcal_comp != NULL);
3408 g_return_if_fail (icalcomp != NULL);
3410 f_data.store = cbdav->priv->store;
3411 f_data.vcal_comp = vcal_comp;
3412 f_data.icalcomp = icalcomp;
3414 icalcomponent_foreach_tzid (icalcomp, add_timezone_cb, &f_data);
3417 /* also removes X-EVOLUTION-CALDAV from all the components */
3419 pack_cobj (ECalBackendCalDAV *cbdav,
3420 icalcomponent *icomp)
3422 icalcomponent *calcomp;
3425 if (icalcomponent_isa (icomp) != ICAL_VCALENDAR_COMPONENT) {
3426 icalcomponent *cclone;
3428 calcomp = e_cal_util_new_top_level ();
3430 cclone = icalcomponent_new_clone (icomp);
3431 strip_unneeded_x_props (cclone);
3432 convert_to_inline_attachment (cbdav, cclone);
3433 icalcomponent_add_component (calcomp, cclone);
3434 add_timezones_from_component (cbdav, calcomp, cclone);
3436 icalcomponent *subcomp;
3437 icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
3439 calcomp = icalcomponent_new_clone (icomp);
3440 for (subcomp = icalcomponent_get_first_component (calcomp, my_kind);
3442 subcomp = icalcomponent_get_next_component (calcomp, my_kind)) {
3443 strip_unneeded_x_props (subcomp);
3444 convert_to_inline_attachment (cbdav, subcomp);
3445 add_timezones_from_component (cbdav, calcomp, subcomp);
3449 objstr = icalcomponent_as_ical_string_r (calcomp);
3450 icalcomponent_free (calcomp);
3459 sanitize_component (ECalBackend *cb,
3460 ECalComponent *comp)
3462 ECalComponentDateTime dt;
3465 /* Check dtstart, dtend and due's timezone, and convert it to local
3466 * default timezone if the timezone is not in our builtin timezone
3468 e_cal_component_get_dtstart (comp, &dt);
3469 if (dt.value && dt.tzid) {
3470 zone = caldav_internal_get_timezone (cb, dt.tzid);
3472 g_free ((gchar *) dt.tzid);
3473 dt.tzid = g_strdup ("UTC");
3474 e_cal_component_set_dtstart (comp, &dt);
3477 e_cal_component_free_datetime (&dt);
3479 e_cal_component_get_dtend (comp, &dt);
3480 if (dt.value && dt.tzid) {
3481 zone = caldav_internal_get_timezone (cb, dt.tzid);
3483 g_free ((gchar *) dt.tzid);
3484 dt.tzid = g_strdup ("UTC");
3485 e_cal_component_set_dtend (comp, &dt);
3488 e_cal_component_free_datetime (&dt);
3490 e_cal_component_get_due (comp, &dt);
3491 if (dt.value && dt.tzid) {
3492 zone = caldav_internal_get_timezone (cb, dt.tzid);
3494 g_free ((gchar *) dt.tzid);
3495 dt.tzid = g_strdup ("UTC");
3496 e_cal_component_set_due (comp, &dt);
3499 e_cal_component_free_datetime (&dt);
3500 e_cal_component_abort_sequence (comp);
3504 cache_contains (ECalBackendCalDAV *cbdav,
3509 ECalComponent *comp;
3511 g_return_val_if_fail (cbdav != NULL, FALSE);
3512 g_return_val_if_fail (uid != NULL, FALSE);
3514 g_return_val_if_fail (cbdav->priv->store != NULL, FALSE);
3516 comp = e_cal_backend_store_get_component (cbdav->priv->store, uid, rid);
3520 g_object_unref (comp);
3525 /* Returns subcomponent of icalcomp, which is a master object, or icalcomp itself, if it's not a VCALENDAR;
3526 * Do not free returned pointer, it'll be freed together with the icalcomp.
3528 static icalcomponent *
3529 get_master_comp (ECalBackendCalDAV *cbdav,
3530 icalcomponent *icalcomp)
3532 icalcomponent *master = icalcomp;
3534 g_return_val_if_fail (cbdav != NULL, NULL);
3535 g_return_val_if_fail (icalcomp != NULL, NULL);
3537 if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
3538 icalcomponent *subcomp;
3539 icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
3543 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
3545 subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
3546 struct icaltimetype sub_rid = icalcomponent_get_recurrenceid (subcomp);
3548 if (icaltime_is_null_time (sub_rid)) {
3559 remove_instance (ECalBackendCalDAV *cbdav,
3560 icalcomponent *icalcomp,
3561 struct icaltimetype rid,
3563 gboolean also_exdate)
3565 icalcomponent *master = icalcomp;
3566 gboolean res = FALSE;
3568 g_return_val_if_fail (icalcomp != NULL, res);
3569 g_return_val_if_fail (!icaltime_is_null_time (rid), res);
3571 /* remove an instance only */
3572 if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
3573 icalcomponent *subcomp;
3574 icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
3576 gboolean start_first = FALSE;
3580 /* remove old instance first */
3581 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
3583 subcomp = start_first ? icalcomponent_get_first_component (icalcomp, my_kind) : icalcomponent_get_next_component (icalcomp, my_kind)) {
3584 struct icaltimetype sub_rid = icalcomponent_get_recurrenceid (subcomp);
3586 start_first = FALSE;
3588 if (icaltime_is_null_time (sub_rid)) {
3591 } else if (icaltime_compare (sub_rid, rid) == 0) {
3592 icalcomponent_remove_component (icalcomp, subcomp);
3593 icalcomponent_free (subcomp);
3597 /* either no master or master not as the first component, thus rescan */
3606 /* whether left at least one instance or a master object */
3612 if (master && also_exdate) {
3613 e_cal_util_remove_instances (master, rid, mod);
3619 static icalcomponent *
3620 replace_master (ECalBackendCalDAV *cbdav,
3621 icalcomponent *old_comp,
3622 icalcomponent *new_master)
3624 icalcomponent *old_master;
3625 if (icalcomponent_isa (old_comp) != ICAL_VCALENDAR_COMPONENT) {
3626 icalcomponent_free (old_comp);
3630 old_master = get_master_comp (cbdav, old_comp);
3632 /* no master, strange */
3633 icalcomponent_free (new_master);
3635 icalcomponent_remove_component (old_comp, old_master);
3636 icalcomponent_free (old_master);
3638 icalcomponent_add_component (old_comp, new_master);
3644 /* the resulting component should be unreffed when done with it;
3645 * the fallback_comp is cloned, if used */
3646 static ECalComponent *
3647 get_ecalcomp_master_from_cache_or_fallback (ECalBackendCalDAV *cbdav,
3650 ECalComponent *fallback_comp)
3652 ECalComponent *comp = NULL;
3653 icalcomponent *icalcomp;
3655 g_return_val_if_fail (cbdav != NULL, NULL);
3656 g_return_val_if_fail (uid != NULL, NULL);
3658 icalcomp = get_comp_from_cache (cbdav, uid, rid, NULL, NULL);
3660 icalcomponent *master = get_master_comp (cbdav, icalcomp);
3663 comp = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master));
3666 icalcomponent_free (icalcomp);
3669 if (!comp && fallback_comp)
3670 comp = e_cal_component_clone (fallback_comp);
3675 /* a busy_lock is supposed to be locked already, when calling this function */
3677 do_create_objects (ECalBackendCalDAV *cbdav,
3678 const GSList *in_calobjs,
3680 GSList **new_components,
3683 ECalComponent *comp;
3684 gboolean online, did_put = FALSE;
3685 struct icaltimetype current;
3686 icalcomponent *icalcomp;
3687 const gchar *in_calobj = in_calobjs->data;
3688 const gchar *comp_uid;
3690 if (!check_state (cbdav, &online, perror))
3693 /* 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"
3694 * in our static capability list. This simplifies a lot of the logic, especially around asynchronous results. */
3695 if (in_calobjs->next != NULL) {
3696 g_propagate_error (perror, e_data_cal_create_error (UnsupportedMethod, _("CalDAV does not support bulk additions")));
3700 comp = e_cal_component_new_from_string (in_calobj);
3703 g_propagate_error (perror, EDC_ERROR (InvalidObject));
3707 icalcomp = e_cal_component_get_icalcomponent (comp);
3708 if (icalcomp == NULL) {
3709 g_object_unref (comp);
3710 g_propagate_error (perror, EDC_ERROR (InvalidObject));
3714 comp_uid = icalcomponent_get_uid (icalcomp);
3718 new_uid = e_cal_component_gen_uid ();
3720 g_object_unref (comp);
3721 g_propagate_error (perror, EDC_ERROR (InvalidObject));
3725 icalcomponent_set_uid (icalcomp, new_uid);
3726 comp_uid = icalcomponent_get_uid (icalcomp);
3731 /* check the object is not in our cache */
3732 if (cache_contains (cbdav, comp_uid, NULL)) {
3733 g_object_unref (comp);
3734 g_propagate_error (perror, EDC_ERROR (ObjectIdAlreadyExists));
3738 /* Set the created and last modified times on the component */
3739 current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
3740 e_cal_component_set_created (comp, ¤t);
3741 e_cal_component_set_last_modified (comp, ¤t);
3743 /* sanitize the component*/
3744 sanitize_component ((ECalBackend *) cbdav, comp);
3747 CalDAVObject object;
3749 object.href = ecalcomp_gen_href (comp);
3751 object.cdata = pack_cobj (cbdav, icalcomp);
3753 did_put = caldav_server_put_object (cbdav, &object, icalcomp, perror);
3755 caldav_object_free (&object, FALSE);
3757 /* mark component as out of synch */
3758 /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_CREATED); */
3763 *uids = g_slist_prepend (*uids, g_strdup (comp_uid));
3766 *new_components = g_slist_prepend(*new_components, get_ecalcomp_master_from_cache_or_fallback (cbdav, comp_uid, NULL, comp));
3769 g_object_unref (comp);
3772 /* a busy_lock is supposed to be locked already, when calling this function */
3774 do_modify_objects (ECalBackendCalDAV *cbdav,
3775 const GSList *calobjs,
3777 GSList **old_components,
3778 GSList **new_components,
3781 ECalComponent *comp;
3782 icalcomponent *cache_comp;
3783 gboolean online, did_put = FALSE;
3784 ECalComponentId *id;
3785 struct icaltimetype current;
3786 gchar *href = NULL, *etag = NULL;
3787 const gchar *calobj = calobjs->data;
3790 *new_components = NULL;
3792 if (!check_state (cbdav, &online, error))
3795 /* We make the assumption that the calobjs list we're passed is always exactly one element long, since we haven't specified "bulk-modifies"
3796 * in our static capability list. This simplifies a lot of the logic, especially around asynchronous results. */
3797 if (calobjs->next != NULL) {
3798 g_propagate_error (error, e_data_cal_create_error (UnsupportedMethod, _("CalDAV does not support bulk modifications")));
3802 comp = e_cal_component_new_from_string (calobj);
3805 g_propagate_error (error, EDC_ERROR (InvalidObject));
3809 if (!e_cal_component_get_icalcomponent (comp) ||
3810 icalcomponent_isa (e_cal_component_get_icalcomponent (comp)) != e_cal_backend_get_kind (E_CAL_BACKEND (cbdav))) {
3811 g_object_unref (comp);
3812 g_propagate_error (error, EDC_ERROR (InvalidObject));
3816 /* Set the last modified time on the component */
3817 current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
3818 e_cal_component_set_last_modified (comp, ¤t);
3820 /* sanitize the component */
3821 sanitize_component ((ECalBackend *) cbdav, comp);
3823 id = e_cal_component_get_id (comp);
3824 e_return_data_cal_error_if_fail (id != NULL, InvalidObject);
3826 /* fetch full component from cache, it will be pushed to the server */
3827 cache_comp = get_comp_from_cache (cbdav, id->uid, NULL, &href, &etag);
3829 if (cache_comp == NULL) {
3830 e_cal_component_free_id (id);
3831 g_object_unref (comp);
3834 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
3839 /* mark component as out of synch */
3840 /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_MODIFIED);*/
3843 if (old_components) {
3844 *old_components = NULL;
3846 if (e_cal_component_is_instance (comp)) {
3847 /* set detached instance as the old object, if any */
3848 ECalComponent *old_instance = e_cal_backend_store_get_component (cbdav->priv->store, id->uid, id->rid);
3850 /* This will give a reference to 'old_component' */
3852 *old_components = g_slist_prepend (*old_components, e_cal_component_clone (old_instance));
3853 g_object_unref (old_instance);
3857 if (!*old_components) {
3858 icalcomponent *master = get_master_comp (cbdav, cache_comp);
3861 /* set full component as the old object */
3862 *old_components = g_slist_prepend (*old_components, e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master)));
3868 case CALOBJ_MOD_ONLY_THIS:
3869 case CALOBJ_MOD_THIS:
3870 if (e_cal_component_is_instance (comp)) {
3871 icalcomponent *new_comp = e_cal_component_get_icalcomponent (comp);
3873 /* new object is only this instance */
3875 *new_components = g_slist_prepend (*new_components, e_cal_component_clone (comp));
3877 /* add the detached instance */
3878 if (icalcomponent_isa (cache_comp) == ICAL_VCALENDAR_COMPONENT) {
3879 /* do not modify the EXDATE, as the component will be put back */
3880 remove_instance (cbdav, cache_comp, icalcomponent_get_recurrenceid (new_comp), mod, FALSE);
3882 /* this is only a master object, thus make is a VCALENDAR component */
3883 icalcomponent *icomp;
3885 icomp = e_cal_util_new_top_level ();
3886 icalcomponent_add_component (icomp, cache_comp);
3888 /* no need to free the cache_comp, as it is inside icomp */
3892 if (cache_comp && cbdav->priv->is_google) {
3893 icalcomponent_set_sequence (cache_comp, icalcomponent_get_sequence (cache_comp) + 1);
3894 icalcomponent_set_sequence (new_comp, icalcomponent_get_sequence (new_comp) + 1);
3897 /* add the detached instance finally */
3898 icalcomponent_add_component (cache_comp, icalcomponent_new_clone (new_comp));
3900 cache_comp = replace_master (cbdav, cache_comp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
3903 case CALOBJ_MOD_ALL:
3904 cache_comp = replace_master (cbdav, cache_comp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
3906 case CALOBJ_MOD_THISANDPRIOR:
3907 case CALOBJ_MOD_THISANDFUTURE:
3912 CalDAVObject object;
3916 object.cdata = pack_cobj (cbdav, cache_comp);
3918 did_put = caldav_server_put_object (cbdav, &object, cache_comp, error);
3920 caldav_object_free (&object, FALSE);
3924 /* mark component as out of synch */
3925 /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_MODIFIED);*/
3929 if (new_components && !*new_components) {
3930 /* read the comp from cache again, as some servers can modify it on put */
3931 *new_components = g_slist_prepend (*new_components, get_ecalcomp_master_from_cache_or_fallback (cbdav, id->uid, id->rid, NULL));
3935 e_cal_component_free_id (id);
3936 icalcomponent_free (cache_comp);
3937 g_object_unref (comp);
3942 /* a busy_lock is supposed to be locked already, when calling this function */
3944 do_remove_objects (ECalBackendCalDAV *cbdav,
3947 GSList **old_components,
3948 GSList **new_components,
3951 icalcomponent *cache_comp;
3953 gchar *href = NULL, *etag = NULL;
3954 const gchar *uid = ((ECalComponentId *) ids->data)->uid;
3955 const gchar *rid = ((ECalComponentId *) ids->data)->rid;
3958 *new_components = NULL;
3960 if (!check_state (cbdav, &online, perror))
3963 /* We make the assumption that the ids list we're passed is always exactly one element long, since we haven't specified "bulk-removes"
3964 * in our static capability list. This simplifies a lot of the logic, especially around asynchronous results. */
3965 if (ids->next != NULL) {
3966 g_propagate_error (perror, e_data_cal_create_error (UnsupportedMethod, _("CalDAV does not support bulk removals")));
3970 cache_comp = get_comp_from_cache (cbdav, uid, NULL, &href, &etag);
3972 if (cache_comp == NULL) {
3973 g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
3977 if (old_components) {
3978 ECalComponent *old = e_cal_backend_store_get_component (cbdav->priv->store, uid, rid);
3981 *old_components = g_slist_prepend (*old_components, e_cal_component_clone (old));
3982 g_object_unref (old);
3984 icalcomponent *master = get_master_comp (cbdav, cache_comp);
3986 *old_components = g_slist_prepend (*old_components, e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master)));
3992 case CALOBJ_MOD_ONLY_THIS:
3993 case CALOBJ_MOD_THIS:
3995 /* remove one instance from the component */
3996 if (remove_instance (cbdav, cache_comp, icaltime_from_string (rid), mod, mod != CALOBJ_MOD_ONLY_THIS)) {
3997 if (new_components) {
3998 icalcomponent *master = get_master_comp (cbdav, cache_comp);
4000 *new_components = g_slist_prepend (*new_components, e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master)));
4004 /* this was the last instance, thus delete whole component */
4006 remove_comp_from_cache (cbdav, uid, NULL);
4009 /* remove whole object */
4010 remove_comp_from_cache (cbdav, uid, NULL);
4013 case CALOBJ_MOD_ALL:
4014 remove_comp_from_cache (cbdav, uid, NULL);
4016 case CALOBJ_MOD_THISANDPRIOR:
4017 case CALOBJ_MOD_THISANDFUTURE:
4022 CalDAVObject caldav_object;
4024 caldav_object.href = href;
4025 caldav_object.etag = etag;
4026 caldav_object.cdata = NULL;
4028 if (mod == CALOBJ_MOD_THIS && rid && *rid) {
4029 caldav_object.cdata = pack_cobj (cbdav, cache_comp);
4031 caldav_server_put_object (cbdav, &caldav_object, cache_comp, perror);
4033 caldav_server_delete_object (cbdav, &caldav_object, perror);
4035 caldav_object_free (&caldav_object, FALSE);
4039 /* mark component as out of synch */
4040 /*if (mod == CALOBJ_MOD_THIS && rid && *rid)
4041 ecalcomp_set_synch_state (cache_comp_master, ECALCOMP_LOCALLY_MODIFIED);
4043 ecalcomp_set_synch_state (cache_comp_master, ECALCOMP_LOCALLY_DELETED);*/
4045 remove_cached_attachment (cbdav, uid);
4047 icalcomponent_free (cache_comp);
4053 extract_objects (icalcomponent *icomp,
4054 icalcomponent_kind ekind,
4058 icalcomponent *scomp;
4059 icalcomponent_kind kind;
4061 e_return_data_cal_error_if_fail (icomp, InvalidArg);
4062 e_return_data_cal_error_if_fail (objects, InvalidArg);
4064 kind = icalcomponent_isa (icomp);
4066 if (kind == ekind) {
4067 *objects = g_slist_prepend (NULL, icomp);
4071 if (kind != ICAL_VCALENDAR_COMPONENT) {
4072 g_propagate_error (error, EDC_ERROR (InvalidObject));
4077 scomp = icalcomponent_get_first_component (icomp,
4081 /* Remove components from toplevel here */
4082 *objects = g_slist_prepend (*objects, scomp);
4083 icalcomponent_remove_component (icomp, scomp);
4085 scomp = icalcomponent_get_next_component (icomp, ekind);
4090 extract_timezones (ECalBackendCalDAV *cbdav,
4091 icalcomponent *icomp)
4093 GSList *timezones = NULL, *iter;
4097 g_return_val_if_fail (cbdav != NULL, FALSE);
4098 g_return_val_if_fail (icomp != NULL, FALSE);
4100 extract_objects (icomp, ICAL_VTIMEZONE_COMPONENT, &timezones, &err);
4106 zone = icaltimezone_new ();
4107 for (iter = timezones; iter; iter = iter->next) {
4108 if (icaltimezone_set_component (zone, iter->data)) {
4109 e_cal_backend_store_put_timezone (cbdav->priv->store, zone);
4111 icalcomponent_free (iter->data);
4115 icaltimezone_free (zone, TRUE);
4116 g_slist_free (timezones);
4122 process_object (ECalBackendCalDAV *cbdav,
4123 ECalComponent *ecomp,
4125 icalproperty_method method,
4128 ESourceRegistry *registry;
4129 ECalBackend *backend;
4130 struct icaltimetype now;
4132 gboolean is_declined, is_in_cache;
4134 ECalComponentId *id = e_cal_component_get_id (ecomp);
4137 backend = E_CAL_BACKEND (cbdav);
4139 e_return_data_cal_error_if_fail (id != NULL, InvalidObject);
4141 registry = e_cal_backend_get_registry (E_CAL_BACKEND (cbdav));
4144 now = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
4145 e_cal_component_set_created (ecomp, &now);
4146 e_cal_component_set_last_modified (ecomp, &now);
4148 /* just to check whether component exists in a cache */
4149 is_in_cache = cache_contains (cbdav, id->uid, NULL) || cache_contains (cbdav, id->uid, id->rid);
4151 new_obj_str = e_cal_component_get_as_string (ecomp);
4152 mod = e_cal_component_is_instance (ecomp) ? CALOBJ_MOD_THIS : CALOBJ_MOD_ALL;
4155 case ICAL_METHOD_PUBLISH:
4156 case ICAL_METHOD_REQUEST:
4157 case ICAL_METHOD_REPLY:
4158 is_declined = e_cal_backend_user_declined (
4159 registry, e_cal_component_get_icalcomponent (ecomp));
4162 GSList *new_components = NULL, *old_components = NULL;
4163 GSList new_obj_strs = {0,};
4165 new_obj_strs.data = new_obj_str;
4166 do_modify_objects (cbdav, &new_obj_strs, mod,
4167 &old_components, &new_components, &err);
4168 if (!err && new_components && new_components->data) {
4169 if (!old_components || !old_components->data) {
4170 e_cal_backend_notify_component_created (backend, new_components->data);
4172 e_cal_backend_notify_component_modified (backend, old_components->data, new_components->data);
4176 e_util_free_nullable_object_slist (old_components);
4177 e_util_free_nullable_object_slist (new_components);
4179 GSList *new_components = NULL, *old_components = NULL;
4183 do_remove_objects (cbdav, &ids, mod, &old_components, &new_components, &err);
4184 if (!err && old_components && old_components->data) {
4185 if (new_components && new_components->data) {
4186 e_cal_backend_notify_component_modified (backend, old_components->data, new_components->data);
4188 e_cal_backend_notify_component_removed (backend, id, old_components->data, NULL);
4192 e_util_free_nullable_object_slist (old_components);
4193 e_util_free_nullable_object_slist (new_components);
4195 } else if (!is_declined) {
4196 GSList *new_components = NULL;
4197 GSList new_objs = {0,};
4199 new_objs.data = new_obj_str;
4201 do_create_objects (cbdav, &new_objs, NULL, &new_components, &err);
4204 if (new_components && new_components->data)
4205 e_cal_backend_notify_component_created (backend, new_components->data);
4208 e_util_free_nullable_object_slist (new_components);
4211 case ICAL_METHOD_CANCEL:
4213 GSList *new_components = NULL, *old_components = NULL;
4217 do_remove_objects (cbdav, &ids, CALOBJ_MOD_THIS, &old_components, &new_components, &err);
4218 if (!err && old_components && old_components->data) {
4219 if (new_components && new_components->data) {
4220 e_cal_backend_notify_component_modified (backend, old_components->data, new_components->data);
4222 e_cal_backend_notify_component_removed (backend, id, old_components->data, NULL);
4226 e_util_free_nullable_object_slist (old_components);
4227 e_util_free_nullable_object_slist (new_components);
4229 err = EDC_ERROR (ObjectNotFound);
4234 err = EDC_ERROR (UnsupportedMethod);
4238 e_cal_component_free_id (id);
4239 g_free (new_obj_str);
4242 g_propagate_error (error, err);
4246 do_receive_objects (ECalBackendSync *backend,
4248 GCancellable *cancellable,
4249 const gchar *calobj,
4252 ECalBackendCalDAV *cbdav;
4253 icalcomponent *icomp;
4254 icalcomponent_kind kind;
4255 icalproperty_method tmethod;
4257 GSList *objects, *iter;
4260 cbdav = E_CAL_BACKEND_CALDAV (backend);
4262 if (!check_state (cbdav, &online, perror))
4265 icomp = icalparser_parse_string (calobj);
4267 /* Try to parse cal object string */
4268 if (icomp == NULL) {
4269 g_propagate_error (perror, EDC_ERROR (InvalidObject));
4273 kind = e_cal_backend_get_kind (E_CAL_BACKEND (backend));
4274 extract_objects (icomp, kind, &objects, &err);
4277 icalcomponent_free (icomp);
4278 g_propagate_error (perror, err);
4282 /* Extract optional timezone compnents */
4283 extract_timezones (cbdav, icomp);
4285 tmethod = icalcomponent_get_method (icomp);
4287 for (iter = objects; iter && !err; iter = iter->next) {
4288 icalcomponent *scomp;
4289 ECalComponent *ecomp;
4290 icalproperty_method method;
4292 scomp = (icalcomponent *) iter->data;
4293 ecomp = e_cal_component_new ();
4295 e_cal_component_set_icalcomponent (ecomp, scomp);
4297 if (icalcomponent_get_first_property (scomp, ICAL_METHOD_PROPERTY)) {
4298 method = icalcomponent_get_method (scomp);
4303 process_object (cbdav, ecomp, online, method, &err);
4304 g_object_unref (ecomp);
4307 g_slist_free (objects);
4309 icalcomponent_free (icomp);
4312 g_propagate_error (perror, err);
4315 #define caldav_busy_stub(_func_name, _params, _call_func, _call_params) \
4317 _func_name _params \
4319 ECalBackendCalDAV *cbdav; \
4320 SlaveCommand old_slave_cmd; \
4321 gboolean was_slave_busy; \
4323 cbdav = E_CAL_BACKEND_CALDAV (backend); \
4325 /* this is done before locking */ \
4326 old_slave_cmd = cbdav->priv->slave_cmd; \
4327 was_slave_busy = cbdav->priv->slave_busy; \
4328 if (was_slave_busy) { \
4329 /* let it pause its work and do our job */ \
4330 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_SLEEP); \
4333 g_mutex_lock (cbdav->priv->busy_lock); \
4334 _call_func _call_params; \
4336 /* this is done before unlocking */ \
4337 if (was_slave_busy) { \
4338 update_slave_cmd (cbdav->priv, old_slave_cmd); \
4339 g_cond_signal (cbdav->priv->cond); \
4342 g_mutex_unlock (cbdav->priv->busy_lock); \
4346 caldav_create_objects,
4347 (ECalBackendSync *backend,
4349 GCancellable *cancellable,
4350 const GSList *in_calobjs,
4352 GSList **new_components,
4362 caldav_modify_objects,
4363 (ECalBackendSync *backend,
4365 GCancellable *cancellable,
4366 const GSList *calobjs,
4368 GSList **old_components,
4369 GSList **new_components,
4380 caldav_remove_objects,
4381 (ECalBackendSync *backend,
4383 GCancellable *cancellable,
4386 GSList **old_components,
4387 GSList **new_components,
4398 caldav_receive_objects,
4399 (ECalBackendSync *backend,
4401 GCancellable *cancellable,
4402 const gchar *calobj,
4412 caldav_send_objects (ECalBackendSync *backend,
4414 GCancellable *cancellable,
4415 const gchar *calobj,
4417 gchar **modified_calobj,
4421 *modified_calobj = g_strdup (calobj);
4425 caldav_get_object (ECalBackendSync *backend,
4427 GCancellable *cancellable,
4433 ECalBackendCalDAV *cbdav;
4434 icalcomponent *icalcomp;
4436 cbdav = E_CAL_BACKEND_CALDAV (backend);
4439 icalcomp = get_comp_from_cache (cbdav, uid, rid, NULL, NULL);
4442 g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
4446 *object = icalcomponent_as_ical_string_r (icalcomp);
4447 icalcomponent_free (icalcomp);
4451 caldav_add_timezone (ECalBackendSync *backend,
4453 GCancellable *cancellable,
4457 icalcomponent *tz_comp;
4458 ECalBackendCalDAV *cbdav;
4460 cbdav = E_CAL_BACKEND_CALDAV (backend);
4462 e_return_data_cal_error_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav), InvalidArg);
4463 e_return_data_cal_error_if_fail (tzobj != NULL, InvalidArg);
4465 tz_comp = icalparser_parse_string (tzobj);
4467 g_propagate_error (error, EDC_ERROR (InvalidObject));
4471 if (icalcomponent_isa (tz_comp) == ICAL_VTIMEZONE_COMPONENT) {
4474 zone = icaltimezone_new ();
4475 icaltimezone_set_component (zone, tz_comp);
4477 e_cal_backend_store_put_timezone (cbdav->priv->store, zone);
4479 icaltimezone_free (zone, TRUE);
4481 icalcomponent_free (tz_comp);
4486 caldav_get_object_list (ECalBackendSync *backend,
4488 GCancellable *cancellable,
4489 const gchar *sexp_string,
4493 ECalBackendCalDAV *cbdav;
4494 ECalBackendSExp *sexp;
4497 GSList *list, *iter;
4498 time_t occur_start = -1, occur_end = -1;
4499 gboolean prunning_by_time;
4501 cbdav = E_CAL_BACKEND_CALDAV (backend);
4503 sexp = e_cal_backend_sexp_new (sexp_string);
4506 g_propagate_error (perror, EDC_ERROR (InvalidQuery));
4510 if (g_str_equal (sexp_string, "#t")) {
4518 prunning_by_time = e_cal_backend_sexp_evaluate_occur_times (sexp, &occur_start, &occur_end);
4520 list = prunning_by_time ?
4521 e_cal_backend_store_get_components_occuring_in_range (cbdav->priv->store, occur_start, occur_end)
4522 : e_cal_backend_store_get_components (cbdav->priv->store);
4524 bkend = E_CAL_BACKEND (backend);
4526 for (iter = list; iter; iter = g_slist_next (iter)) {
4527 ECalComponent *comp = E_CAL_COMPONENT (iter->data);
4530 e_cal_backend_sexp_match_comp (sexp, comp, bkend)) {
4531 *objects = g_slist_prepend (*objects, e_cal_component_get_as_string (comp));
4534 g_object_unref (comp);
4537 g_object_unref (sexp);
4538 g_slist_free (list);
4542 caldav_start_view (ECalBackend *backend,
4543 EDataCalView *query)
4545 ECalBackendCalDAV *cbdav;
4546 ECalBackendSExp *sexp;
4549 GSList *list, *iter;
4550 const gchar *sexp_string;
4551 time_t occur_start = -1, occur_end = -1;
4552 gboolean prunning_by_time;
4553 cbdav = E_CAL_BACKEND_CALDAV (backend);
4555 sexp_string = e_data_cal_view_get_text (query);
4556 sexp = e_cal_backend_sexp_new (sexp_string);
4558 /* FIXME:check invalid sexp */
4560 if (g_str_equal (sexp_string, "#t")) {
4565 prunning_by_time = e_cal_backend_sexp_evaluate_occur_times (sexp,
4569 bkend = E_CAL_BACKEND (backend);
4571 list = prunning_by_time ?
4572 e_cal_backend_store_get_components_occuring_in_range (cbdav->priv->store, occur_start, occur_end)
4573 : e_cal_backend_store_get_components (cbdav->priv->store);
4575 for (iter = list; iter; iter = g_slist_next (iter)) {
4576 ECalComponent *comp = E_CAL_COMPONENT (iter->data);
4579 e_cal_backend_sexp_match_comp (sexp, comp, bkend)) {
4580 e_data_cal_view_notify_components_added_1 (query, comp);
4583 g_object_unref (comp);
4586 g_object_unref (sexp);
4587 g_slist_free (list);
4589 e_data_cal_view_notify_complete (query, NULL /* Success */);
4593 caldav_get_free_busy (ECalBackendSync *backend,
4595 GCancellable *cancellable,
4596 const GSList *users,
4602 ECalBackendCalDAV *cbdav;
4603 icalcomponent *icalcomp;
4604 ECalComponent *comp;
4605 ECalComponentDateTime dt;
4606 ECalComponentOrganizer organizer = {NULL};
4607 ESourceAuthentication *auth_extension;
4609 struct icaltimetype dtvalue;
4613 GSList *attendees = NULL, *to_free = NULL;
4614 const gchar *extension_name;
4618 cbdav = E_CAL_BACKEND_CALDAV (backend);
4620 e_return_data_cal_error_if_fail (users != NULL, InvalidArg);
4621 e_return_data_cal_error_if_fail (freebusy != NULL, InvalidArg);
4622 e_return_data_cal_error_if_fail (start < end, InvalidArg);
4624 if (!cbdav->priv->calendar_schedule) {
4625 g_propagate_error (error, EDC_ERROR_EX (OtherError, _("Calendar doesn't support Free/Busy")));
4629 if (!cbdav->priv->schedule_outbox_url) {
4630 caldav_receive_schedule_outbox_url (cbdav);
4631 if (!cbdav->priv->schedule_outbox_url) {
4632 cbdav->priv->calendar_schedule = FALSE;
4633 g_propagate_error (error, EDC_ERROR_EX (OtherError, "Schedule outbox url not found"));
4638 comp = e_cal_component_new ();
4639 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_FREEBUSY);
4641 str = e_cal_component_gen_uid ();
4642 e_cal_component_set_uid (comp, str);
4645 utc = icaltimezone_get_utc_timezone ();
4646 dt.value = &dtvalue;
4647 dt.tzid = icaltimezone_get_tzid (utc);
4649 dtvalue = icaltime_current_time_with_zone (utc);
4650 e_cal_component_set_dtstamp (comp, &dtvalue);
4652 dtvalue = icaltime_from_timet_with_zone (start, FALSE, utc);
4653 e_cal_component_set_dtstart (comp, &dt);
4655 dtvalue = icaltime_from_timet_with_zone (end, FALSE, utc);
4656 e_cal_component_set_dtend (comp, &dt);
4658 usermail = get_usermail (E_CAL_BACKEND (backend));
4659 if (usermail != NULL && *usermail == '\0') {
4664 source = e_backend_get_source (E_BACKEND (backend));
4665 extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
4666 auth_extension = e_source_get_extension (source, extension_name);
4668 if (usermail == NULL)
4669 usermail = e_source_authentication_dup_user (auth_extension);
4671 organizer.value = g_strconcat ("mailto:", usermail, NULL);
4672 e_cal_component_set_organizer (comp, &organizer);
4673 g_free ((gchar *) organizer.value);
4677 for (u = users; u; u = u->next) {
4678 ECalComponentAttendee *ca;
4679 gchar *temp = g_strconcat ("mailto:", (const gchar *)u->data, NULL);
4681 ca = g_new0 (ECalComponentAttendee, 1);
4684 ca->cutype = ICAL_CUTYPE_INDIVIDUAL;
4685 ca->status = ICAL_PARTSTAT_NEEDSACTION;
4686 ca->role = ICAL_ROLE_CHAIR;
4688 to_free = g_slist_prepend (to_free, temp);
4689 attendees = g_slist_append (attendees, ca);
4692 e_cal_component_set_attendee_list (comp, attendees);
4694 g_slist_foreach (attendees, (GFunc) g_free, NULL);
4695 g_slist_free (attendees);
4697 g_slist_foreach (to_free, (GFunc) g_free, NULL);
4698 g_slist_free (to_free);
4700 e_cal_component_abort_sequence (comp);
4702 /* put the free/busy request to a VCALENDAR */
4703 icalcomp = e_cal_util_new_top_level ();
4704 icalcomponent_set_method (icalcomp, ICAL_METHOD_REQUEST);
4705 icalcomponent_add_component (icalcomp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
4707 str = icalcomponent_as_ical_string_r (icalcomp);
4709 icalcomponent_free (icalcomp);
4710 g_object_unref (comp);
4712 e_return_data_cal_error_if_fail (str != NULL, OtherError);
4714 caldav_post_freebusy (cbdav, cbdav->priv->schedule_outbox_url, &str, &err);
4717 /* parse returned xml */
4720 doc = xmlReadMemory (str, strlen (str), "response.xml", NULL, 0);
4722 xmlXPathContextPtr xpctx;
4723 xmlXPathObjectPtr result;
4725 xpctx = xmlXPathNewContext (doc);
4726 xmlXPathRegisterNs (xpctx, (xmlChar *) "D", (xmlChar *) "DAV:");
4727 xmlXPathRegisterNs (xpctx, (xmlChar *) "C", (xmlChar *) "urn:ietf:params:xml:ns:caldav");
4729 result = xpath_eval (xpctx, "/C:schedule-response/C:response");
4731 if (result == NULL || result->type != XPATH_NODESET) {
4732 err = EDC_ERROR_EX (OtherError, "Unexpected result in schedule-response");
4736 n = xmlXPathNodeSetGetLength (result->nodesetval);
4737 for (i = 0; i < n; i++) {
4740 tmp = xp_object_get_string (xpath_eval (xpctx, "string(/C:schedule-response/C:response[%d]/C:calendar-data)", i + 1));
4742 GSList *objects = NULL, *o;
4744 icalcomp = icalparser_parse_string (tmp);
4746 extract_objects (icalcomp, ICAL_VFREEBUSY_COMPONENT, &objects, &err);
4747 if (icalcomp && !err) {
4748 for (o = objects; o; o = o->next) {
4749 gchar *obj_str = icalcomponent_as_ical_string_r (o->data);
4751 if (obj_str && *obj_str)
4752 *freebusy = g_slist_append (*freebusy, obj_str);
4758 g_slist_foreach (objects, (GFunc) icalcomponent_free, NULL);
4759 g_slist_free (objects);
4762 icalcomponent_free (icalcomp);
4773 xmlXPathFreeObject (result);
4774 xmlXPathFreeContext (xpctx);
4782 g_propagate_error (error, err);
4786 caldav_notify_online_cb (ECalBackend *backend,
4789 ECalBackendCalDAV *cbdav;
4792 cbdav = E_CAL_BACKEND_CALDAV (backend);
4794 /*g_mutex_lock (cbdav->priv->busy_lock);*/
4796 online = e_backend_get_online (E_BACKEND (backend));
4798 if (!cbdav->priv->loaded) {
4799 e_cal_backend_notify_online (backend, online);
4800 /*g_mutex_unlock (cbdav->priv->busy_lock);*/
4805 /* Wake up the slave thread */
4806 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_WORK);
4807 g_cond_signal (cbdav->priv->cond);
4809 soup_session_abort (cbdav->priv->session);
4810 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_SLEEP);
4813 e_cal_backend_notify_online (backend, online);
4815 /*g_mutex_unlock (cbdav->priv->busy_lock);*/
4818 static icaltimezone *
4819 caldav_internal_get_timezone (ECalBackend *backend,
4823 ECalBackendCalDAV *cbdav;
4825 cbdav = E_CAL_BACKEND_CALDAV (backend);
4828 if (cbdav->priv->store)
4829 zone = (icaltimezone *) e_cal_backend_store_get_timezone (cbdav->priv->store, tzid);
4831 if (!zone && E_CAL_BACKEND_CLASS (parent_class)->internal_get_timezone)
4832 zone = E_CAL_BACKEND_CLASS (parent_class)->internal_get_timezone (backend, tzid);
4838 caldav_source_changed_thread (gpointer data)
4840 ECalBackendCalDAV *cbdav = data;
4841 SlaveCommand old_slave_cmd;
4842 gboolean old_slave_busy;
4844 g_return_val_if_fail (cbdav != NULL, NULL);
4846 old_slave_cmd = cbdav->priv->slave_cmd;
4847 old_slave_busy = cbdav->priv->slave_busy;
4848 if (old_slave_busy) {
4849 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_SLEEP);
4850 g_mutex_lock (cbdav->priv->busy_lock);
4853 initialize_backend (cbdav, NULL);
4855 /* always wakeup thread, even when it was sleeping */
4856 g_cond_signal (cbdav->priv->cond);
4858 if (old_slave_busy) {
4859 update_slave_cmd (cbdav->priv, old_slave_cmd);
4860 g_mutex_unlock (cbdav->priv->busy_lock);
4863 cbdav->priv->updating_source = FALSE;
4865 g_object_unref (cbdav);
4871 caldav_source_changed_cb (ESource *source,
4872 ECalBackendCalDAV *cbdav)
4876 g_return_if_fail (source != NULL);
4877 g_return_if_fail (cbdav != NULL);
4879 if (cbdav->priv->updating_source)
4882 cbdav->priv->updating_source = TRUE;
4884 thread = g_thread_new (NULL, caldav_source_changed_thread, g_object_ref (cbdav));
4885 g_thread_unref (thread);
4888 static ESourceAuthenticationResult
4889 caldav_try_password_sync (ESourceAuthenticator *authenticator,
4890 const GString *password,
4891 GCancellable *cancellable,
4894 ECalBackendCalDAV *cbdav;
4895 ESourceAuthenticationResult result;
4896 GError *local_error = NULL;
4898 cbdav = E_CAL_BACKEND_CALDAV (authenticator);
4900 /* Busy lock is already acquired by caldav_do_open(). */
4902 g_free (cbdav->priv->password);
4903 cbdav->priv->password = g_strdup (password->str);
4905 open_calendar (cbdav, &local_error);
4907 if (local_error == NULL) {
4908 result = E_SOURCE_AUTHENTICATION_ACCEPTED;
4909 } else if (g_error_matches (local_error, E_DATA_CAL_ERROR, AuthenticationFailed)) {
4910 result = E_SOURCE_AUTHENTICATION_REJECTED;
4911 g_clear_error (&local_error);
4913 result = E_SOURCE_AUTHENTICATION_ERROR;
4914 g_propagate_error (error, local_error);
4921 caldav_source_authenticator_init (ESourceAuthenticatorInterface *interface)
4923 interface->try_password_sync = caldav_try_password_sync;
4926 /* ************************************************************************* */
4927 /* ***************************** GObject Foo ******************************* */
4929 G_DEFINE_TYPE_WITH_CODE (
4931 e_cal_backend_caldav,
4932 E_TYPE_CAL_BACKEND_SYNC,
4933 G_IMPLEMENT_INTERFACE (
4934 E_TYPE_SOURCE_AUTHENTICATOR,
4935 caldav_source_authenticator_init))
4938 e_cal_backend_caldav_dispose (GObject *object)
4940 ECalBackendCalDAVPrivate *priv;
4943 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (object);
4945 /* tell the slave to stop before acquiring a lock,
4946 * as it can work at the moment, and lock can be locked */
4947 update_slave_cmd (priv, SLAVE_SHOULD_DIE);
4949 g_mutex_lock (priv->busy_lock);
4951 if (priv->disposed) {
4952 g_mutex_unlock (priv->busy_lock);
4956 source = e_backend_get_source (E_BACKEND (object));
4958 g_signal_handlers_disconnect_by_func (G_OBJECT (source), caldav_source_changed_cb, object);
4960 /* stop the slave */
4961 if (priv->synch_slave) {
4962 g_cond_signal (priv->cond);
4964 /* wait until the slave died */
4965 g_cond_wait (priv->slave_gone_cond, priv->busy_lock);
4968 g_object_unref (priv->session);
4969 g_object_unref (priv->proxy);
4972 g_free (priv->schedule_outbox_url);
4974 if (priv->store != NULL) {
4975 g_object_unref (priv->store);
4978 priv->disposed = TRUE;
4979 g_mutex_unlock (priv->busy_lock);
4981 /* Chain up to parent's dispose() method. */
4982 G_OBJECT_CLASS (parent_class)->dispose (object);
4986 e_cal_backend_caldav_finalize (GObject *object)
4988 ECalBackendCalDAVPrivate *priv;
4990 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (object);
4992 g_mutex_free (priv->busy_lock);
4993 g_cond_free (priv->cond);
4994 g_cond_free (priv->slave_gone_cond);
4996 g_free (priv->password);
4998 /* Chain up to parent's finalize() method. */
4999 G_OBJECT_CLASS (parent_class)->finalize (object);
5003 cal_backend_caldav_constructed (GObject *object)
5006 ESourceWebdav *extension;
5007 ECalBackendCalDAV *cbdav;
5008 const gchar *extension_name;
5010 cbdav = E_CAL_BACKEND_CALDAV (object);
5012 source = e_backend_get_source (E_BACKEND (cbdav));
5013 extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
5014 extension = e_source_get_extension (source, extension_name);
5016 g_object_bind_property (
5017 extension, "ignore-invalid-cert",
5018 cbdav->priv->session, SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE,
5019 G_BINDING_SYNC_CREATE |
5020 G_BINDING_INVERT_BOOLEAN);
5022 /* Chain up to parent's constructed() method. */
5023 G_OBJECT_CLASS (e_cal_backend_caldav_parent_class)->
5024 constructed (object);
5028 e_cal_backend_caldav_init (ECalBackendCalDAV *cbdav)
5030 cbdav->priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
5031 cbdav->priv->session = soup_session_sync_new ();
5032 g_object_set (cbdav->priv->session, SOUP_SESSION_TIMEOUT, 90, NULL);
5034 cbdav->priv->proxy = e_proxy_new ();
5035 e_proxy_setup_proxy (cbdav->priv->proxy);
5036 g_signal_connect (cbdav->priv->proxy, "changed", G_CALLBACK (proxy_settings_changed), cbdav->priv);
5038 if (G_UNLIKELY (caldav_debug_show (DEBUG_MESSAGE)))
5039 caldav_debug_setup (cbdav->priv->session);
5041 cbdav->priv->disposed = FALSE;
5042 cbdav->priv->loaded = FALSE;
5043 cbdav->priv->opened = FALSE;
5045 /* Thinks the 'getctag' extension is available the first time, but unset it when realizes it isn't. */
5046 cbdav->priv->ctag_supported = TRUE;
5047 cbdav->priv->ctag_to_store = NULL;
5049 cbdav->priv->schedule_outbox_url = NULL;
5051 cbdav->priv->is_google = FALSE;
5053 cbdav->priv->busy_lock = g_mutex_new ();
5054 cbdav->priv->cond = g_cond_new ();
5055 cbdav->priv->slave_gone_cond = g_cond_new ();
5057 /* Slave control ... */
5058 cbdav->priv->slave_cmd = SLAVE_SHOULD_SLEEP;
5059 cbdav->priv->slave_busy = FALSE;
5060 cbdav->priv->refresh_time.tv_usec = 0;
5061 cbdav->priv->refresh_time.tv_sec = DEFAULT_REFRESH_TIME;
5063 g_signal_connect (cbdav->priv->session, "authenticate",
5064 G_CALLBACK (soup_authenticate), cbdav);
5066 e_cal_backend_sync_set_lock (E_CAL_BACKEND_SYNC (cbdav), FALSE);
5069 cbdav, "notify::online",
5070 G_CALLBACK (caldav_notify_online_cb), NULL);
5074 e_cal_backend_caldav_class_init (ECalBackendCalDAVClass *class)
5076 GObjectClass *object_class;
5077 ECalBackendClass *backend_class;
5078 ECalBackendSyncClass *sync_class;
5080 object_class = (GObjectClass *) class;
5081 backend_class = (ECalBackendClass *) class;
5082 sync_class = (ECalBackendSyncClass *) class;
5084 caldav_debug_init ();
5086 parent_class = (ECalBackendSyncClass *) g_type_class_peek_parent (class);
5087 g_type_class_add_private (class, sizeof (ECalBackendCalDAVPrivate));
5089 object_class->dispose = e_cal_backend_caldav_dispose;
5090 object_class->finalize = e_cal_backend_caldav_finalize;
5091 object_class->constructed = cal_backend_caldav_constructed;
5093 sync_class->get_backend_property_sync = caldav_get_backend_property;
5095 sync_class->open_sync = caldav_do_open;
5096 sync_class->refresh_sync = caldav_refresh;
5097 sync_class->remove_sync = caldav_remove;
5099 sync_class->create_objects_sync = caldav_create_objects;
5100 sync_class->modify_objects_sync = caldav_modify_objects;
5101 sync_class->remove_objects_sync = caldav_remove_objects;
5103 sync_class->receive_objects_sync = caldav_receive_objects;
5104 sync_class->send_objects_sync = caldav_send_objects;
5105 sync_class->get_object_sync = caldav_get_object;
5106 sync_class->get_object_list_sync = caldav_get_object_list;
5107 sync_class->add_timezone_sync = caldav_add_timezone;
5108 sync_class->get_free_busy_sync = caldav_get_free_busy;
5110 backend_class->start_view = caldav_start_view;
5112 backend_class->internal_get_timezone = caldav_internal_get_timezone;