caldav_set_mode doesn't use a priv->lock now
[platform/upstream/evolution-data-server.git] / calendar / backends / caldav / e-cal-backend-caldav.c
1 /*
2  * Evolution calendar - caldav backend
3  *
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.
8  *
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.
13  *
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/>
16  *
17  *
18  * Authors:
19  *              Christian Kellner <gicmo@gnome.org>
20  *
21  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
22  *
23  */
24
25 #include <config.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <gconf/gconf-client.h>
29 #include <glib/gi18n-lib.h>
30 #include "libedataserver/e-xml-hash-utils.h"
31 #include "libedataserver/e-proxy.h"
32 #include <libecal/e-cal-recur.h>
33 #include <libecal/e-cal-util.h>
34 #include <libecal/e-cal-time-util.h>
35 #include <libedata-cal/e-cal-backend-cache.h>
36 #include <libedata-cal/e-cal-backend-util.h>
37 #include <libedata-cal/e-cal-backend-sexp.h>
38
39 /* LibXML2 includes */
40 #include <libxml/parser.h>
41 #include <libxml/tree.h>
42 #include <libxml/xpath.h>
43 #include <libxml/xpathInternals.h>
44
45 /* LibSoup includes */
46 #include <libsoup/soup.h>
47
48 #include "e-cal-backend-caldav.h"
49
50 #define d(x)
51
52 #define CALDAV_CTAG_KEY "CALDAV_CTAG"
53
54 /* in seconds */
55 #define DEFAULT_REFRESH_TIME 60
56
57 typedef enum {
58
59         SLAVE_SHOULD_SLEEP,
60         SLAVE_SHOULD_WORK,
61         SLAVE_SHOULD_DIE
62
63 } SlaveCommand;
64
65 /* Private part of the ECalBackendHttp structure */
66 struct _ECalBackendCalDAVPrivate {
67
68         /* online/offline */
69         CalMode mode;
70
71         /* The local disk cache */
72         ECalBackendCache *cache;
73
74         /* should we sync for offline mode? */
75         gboolean do_offline;
76
77         /* TRUE after caldav_open */
78         gboolean loaded;
79
80         /* lock to protect cache */
81         GMutex *lock;
82
83         /* cond to synch threads */
84         GCond *cond;
85
86         /* cond to know the slave gone */
87         GCond *slave_gone_cond;
88
89         /* BG synch thread */
90         const GThread *synch_slave; /* just for a reference, whether thread exists */
91         SlaveCommand slave_cmd;
92         GTimeVal refresh_time;
93         gboolean do_synch;
94
95         /* The main soup session  */
96         SoupSession *session;
97         EProxy *proxy;
98
99         /* well, guess what */
100         gboolean read_only;
101
102         /* clandar uri */
103         gchar *uri;
104
105         /* Authentication info */
106         gchar *username;
107         gchar *password;
108         gboolean need_auth;
109
110         /* object cleanup */
111         gboolean disposed;
112
113         icaltimezone *default_zone;
114
115         /* support for 'getctag' extension */
116         gboolean ctag_supported;
117
118         /* TRUE when 'calendar-schedule' supported on the server */
119         gboolean calendar_schedule;
120         /* with 'calendar-schedule' supported, here's an outbox url
121            for queries of free/busy information */
122         gchar *schedule_outbox_url;
123 };
124
125 /* ************************************************************************* */
126 /* Debugging */
127
128 #define DEBUG_MESSAGE "message"
129 #define DEBUG_MESSAGE_HEADER "message:header"
130 #define DEBUG_MESSAGE_BODY "message:body"
131 #define DEBUG_SERVER_ITEMS "items"
132
133 static gboolean caldav_debug_all = FALSE;
134 static GHashTable *caldav_debug_table = NULL;
135
136 static void
137 add_debug_key (const gchar *start, const gchar *end)
138 {
139         gchar *debug_key;
140         gchar *debug_value;
141
142         if (start == end) {
143                 return;
144         }
145
146         debug_key = debug_value = g_strndup (start, end - start);
147
148         debug_key = g_strchug (debug_key);
149         debug_key = g_strchomp (debug_key);
150
151         if (strlen (debug_key) == 0) {
152                 g_free (debug_value);
153                 return;
154         }
155
156         g_hash_table_insert (caldav_debug_table,
157                              debug_key,
158                              debug_value);
159
160         d(g_debug ("Adding %s to enabled debugging keys", debug_key));
161 }
162
163 static gpointer
164 caldav_debug_init_once (gpointer data)
165 {
166         const gchar *dbg;
167
168         dbg = g_getenv ("CALDAV_DEBUG");
169
170         if (dbg) {
171                 const gchar *ptr;
172
173                 d(g_debug ("Got debug env variable: [%s]", dbg));
174
175                 caldav_debug_table = g_hash_table_new (g_str_hash,
176                                                        g_str_equal);
177
178                 ptr = dbg;
179
180                 while (*ptr != '\0') {
181                         if (*ptr == ',' || *ptr == ':') {
182
183                                 add_debug_key (dbg, ptr);
184
185                                 if (*ptr == ',') {
186                                         dbg = ptr + 1;
187                                 }
188                         }
189
190                         ptr++;
191                 }
192
193                 if (ptr - dbg > 0) {
194                         add_debug_key (dbg, ptr);
195                 }
196
197                 if (g_hash_table_lookup (caldav_debug_table, "all")) {
198                         caldav_debug_all = TRUE;
199                         g_hash_table_destroy (caldav_debug_table);
200                         caldav_debug_table = NULL;
201                 }
202         }
203
204         return NULL;
205 }
206
207 static void
208 caldav_debug_init (void)
209 {
210         static GOnce debug_once = G_ONCE_INIT;
211
212         g_once (&debug_once,
213                 caldav_debug_init_once,
214                 NULL);
215 }
216
217 static gboolean
218 caldav_debug_show (const gchar *component)
219 {
220         if (G_UNLIKELY (caldav_debug_all)) {
221                 return TRUE;
222         } else if (G_UNLIKELY (caldav_debug_table != NULL) &&
223                    g_hash_table_lookup (caldav_debug_table, component)) {
224                 return TRUE;
225         }
226
227         return FALSE;
228 }
229
230 #define DEBUG_MAX_BODY_SIZE (100 * 1024 * 1024)
231
232 static void
233 caldav_debug_setup (SoupSession *session)
234 {
235         SoupLogger *logger;
236         SoupLoggerLogLevel level;
237
238         if (caldav_debug_show (DEBUG_MESSAGE_BODY))
239                 level = SOUP_LOGGER_LOG_BODY;
240         else if (caldav_debug_show (DEBUG_MESSAGE_HEADER))
241                 level = SOUP_LOGGER_LOG_HEADERS;
242         else
243                 level = SOUP_LOGGER_LOG_MINIMAL;
244
245         logger = soup_logger_new (level, DEBUG_MAX_BODY_SIZE);
246         soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
247         g_object_unref (logger);
248 }
249
250 static ECalBackendSyncClass *parent_class = NULL;
251
252 static icaltimezone *caldav_internal_get_default_timezone (ECalBackend *backend);
253 static icaltimezone *caldav_internal_get_timezone (ECalBackend *backend, const gchar *tzid);
254
255 static gboolean remove_comp_from_cache (ECalBackendCalDAV *cbdav, const gchar *uid, const gchar *rid);
256 static gboolean put_comp_to_cache (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp, const gchar *href, const gchar *etag);
257
258 /* ************************************************************************* */
259 /* Misc. utility functions */
260 #define X_E_CALDAV "X-EVOLUTION-CALDAV-"
261
262 static void
263 icomp_x_prop_set (icalcomponent *comp, const gchar *key, const gchar *value)
264 {
265         icalproperty *xprop;
266
267         /* Find the old one first */
268         xprop = icalcomponent_get_first_property (comp, ICAL_X_PROPERTY);
269
270         while (xprop) {
271                 const gchar *str = icalproperty_get_x_name (xprop);
272
273                 if (!strcmp (str, key)) {
274                         if (value) {
275                                 icalproperty_set_value_from_string (xprop, value, "NO");
276                         } else {
277                                 icalcomponent_remove_property (comp, xprop);
278                                 icalproperty_free (xprop);
279                         }
280                         break;
281                 }
282
283                 xprop = icalcomponent_get_next_property (comp, ICAL_X_PROPERTY);
284         }
285
286         if (!xprop && value) {
287                 xprop = icalproperty_new_x (value);
288                 icalproperty_set_x_name (xprop, key);
289                 icalcomponent_add_property (comp, xprop);
290         }
291 }
292
293
294 static gchar *
295 icomp_x_prop_get (icalcomponent *comp, const gchar *key)
296 {
297         icalproperty *xprop;
298
299         /* Find the old one first */
300         xprop = icalcomponent_get_first_property (comp, ICAL_X_PROPERTY);
301
302         while (xprop) {
303                 const gchar *str = icalproperty_get_x_name (xprop);
304
305                 if (!strcmp (str, key)) {
306                         break;
307                 }
308
309                 xprop = icalcomponent_get_next_property (comp, ICAL_X_PROPERTY);
310         }
311
312         if (xprop) {
313                 return icalproperty_get_value_as_string_r (xprop);
314         }
315
316         return NULL;
317 }
318
319 /* passing NULL as 'href' removes the property */
320 static void
321 ecalcomp_set_href (ECalComponent *comp, const gchar *href)
322 {
323         icalcomponent *icomp;
324
325         icomp = e_cal_component_get_icalcomponent (comp);
326         g_return_if_fail (icomp != NULL);
327
328         icomp_x_prop_set (icomp, X_E_CALDAV "HREF", href);
329 }
330
331 static gchar *
332 ecalcomp_get_href (ECalComponent *comp)
333 {
334         icalcomponent *icomp;
335         gchar          *str;
336
337         str = NULL;
338         icomp = e_cal_component_get_icalcomponent (comp);
339         g_return_val_if_fail (icomp != NULL, NULL);
340
341         str =  icomp_x_prop_get (icomp, X_E_CALDAV "HREF");
342
343         return str;
344 }
345
346 /* passing NULL as 'etag' removes the property */
347 static void
348 ecalcomp_set_etag (ECalComponent *comp, const gchar *etag)
349 {
350         icalcomponent *icomp;
351
352         icomp = e_cal_component_get_icalcomponent (comp);
353         g_return_if_fail (icomp != NULL);
354
355         icomp_x_prop_set (icomp, X_E_CALDAV "ETAG", etag);
356 }
357
358 static gchar *
359 ecalcomp_get_etag (ECalComponent *comp)
360 {
361         icalcomponent *icomp;
362         gchar          *str;
363
364         str = NULL;
365         icomp = e_cal_component_get_icalcomponent (comp);
366         g_return_val_if_fail (icomp != NULL, NULL);
367
368         str =  icomp_x_prop_get (icomp, X_E_CALDAV "ETAG");
369
370         return str;
371 }
372
373 /*typedef enum {
374
375         / * object is in synch,
376          * now isnt that ironic? :) * /
377         ECALCOMP_IN_SYNCH = 0,
378
379         / * local changes * /
380         ECALCOMP_LOCALLY_CREATED,
381         ECALCOMP_LOCALLY_DELETED,
382         ECALCOMP_LOCALLY_MODIFIED
383
384 } ECalCompSyncState;
385
386 / * oos = out of synch * /
387 static void
388 ecalcomp_set_synch_state (ECalComponent *comp, ECalCompSyncState state)
389 {
390         icalcomponent *icomp;
391         gchar          *state_string;
392
393         icomp = e_cal_component_get_icalcomponent (comp);
394
395         state_string = g_strdup_printf ("%d", state);
396
397         icomp_x_prop_set (icomp, X_E_CALDAV "ETAG", state_string);
398
399         g_free (state_string);
400 }*/
401
402
403 /* gen uid, set it internally and report it back so we can instantly
404  * use it
405  * and btw FIXME!!! */
406 static gchar *
407 ecalcomp_gen_href (ECalComponent *comp)
408 {
409         gchar *href, *iso;
410
411         icalcomponent *icomp;
412
413         iso = isodate_from_time_t (time (NULL));
414
415         href = g_strconcat (iso, ".ics", NULL);
416
417         g_free (iso);
418
419         icomp = e_cal_component_get_icalcomponent (comp);
420         icomp_x_prop_set (icomp, X_E_CALDAV "HREF", href);
421
422         return href;
423 }
424
425 /* ensure etag is quoted (to workaround potential server bugs) */
426 static gchar *
427 quote_etag (const gchar *etag)
428 {
429         gchar *ret;
430
431         if (etag && (strlen (etag) < 2 || etag[strlen (etag) - 1] != '\"')) {
432                 ret = g_strdup_printf ("\"%s\"", etag);
433         } else {
434                 ret = g_strdup (etag);
435         }
436
437         return ret;
438 }
439
440 /* ************************************************************************* */
441
442 static ECalBackendSyncStatus
443 status_code_to_result (guint status_code, ECalBackendCalDAVPrivate  *priv)
444 {
445         ECalBackendSyncStatus result;
446
447         if (SOUP_STATUS_IS_SUCCESSFUL (status_code)) {
448                 return GNOME_Evolution_Calendar_Success;
449         }
450
451         switch (status_code) {
452
453         case 404:
454                 result = GNOME_Evolution_Calendar_NoSuchCal;
455                 break;
456
457         case 403:
458                 result = GNOME_Evolution_Calendar_AuthenticationFailed;
459                 break;
460
461         case 401:
462                 if (priv && priv->need_auth)
463                         result = GNOME_Evolution_Calendar_AuthenticationFailed;
464                 else
465                         result = GNOME_Evolution_Calendar_AuthenticationRequired;
466                 break;
467
468         default:
469                 d(g_debug ("CalDAV:%s: Unhandled status code %d\n", G_STRFUNC, status_code));
470                 result = GNOME_Evolution_Calendar_OtherError;
471         }
472
473         return result;
474 }
475
476 /* !TS, call with lock held */
477 static ECalBackendSyncStatus
478 check_state (ECalBackendCalDAV *cbdav, gboolean *online)
479 {
480         ECalBackendCalDAVPrivate *priv;
481
482         priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
483
484         *online = FALSE;
485
486         if (!priv->loaded) {
487                 return GNOME_Evolution_Calendar_OtherError;
488         }
489
490         if (priv->mode == CAL_MODE_LOCAL) {
491
492                 if (! priv->do_offline) {
493                         return GNOME_Evolution_Calendar_RepositoryOffline;
494                 }
495
496         } else {
497                 *online = TRUE;
498         }
499
500         return  GNOME_Evolution_Calendar_Success;
501 }
502
503 /* ************************************************************************* */
504 /* XML Parsing code */
505
506 static xmlXPathObjectPtr
507 xpath_eval (xmlXPathContextPtr ctx, const gchar *format, ...)
508 {
509         xmlXPathObjectPtr  result;
510         va_list            args;
511         gchar              *expr;
512
513         if (ctx == NULL) {
514                 return NULL;
515         }
516
517         va_start (args, format);
518         expr = g_strdup_vprintf (format, args);
519         va_end (args);
520
521         result = xmlXPathEvalExpression ((xmlChar *) expr, ctx);
522         g_free (expr);
523
524         if (result == NULL) {
525                 return NULL;
526         }
527
528         if (result->type == XPATH_NODESET &&
529             xmlXPathNodeSetIsEmpty (result->nodesetval)) {
530                 xmlXPathFreeObject (result);
531                 return NULL;
532         }
533
534         return result;
535 }
536
537 #if 0
538 static gboolean
539 parse_status_node (xmlNodePtr node, guint *status_code)
540 {
541         xmlChar  *content;
542         gboolean  res;
543
544         content = xmlNodeGetContent (node);
545
546         res = soup_headers_parse_status_line ((gchar *) content,
547                                               NULL,
548                                               status_code,
549                                               NULL);
550         xmlFree (content);
551
552         return res;
553 }
554 #endif
555
556 static gchar *
557 xp_object_get_string (xmlXPathObjectPtr result)
558 {
559         gchar *ret = NULL;
560
561         if (result == NULL)
562                 return ret;
563
564         if (result->type == XPATH_STRING) {
565                 ret = g_strdup ((gchar *) result->stringval);
566         }
567
568         xmlXPathFreeObject (result);
569         return ret;
570 }
571
572 /* as get_string but will normailze it (i.e. only take
573  * the last part of the href) */
574 static gchar *
575 xp_object_get_href (xmlXPathObjectPtr result)
576 {
577         gchar *ret = NULL;
578         gchar *val;
579
580         if (result == NULL)
581                 return ret;
582
583         if (result->type == XPATH_STRING) {
584                 val = (gchar *) result->stringval;
585
586                 if ((ret = g_strrstr (val, "/")) == NULL) {
587                         ret = val;
588                 } else {
589                         ret++; /* skip the unwanted "/" */
590                 }
591
592                 ret = g_strdup (ret);
593
594                 if (caldav_debug_show (DEBUG_SERVER_ITEMS))
595                         printf ("CalDAV found href: %s\n", ret);
596         }
597
598         xmlXPathFreeObject (result);
599         return ret;
600 }
601
602 /* like get_string but will quote the etag if necessary */
603 static gchar *
604 xp_object_get_etag (xmlXPathObjectPtr result)
605 {
606         gchar *ret = NULL;
607         gchar *str;
608
609         if (result == NULL)
610                 return ret;
611
612         if (result->type == XPATH_STRING) {
613                 str = (gchar *) result->stringval;
614
615                 ret = quote_etag (str);
616         }
617
618         xmlXPathFreeObject (result);
619         return ret;
620 }
621
622 static guint
623 xp_object_get_status (xmlXPathObjectPtr result)
624 {
625         gboolean res;
626         guint    ret = 0;
627
628         if (result == NULL)
629                 return ret;
630
631         if (result->type == XPATH_STRING) {
632                 res = soup_headers_parse_status_line ((gchar *) result->stringval,
633                                                         NULL,
634                                                         &ret,
635                                                         NULL);
636
637                 if (!res) {
638                         ret = 0;
639                 }
640         }
641
642         xmlXPathFreeObject (result);
643         return ret;
644 }
645
646 #if 0
647 static gint
648 xp_object_get_number (xmlXPathObjectPtr result)
649 {
650         gint ret = -1;
651
652         if (result == NULL)
653                 return ret;
654
655         if (result->type == XPATH_STRING) {
656                 ret = result->boolval;
657         }
658
659         xmlXPathFreeObject (result);
660         return ret;
661 }
662 #endif
663
664 /*** *** *** *** *** *** */
665 #define XPATH_HREF "string(/D:multistatus/D:response[%d]/D:href)"
666 #define XPATH_STATUS "string(/D:multistatus/D:response[%d]/D:propstat/D:status)"
667 #define XPATH_GETETAG_STATUS "string(/D:multistatus/D:response[%d]/D:propstat/D:prop/D:getetag/../../D:status)"
668 #define XPATH_GETETAG "string(/D:multistatus/D:response[%d]/D:propstat/D:prop/D:getetag)"
669 #define XPATH_CALENDAR_DATA "string(/D:multistatus/D:response[%d]/C:calendar-data)"
670 #define XPATH_GETCTAG_STATUS "string(/D:multistatus/D:response/D:propstat/D:prop/CS:getctag/../../D:status)"
671 #define XPATH_GETCTAG "string(/D:multistatus/D:response/D:propstat/D:prop/CS:getctag)"
672 #define XPATH_OWNER_STATUS "string(/D:multistatus/D:response/D:propstat/D:prop/D:owner/D:href/../../../D:status)"
673 #define XPATH_OWNER "string(/D:multistatus/D:response/D:propstat/D:prop/D:owner/D:href)"
674 #define XPATH_SCHEDULE_OUTBOX_URL_STATUS "string(/D:multistatus/D:response/D:propstat/D:prop/C:schedule-outbox-URL/D:href/../../../D:status)"
675 #define XPATH_SCHEDULE_OUTBOX_URL "string(/D:multistatus/D:response/D:propstat/D:prop/C:schedule-outbox-URL/D:href)"
676
677 typedef struct _CalDAVObject CalDAVObject;
678
679 struct _CalDAVObject {
680
681         gchar *href;
682         gchar *etag;
683
684         guint status;
685
686         gchar *cdata;
687 };
688
689 static void
690 caldav_object_free (CalDAVObject *object, gboolean free_object_itself)
691 {
692         g_free (object->href);
693         g_free (object->etag);
694         g_free (object->cdata);
695
696         if (free_object_itself) {
697                 g_free (object);
698         }
699 }
700
701 static gboolean
702 parse_report_response (SoupMessage *soup_message, CalDAVObject **objs, gint *len)
703 {
704         xmlXPathContextPtr xpctx;
705         xmlXPathObjectPtr  result;
706         xmlDocPtr          doc;
707         gint                i, n;
708         gboolean           res;
709
710         g_return_val_if_fail (soup_message != NULL, FALSE);
711         g_return_val_if_fail (objs != NULL || len != NULL, FALSE);
712
713         res = TRUE;
714         doc = xmlReadMemory (soup_message->response_body->data,
715                              soup_message->response_body->length,
716                              "response.xml",
717                              NULL,
718                              0);
719
720         if (doc == NULL) {
721                 return FALSE;
722         }
723
724         xpctx = xmlXPathNewContext (doc);
725
726         xmlXPathRegisterNs (xpctx, (xmlChar *) "D",
727                             (xmlChar *) "DAV:");
728
729         xmlXPathRegisterNs (xpctx, (xmlChar *) "C",
730                             (xmlChar *) "urn:ietf:params:xml:ns:caldav");
731
732         result = xpath_eval (xpctx, "/D:multistatus/D:response");
733
734         if (result == NULL || result->type != XPATH_NODESET) {
735                 *len = 0;
736                 res = FALSE;
737                 goto out;
738         }
739
740         n = xmlXPathNodeSetGetLength (result->nodesetval);
741         *len = n;
742
743         *objs = g_new0 (CalDAVObject, n);
744
745         for (i = 0; i < n; i++) {
746                 CalDAVObject *object;
747                 xmlXPathObjectPtr xpres;
748
749                 object = *objs + i;
750                 /* see if we got a status child in the response element */
751
752                 xpres = xpath_eval (xpctx, XPATH_HREF, i + 1);
753                 object->href = xp_object_get_href (xpres);
754
755                 xpres = xpath_eval (xpctx,XPATH_STATUS , i + 1);
756                 object->status = xp_object_get_status (xpres);
757
758                 if (object->status && object->status != 200) {
759                         continue;
760                 }
761
762                 xpres = xpath_eval (xpctx, XPATH_GETETAG_STATUS, i + 1);
763                 object->status = xp_object_get_status (xpres);
764
765                 if (object->status != 200) {
766                         continue;
767                 }
768
769                 xpres = xpath_eval (xpctx, XPATH_GETETAG, i + 1);
770                 object->etag = xp_object_get_etag (xpres);
771
772                 xpres = xpath_eval (xpctx, XPATH_CALENDAR_DATA, i + 1);
773                 object->cdata = xp_object_get_string (xpres);
774         }
775
776 out:
777         if (result != NULL)
778                 xmlXPathFreeObject (result);
779         xmlXPathFreeContext (xpctx);
780         xmlFreeDoc (doc);
781         return res;
782 }
783
784 /* returns whether was able to read the xpath_value from the server's response; *value contains the result */
785 static gboolean
786 parse_propfind_response (SoupMessage *message, const gchar *xpath_status, const gchar *xpath_value, gchar **value)
787 {
788         xmlXPathContextPtr xpctx;
789         xmlDocPtr          doc;
790         gboolean           res = FALSE;
791
792         g_return_val_if_fail (message != NULL, FALSE);
793         g_return_val_if_fail (value != NULL, FALSE);
794
795         doc = xmlReadMemory (message->response_body->data,
796                              message->response_body->length,
797                              "response.xml",
798                              NULL,
799                              0);
800
801         if (doc == NULL) {
802                 return FALSE;
803         }
804
805         xpctx = xmlXPathNewContext (doc);
806         xmlXPathRegisterNs (xpctx, (xmlChar *) "D", (xmlChar *) "DAV:");
807         xmlXPathRegisterNs (xpctx, (xmlChar *) "C", (xmlChar *) "urn:ietf:params:xml:ns:caldav");
808         xmlXPathRegisterNs (xpctx, (xmlChar *) "CS", (xmlChar *) "http://calendarserver.org/ns/");
809
810         if (xpath_status == NULL || xp_object_get_status (xpath_eval (xpctx, xpath_status)) == 200) {
811                 gchar *txt = xp_object_get_string (xpath_eval (xpctx, xpath_value));
812
813                 if (txt && *txt) {
814                         gint len = strlen (txt);
815
816                         if (*txt == '\"' && len > 2 && txt [len - 1] == '\"') {
817                                 /* dequote */
818                                 *value = g_strndup (txt + 1, len - 2);
819                         } else {
820                                 *value = txt;
821                                 txt = NULL;
822                         }
823
824                         res = (*value) != NULL;
825                 }
826
827                 g_free (txt);
828         }
829
830         xmlXPathFreeContext (xpctx);
831         xmlFreeDoc (doc);
832
833         return res;
834 }
835
836 /* ************************************************************************* */
837 /* Authentication helpers for libsoup */
838
839 static void
840 soup_authenticate (SoupSession  *session,
841                    SoupMessage  *msg,
842                    SoupAuth     *auth,
843                    gboolean      retrying,
844                    gpointer      data)
845 {
846         ECalBackendCalDAVPrivate *priv;
847         ECalBackendCalDAV        *cbdav;
848
849         cbdav = E_CAL_BACKEND_CALDAV (data);
850         priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
851
852         /* do not send same password twice, but keep it for later use */
853         if (!retrying)
854                 soup_auth_authenticate (auth, priv->username, priv->password);
855 }
856
857 /* ************************************************************************* */
858 /* direct CalDAV server access functions */
859
860 static void
861 redirect_handler (SoupMessage *msg, gpointer user_data)
862 {
863         if (SOUP_STATUS_IS_REDIRECTION (msg->status_code)) {
864                 SoupSession *soup_session = user_data;
865                 SoupURI *new_uri;
866                 const gchar *new_loc;
867
868                 new_loc = soup_message_headers_get (msg->response_headers, "Location");
869                 if (!new_loc)
870                         return;
871
872                 new_uri = soup_uri_new_with_base (soup_message_get_uri (msg), new_loc);
873                 if (!new_uri) {
874                         soup_message_set_status_full (msg,
875                                                       SOUP_STATUS_MALFORMED,
876                                                       "Invalid Redirect URL");
877                         return;
878                 }
879
880                 soup_message_set_uri (msg, new_uri);
881                 soup_session_requeue_message (soup_session, msg);
882
883                 soup_uri_free (new_uri);
884         }
885 }
886
887 static void
888 send_and_handle_redirection (SoupSession *soup_session, SoupMessage *msg, gchar **new_location)
889 {
890         gchar *old_uri = NULL;
891
892         if (new_location)
893                 old_uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
894
895         soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT);
896         soup_message_add_header_handler (msg, "got_body", "Location", G_CALLBACK (redirect_handler), soup_session);
897         soup_session_send_message (soup_session, msg);
898
899         if (new_location) {
900                 gchar *new_loc = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
901
902                 if (new_loc && old_uri && !g_str_equal (new_loc, old_uri))
903                         *new_location = new_loc;
904                 else
905                         g_free (new_loc);
906         }
907
908         g_free (old_uri);
909 }
910
911 static gchar *
912 caldav_generate_uri (ECalBackendCalDAV *cbdav, const gchar *target)
913 {
914         ECalBackendCalDAVPrivate  *priv;
915         gchar *uri;
916
917         priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
918
919         /* priv->uri *have* trailing slash already */
920         uri = g_strconcat (priv->uri, target, NULL);
921
922         return uri;
923 }
924
925 static ECalBackendSyncStatus
926 caldav_server_open_calendar (ECalBackendCalDAV *cbdav)
927 {
928         ECalBackendCalDAVPrivate  *priv;
929         SoupMessage               *message;
930         const gchar                *header;
931         gboolean                   calendar_access;
932         gboolean                   put_allowed;
933         gboolean                   delete_allowed;
934
935         priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
936
937         /* FIXME: setup text_uri */
938
939         message = soup_message_new (SOUP_METHOD_OPTIONS, priv->uri);
940         if (message == NULL)
941                 return GNOME_Evolution_Calendar_NoSuchCal;
942         soup_message_headers_append (message->request_headers,
943                                      "User-Agent", "Evolution/" VERSION);
944
945         send_and_handle_redirection (priv->session, message, NULL);
946
947         if (! SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
948                 guint status_code = message->status_code;
949
950                 g_object_unref (message);
951
952                 return status_code_to_result (status_code, priv);
953         }
954
955         /* parse the dav header, we are intreseted in the
956          * calendar-access bit only at the moment */
957         header = soup_message_headers_get (message->response_headers, "DAV");
958         if (header) {
959                 calendar_access = soup_header_contains (header, "calendar-access");
960                 priv->calendar_schedule = soup_header_contains (header, "calendar-schedule");
961         } else {
962                 calendar_access = FALSE;
963                 priv->calendar_schedule = FALSE;
964         }
965
966         /* parse the Allow header and look for PUT, DELETE at the
967          * moment (maybe we should check more here, for REPORT eg) */
968         header = soup_message_headers_get (message->response_headers, "Allow");
969         if (header) {
970                 put_allowed = soup_header_contains (header, "PUT");
971                 delete_allowed = soup_header_contains (header, "DELETE");
972         } else
973                 put_allowed = delete_allowed = FALSE;
974
975         g_object_unref (message);
976
977         if (calendar_access) {
978                 priv->read_only = ! (put_allowed && delete_allowed);
979                 priv->do_synch = TRUE;
980                 return GNOME_Evolution_Calendar_Success;
981         }
982
983         return GNOME_Evolution_Calendar_NoSuchCal;
984 }
985
986 /* Returns whether calendar changed on the server. This works only when server
987    supports 'getctag' extension. */
988 static gboolean
989 check_calendar_changed_on_server (ECalBackendCalDAV *cbdav)
990 {
991         ECalBackendCalDAVPrivate *priv;
992         xmlOutputBufferPtr        buf;
993         SoupMessage              *message;
994         xmlDocPtr                 doc;
995         xmlNodePtr                root, node;
996         xmlNsPtr                  ns, nsdav;
997         gboolean                  result = TRUE;
998
999         g_return_val_if_fail (cbdav != NULL, TRUE);
1000
1001         priv   = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1002
1003         /* no support for 'getctag', thus update cache */
1004         if (!priv->ctag_supported)
1005                 return TRUE;
1006
1007         /* Prepare the soup message */
1008         message = soup_message_new ("PROPFIND", priv->uri);
1009         if (message == NULL)
1010                 return FALSE;
1011
1012         doc = xmlNewDoc ((xmlChar *) "1.0");
1013         root = xmlNewDocNode (doc, NULL, (xmlChar *) "propfind", NULL);
1014         xmlDocSetRootElement (doc, root);
1015         nsdav = xmlNewNs (root, (xmlChar *) "DAV:", NULL);
1016         ns = xmlNewNs (root, (xmlChar *) "http://calendarserver.org/ns/", (xmlChar *) "CS");
1017
1018         node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1019         node = xmlNewTextChild (node, nsdav, (xmlChar *) "getctag", NULL);
1020         xmlSetNs (node, ns);
1021
1022         buf = xmlAllocOutputBuffer (NULL);
1023         xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1024         xmlOutputBufferFlush (buf);
1025
1026         soup_message_headers_append (message->request_headers,
1027                                      "User-Agent", "Evolution/" VERSION);
1028         soup_message_headers_append (message->request_headers,
1029                                      "Depth", "0");
1030
1031         soup_message_set_request (message,
1032                                   "application/xml",
1033                                   SOUP_MEMORY_COPY,
1034                                   (gchar *) buf->buffer->content,
1035                                   buf->buffer->use);
1036
1037         /* Send the request now */
1038         send_and_handle_redirection (priv->session, message, NULL);
1039
1040         /* Clean up the memory */
1041         xmlOutputBufferClose (buf);
1042         xmlFreeDoc (doc);
1043
1044         /* Check the result */
1045         if (message->status_code != 207) {
1046                 /* does not support it, but report calendar changed to update cache */
1047                 priv->ctag_supported = FALSE;
1048         } else {
1049                 gchar *ctag = NULL;
1050
1051                 if (parse_propfind_response (message, XPATH_GETCTAG_STATUS, XPATH_GETCTAG, &ctag)) {
1052                         const gchar *my_ctag = e_cal_backend_cache_get_key_value (priv->cache, CALDAV_CTAG_KEY);
1053
1054                         if (ctag && my_ctag && g_str_equal (ctag, my_ctag)) {
1055                                 /* ctag is same, no change in the calendar */
1056                                 result = FALSE;
1057                         } else {
1058                                 e_cal_backend_cache_put_key_value (priv->cache, CALDAV_CTAG_KEY, ctag);
1059                         }
1060
1061                         g_free (ctag);
1062                 } else {
1063                         priv->ctag_supported = FALSE;
1064                 }
1065         }
1066
1067         g_object_unref (message);
1068
1069         return result;
1070 }
1071
1072 static gboolean
1073 caldav_server_list_objects (ECalBackendCalDAV *cbdav, CalDAVObject **objs, gint *len)
1074 {
1075         ECalBackendCalDAVPrivate *priv;
1076         xmlOutputBufferPtr   buf;
1077         SoupMessage         *message;
1078         xmlNodePtr           node;
1079         xmlNodePtr           sn;
1080         xmlNodePtr           root;
1081         xmlDocPtr            doc;
1082         xmlNsPtr             nsdav;
1083         xmlNsPtr             nscd;
1084         gboolean             result;
1085
1086         priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1087         /* Allocate the soup message */
1088         message = soup_message_new ("REPORT", priv->uri);
1089         if (message == NULL)
1090                 return FALSE;
1091
1092         /* Maybe we should just do a g_strdup_printf here? */
1093         /* Prepare request body */
1094         doc = xmlNewDoc ((xmlChar *) "1.0");
1095         root = xmlNewDocNode (doc, NULL, (xmlChar *) "calendar-query", NULL);
1096         nscd = xmlNewNs (root, (xmlChar *) "urn:ietf:params:xml:ns:caldav", (xmlChar *) "C");
1097         xmlSetNs (root, nscd);
1098         xmlDocSetRootElement (doc, root);
1099
1100         /* Add webdav tags */
1101         nsdav = xmlNewNs (root, (xmlChar *) "DAV:", (xmlChar *) "D");
1102         node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1103         xmlNewTextChild (node, nsdav, (xmlChar *) "getetag", NULL);
1104
1105         node = xmlNewTextChild (root, nscd, (xmlChar *) "filter", NULL);
1106         node = xmlNewTextChild (node, nscd, (xmlChar *) "comp-filter", NULL);
1107         xmlSetProp (node, (xmlChar *) "name", (xmlChar *) "VCALENDAR");
1108
1109         sn = xmlNewTextChild (node, nscd, (xmlChar *) "comp-filter", NULL);
1110         switch (e_cal_backend_get_kind (E_CAL_BACKEND (cbdav))) {
1111                 default:
1112                 case ICAL_VEVENT_COMPONENT:
1113                         xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VEVENT");
1114                         break;
1115                 case ICAL_VJOURNAL_COMPONENT:
1116                         xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VJOURNAL");
1117                         break;
1118                 case ICAL_VTODO_COMPONENT:
1119                         xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VTODO");
1120                         break;
1121         }
1122         /* ^^^ add timerange for performance?  */
1123
1124
1125         buf = xmlAllocOutputBuffer (NULL);
1126         xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1127         xmlOutputBufferFlush (buf);
1128
1129         /* Prepare the soup message */
1130         soup_message_headers_append (message->request_headers,
1131                                      "User-Agent", "Evolution/" VERSION);
1132         soup_message_headers_append (message->request_headers,
1133                                      "Depth", "1");
1134
1135         soup_message_set_request (message,
1136                                   "application/xml",
1137                                   SOUP_MEMORY_COPY,
1138                                   (gchar *) buf->buffer->content,
1139                                   buf->buffer->use);
1140
1141         /* Send the request now */
1142         send_and_handle_redirection (priv->session, message, NULL);
1143
1144         /* Clean up the memory */
1145         xmlOutputBufferClose (buf);
1146         xmlFreeDoc (doc);
1147
1148         /* Check the result */
1149         if (message->status_code != 207) {
1150                 g_warning ("Server did not response with 207, but with code %d (%s)", message->status_code, soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : "Unknown code");
1151
1152                 g_object_unref (message);
1153                 return FALSE;
1154         }
1155
1156         /* Parse the response body */
1157         result = parse_report_response (message, objs, len);
1158
1159         g_object_unref (message);
1160         return result;
1161 }
1162
1163
1164 static ECalBackendSyncStatus
1165 caldav_server_get_object (ECalBackendCalDAV *cbdav, CalDAVObject *object)
1166 {
1167         ECalBackendCalDAVPrivate *priv;
1168         ECalBackendSyncStatus     result;
1169         SoupMessage              *message;
1170         const gchar               *hdr;
1171         gchar                     *uri;
1172
1173         priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1174         result = GNOME_Evolution_Calendar_Success;
1175
1176         g_assert (object != NULL && object->href != NULL);
1177
1178         uri = caldav_generate_uri (cbdav, object->href);
1179         message = soup_message_new (SOUP_METHOD_GET, uri);
1180         if (message == NULL) {
1181                 g_free (uri);
1182                 return GNOME_Evolution_Calendar_NoSuchCal;
1183         }
1184
1185         soup_message_headers_append (message->request_headers,
1186                                      "User-Agent", "Evolution/" VERSION);
1187
1188         send_and_handle_redirection (priv->session, message, NULL);
1189
1190         if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1191                 guint status_code = message->status_code;
1192                 g_object_unref (message);
1193
1194                 g_warning ("Could not fetch object '%s' from server, status:%d (%s)", uri, status_code, soup_status_get_phrase (status_code) ? soup_status_get_phrase (status_code) : "Unknown code");
1195                 g_free (uri);
1196                 return status_code_to_result (status_code, priv);
1197         }
1198
1199         hdr = soup_message_headers_get (message->response_headers, "Content-Type");
1200
1201         if (hdr == NULL || g_ascii_strncasecmp (hdr, "text/calendar", 13)) {
1202                 result = GNOME_Evolution_Calendar_InvalidObject;
1203                 g_object_unref (message);
1204                 g_warning ("Object to fetch '%s' not of type text/calendar", uri);
1205                 g_free (uri);
1206                 return result;
1207         }
1208
1209         hdr = soup_message_headers_get (message->response_headers, "ETag");
1210
1211         if (hdr != NULL) {
1212                 g_free (object->etag);
1213                 object->etag = quote_etag (hdr);
1214         } else if (!object->etag) {
1215                 g_warning ("UUHH no ETag, now that's bad! (at '%s')", uri);
1216         }
1217         g_free (uri);
1218
1219         g_free (object->cdata);
1220         object->cdata = g_strdup (message->response_body->data);
1221
1222         g_object_unref (message);
1223
1224         return result;
1225 }
1226
1227 static ECalBackendSyncStatus
1228 caldav_post_freebusy (ECalBackendCalDAV *cbdav, const gchar *url, gchar **post_fb)
1229 {
1230         ECalBackendCalDAVPrivate *priv;
1231         SoupMessage *message;
1232
1233         g_return_val_if_fail (cbdav != NULL, GNOME_Evolution_Calendar_OtherError);
1234         g_return_val_if_fail (url != NULL, GNOME_Evolution_Calendar_OtherError);
1235         g_return_val_if_fail (post_fb != NULL, GNOME_Evolution_Calendar_OtherError);
1236         g_return_val_if_fail (*post_fb != NULL, GNOME_Evolution_Calendar_OtherError);
1237
1238         priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1239
1240         message = soup_message_new (SOUP_METHOD_POST, url);
1241         if (message == NULL) {
1242                 return GNOME_Evolution_Calendar_NoSuchCal;
1243         }
1244
1245         soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1246         soup_message_set_request (message,
1247                                   "text/calendar; charset=utf-8",
1248                                   SOUP_MEMORY_COPY,
1249                                   *post_fb, strlen (*post_fb));
1250
1251         send_and_handle_redirection (priv->session, message, NULL);
1252
1253         if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1254                 guint status_code = message->status_code;
1255                 g_object_unref (message);
1256
1257                 g_warning ("Could not post free/busy request to '%s', status:%d (%s)", url, status_code, soup_status_get_phrase (status_code) ? soup_status_get_phrase (status_code) : "Unknown code");
1258                 return status_code_to_result (status_code, priv);
1259         }
1260
1261         g_free (*post_fb);
1262         *post_fb = g_strdup (message->response_body->data);
1263
1264         g_object_unref (message);
1265
1266         return GNOME_Evolution_Calendar_Success;
1267 }
1268
1269 static ECalBackendSyncStatus
1270 caldav_server_put_object (ECalBackendCalDAV *cbdav, CalDAVObject *object, icalcomponent *icalcomp)
1271 {
1272         ECalBackendCalDAVPrivate *priv;
1273         ECalBackendSyncStatus     result;
1274         SoupMessage              *message;
1275         const gchar               *hdr;
1276         gchar                     *uri;
1277
1278         priv   = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1279         result = GNOME_Evolution_Calendar_Success;
1280         hdr    = NULL;
1281
1282         g_assert (object != NULL && object->cdata != NULL);
1283
1284         uri = caldav_generate_uri (cbdav, object->href);
1285         message = soup_message_new (SOUP_METHOD_PUT, uri);
1286         g_free (uri);
1287         if (message == NULL)
1288                 return GNOME_Evolution_Calendar_NoSuchCal;
1289
1290         soup_message_headers_append (message->request_headers,
1291                                      "User-Agent", "Evolution/" VERSION);
1292
1293         /* For new items we use the If-None-Match so we don't
1294          * acidently override resources, for item updates we
1295          * use the If-Match header to avoid the Lost-update
1296          * problem */
1297         if (object->etag == NULL) {
1298                 soup_message_headers_append (message->request_headers, "If-None-Match", "*");
1299         } else {
1300                 soup_message_headers_append (message->request_headers,
1301                                              "If-Match", object->etag);
1302         }
1303
1304         soup_message_set_request (message,
1305                                   "text/calendar; charset=utf-8",
1306                                   SOUP_MEMORY_COPY,
1307                                   object->cdata,
1308                                   strlen (object->cdata));
1309
1310         uri = NULL;
1311         send_and_handle_redirection (priv->session, message, &uri);
1312
1313         if (uri) {
1314                 gchar *file = strrchr (uri, '/');
1315
1316                 /* there was a redirect, update href properly */
1317                 if (file) {
1318                         gchar *decoded;
1319
1320                         g_free (object->href);
1321
1322                         decoded = soup_uri_decode (file + 1);
1323                         object->href = soup_uri_encode (decoded ? decoded : (file + 1), NULL);
1324
1325                         g_free (decoded);
1326                 }
1327
1328                 g_free (uri);
1329         }
1330
1331         result = status_code_to_result (message->status_code, priv);
1332
1333         if (result == GNOME_Evolution_Calendar_Success) {
1334                 gboolean was_get = FALSE;
1335
1336                 hdr = soup_message_headers_get (message->response_headers, "ETag");
1337                 if (hdr != NULL) {
1338                         g_free (object->etag);
1339                         object->etag = quote_etag (hdr);
1340                 } else {
1341                         /* no ETag header returned, check for it with a GET */
1342                         hdr = soup_message_headers_get (message->response_headers, "Location");
1343                         if (hdr) {
1344                                 /* reflect possible href change first */
1345                                 gchar *file = strrchr (hdr, '/');
1346
1347                                 if (file) {
1348                                         gchar *decoded;
1349
1350                                         g_free (object->href);
1351
1352                                         decoded = soup_uri_decode (file + 1);
1353                                         object->href = soup_uri_encode (decoded ? decoded : (file + 1), NULL);
1354
1355                                         g_free (decoded);
1356                                 }
1357                         }
1358
1359                         result = caldav_server_get_object (cbdav, object);
1360                         was_get = TRUE;
1361                 }
1362
1363                 if (result == GNOME_Evolution_Calendar_Success) {
1364                         icalcomponent *use_comp = NULL;
1365
1366                         if (object->cdata && was_get) {
1367                                 /* maybe server also modified component, thus rather store the server's */
1368                                 use_comp = icalparser_parse_string (object->cdata);
1369                         }
1370
1371                         if (!use_comp)
1372                                 use_comp = icalcomp;
1373
1374                         put_comp_to_cache (cbdav, use_comp, object->href, object->etag);
1375
1376                         if (use_comp != icalcomp)
1377                                 icalcomponent_free (use_comp);
1378                 }
1379         }
1380
1381         g_object_unref (message);
1382         return result;
1383 }
1384
1385 static ECalBackendSyncStatus
1386 caldav_server_delete_object (ECalBackendCalDAV *cbdav, CalDAVObject *object)
1387 {
1388         ECalBackendCalDAVPrivate *priv;
1389         ECalBackendSyncStatus     result;
1390         SoupMessage              *message;
1391         gchar                     *uri;
1392
1393         priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1394         result = GNOME_Evolution_Calendar_Success;
1395
1396         g_assert (object != NULL && object->href != NULL);
1397
1398         uri = caldav_generate_uri (cbdav, object->href);
1399         message = soup_message_new (SOUP_METHOD_DELETE, uri);
1400         g_free (uri);
1401         if (message == NULL)
1402                 return GNOME_Evolution_Calendar_NoSuchCal;
1403
1404         soup_message_headers_append (message->request_headers,
1405                                      "User-Agent", "Evolution/" VERSION);
1406
1407         if (object->etag != NULL) {
1408                 soup_message_headers_append (message->request_headers,
1409                                              "If-Match", object->etag);
1410         }
1411
1412         send_and_handle_redirection (priv->session, message, NULL);
1413
1414         result = status_code_to_result (message->status_code, priv);
1415
1416         g_object_unref (message);
1417
1418         return result;
1419 }
1420
1421 static gboolean
1422 caldav_receive_schedule_outbox_url (ECalBackendCalDAV *cbdav)
1423 {
1424         ECalBackendCalDAVPrivate *priv;
1425         SoupMessage *message;
1426         xmlOutputBufferPtr buf;
1427         xmlDocPtr doc;
1428         xmlNodePtr root, node;
1429         xmlNsPtr nsdav;
1430         gchar *owner = NULL;
1431
1432         g_return_val_if_fail (cbdav != NULL, FALSE);
1433
1434         priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1435         g_return_val_if_fail (priv != NULL, FALSE);
1436         g_return_val_if_fail (priv->schedule_outbox_url == NULL, TRUE);
1437
1438         /* Prepare the soup message */
1439         message = soup_message_new ("PROPFIND", priv->uri);
1440         if (message == NULL)
1441                 return FALSE;
1442
1443         doc = xmlNewDoc ((xmlChar *) "1.0");
1444         root = xmlNewDocNode (doc, NULL, (xmlChar *) "propfind", NULL);
1445         xmlDocSetRootElement (doc, root);
1446         nsdav = xmlNewNs (root, (xmlChar *) "DAV:", NULL);
1447
1448         node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1449         node = xmlNewTextChild (node, nsdav, (xmlChar *) "owner", NULL);
1450
1451         buf = xmlAllocOutputBuffer (NULL);
1452         xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1453         xmlOutputBufferFlush (buf);
1454
1455         soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1456         soup_message_headers_append (message->request_headers, "Depth", "0");
1457
1458         soup_message_set_request (message,
1459                                   "application/xml",
1460                                   SOUP_MEMORY_COPY,
1461                                   (gchar *) buf->buffer->content,
1462                                   buf->buffer->use);
1463
1464         /* Send the request now */
1465         send_and_handle_redirection (priv->session, message, NULL);
1466
1467         /* Clean up the memory */
1468         xmlOutputBufferClose (buf);
1469         xmlFreeDoc (doc);
1470
1471         /* Check the result */
1472         if (message->status_code == 207 && parse_propfind_response (message, XPATH_OWNER_STATUS, XPATH_OWNER, &owner) && owner && *owner) {
1473                 xmlNsPtr nscd;
1474                 SoupURI *suri;
1475
1476                 g_object_unref (message);
1477
1478                 /* owner is a full path to the user's URL, thus change it in
1479                    calendar's uri when asking for schedule-outbox-URL */
1480                 suri = soup_uri_new (priv->uri);
1481                 soup_uri_set_path (suri, owner);
1482                 g_free (owner);
1483                 owner = soup_uri_to_string (suri, FALSE);
1484                 soup_uri_free (suri);
1485
1486                 message = soup_message_new ("PROPFIND", owner);
1487                 if (message == NULL) {
1488                         g_free (owner);
1489                         return FALSE;
1490                 }
1491
1492                 doc = xmlNewDoc ((xmlChar *) "1.0");
1493                 root = xmlNewDocNode (doc, NULL, (xmlChar *) "propfind", NULL);
1494                 xmlDocSetRootElement (doc, root);
1495                 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", NULL);
1496                 nscd = xmlNewNs (root, (xmlChar *) "urn:ietf:params:xml:ns:caldav", (xmlChar *) "C");
1497
1498                 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1499                 node = xmlNewTextChild (node, nscd, (xmlChar *) "schedule-outbox-URL", NULL);
1500
1501                 buf = xmlAllocOutputBuffer (NULL);
1502                 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1503                 xmlOutputBufferFlush (buf);
1504
1505                 soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1506                 soup_message_headers_append (message->request_headers, "Depth", "0");
1507
1508                 soup_message_set_request (message,
1509                                   "application/xml",
1510                                   SOUP_MEMORY_COPY,
1511                                   (gchar *) buf->buffer->content,
1512                                   buf->buffer->use);
1513
1514                 /* Send the request now */
1515                 send_and_handle_redirection (priv->session, message, NULL);
1516
1517                 if (message->status_code == 207 && parse_propfind_response (message, XPATH_SCHEDULE_OUTBOX_URL_STATUS, XPATH_SCHEDULE_OUTBOX_URL, &priv->schedule_outbox_url)) {
1518                         if (!*priv->schedule_outbox_url) {
1519                                 g_free (priv->schedule_outbox_url);
1520                                 priv->schedule_outbox_url = NULL;
1521                         } else {
1522                                 /* make it a full URI */
1523                                 suri = soup_uri_new (priv->uri);
1524                                 soup_uri_set_path (suri, priv->schedule_outbox_url);
1525                                 g_free (priv->schedule_outbox_url);
1526                                 priv->schedule_outbox_url = soup_uri_to_string (suri, FALSE);
1527                                 soup_uri_free (suri);
1528                         }
1529                 }
1530
1531                 /* Clean up the memory */
1532                 xmlOutputBufferClose (buf);
1533                 xmlFreeDoc (doc);
1534         }
1535
1536         if (message)
1537                 g_object_unref (message);
1538
1539         g_free (owner);
1540
1541         return priv->schedule_outbox_url != NULL;
1542 }
1543
1544 /* ************************************************************************* */
1545 /* Synchronization foo */
1546
1547 struct put_to_cache_data
1548 {
1549         icalcomponent *icomp;
1550         gchar *href;
1551         gchar *etag;
1552 };
1553
1554 static void
1555 put_to_cache_data_and_free_cb (gpointer pdata, gpointer user_data)
1556 {
1557         struct put_to_cache_data *data = pdata;
1558         ECalBackendCalDAV *cbdav = user_data;
1559
1560         g_return_if_fail (data != NULL);
1561         g_return_if_fail (data->icomp != NULL);
1562
1563         put_comp_to_cache (cbdav, data->icomp, data->href, data->etag);
1564
1565         icalcomponent_free (data->icomp);
1566         g_free (data->href);
1567         g_free (data->etag);
1568         g_free (data);
1569 }
1570
1571 static gboolean extract_timezones (ECalBackendCalDAV *cbdav, icalcomponent *icomp);
1572
1573 static gboolean
1574 synchronize_object (ECalBackendCalDAV *cbdav,
1575                     CalDAVObject      *object,
1576                     ECalComponent     *old_comp,
1577                     GList            **created,
1578                     GList            **modified,
1579                     GList            **put_to_cache)
1580 {
1581         ECalBackendCalDAVPrivate *priv;
1582         ECalBackendSyncStatus     result;
1583         ECalBackend              *bkend;
1584         icalcomponent            *icomp, *subcomp;
1585         icalcomponent_kind        kind;
1586         gboolean                  res;
1587
1588         res  = TRUE;
1589         result  = caldav_server_get_object (cbdav, object);
1590
1591         if (result != GNOME_Evolution_Calendar_Success)
1592                 return FALSE;
1593
1594         priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1595
1596         icomp = icalparser_parse_string (object->cdata);
1597         kind  = icalcomponent_isa (icomp);
1598         bkend = E_CAL_BACKEND (cbdav);
1599
1600         extract_timezones (cbdav, icomp);
1601
1602         if (kind == ICAL_VCALENDAR_COMPONENT) {
1603                 ECalComponent *comp;
1604                 struct put_to_cache_data *data;
1605
1606                 kind = e_cal_backend_get_kind (bkend);
1607                 res = FALSE;
1608
1609                 for (subcomp = icalcomponent_get_first_component (icomp, kind);
1610                      subcomp;
1611                      subcomp = icalcomponent_get_next_component (icomp, kind)) {
1612                         res = TRUE;
1613
1614                         comp = e_cal_component_new ();
1615                         if (e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (subcomp))) {
1616                                 ecalcomp_set_href (comp, object->href);
1617                                 ecalcomp_set_etag (comp, object->etag);
1618
1619                                 if (old_comp == NULL) {
1620                                         *created = g_list_prepend (*created, g_object_ref (comp));
1621                                 } else {
1622                                         /* they will be in the opposite order in the list */
1623                                         *modified = g_list_prepend (*modified, g_object_ref (old_comp));
1624                                         *modified = g_list_prepend (*modified, g_object_ref (comp));
1625                                 }
1626                         }
1627
1628                         g_object_unref (comp);
1629                 }
1630
1631                 data = g_new0 (struct put_to_cache_data, 1);
1632                 data->icomp = icomp;
1633                 data->href = g_strdup (object->href);
1634                 data->etag = g_strdup (object->etag);
1635
1636                 *put_to_cache = g_list_prepend (*put_to_cache, data);
1637         } else {
1638                 res = FALSE;
1639                 icalcomponent_free (icomp);
1640         }
1641
1642         return res;
1643 }
1644
1645 #define etags_match(_tag1, _tag2) ((_tag1 == _tag2) ? TRUE :                 \
1646                                    g_str_equal (_tag1 != NULL ? _tag1 : "",  \
1647                                                 _tag2 != NULL ? _tag2 : ""))
1648
1649 static void
1650 synchronize_cache (ECalBackendCalDAV *cbdav)
1651 {
1652         ECalBackendCalDAVPrivate *priv;
1653         ECalBackend              *bkend;
1654         ECalBackendCache         *bcache;
1655         CalDAVObject             *sobjs;
1656         CalDAVObject             *object;
1657         GHashTable               *hindex;
1658         GList                    *cobjs, *created = NULL, *modified = NULL, *put_to_cache = NULL;
1659         GList                    *citer;
1660         gboolean                  res;
1661         gint                      len;
1662         gint                       i;
1663
1664         priv   = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1665         bkend  = E_CAL_BACKEND (cbdav);
1666         bcache = priv->cache;
1667         len    = 0;
1668         sobjs  = NULL;
1669
1670         if (!check_calendar_changed_on_server (cbdav)) {
1671                 /* no changes on the server, no update required */
1672                 return;
1673         }
1674
1675         res = caldav_server_list_objects (cbdav, &sobjs, &len);
1676
1677         if (!res)
1678                 return;
1679
1680         hindex = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1681         cobjs = e_cal_backend_cache_get_components (bcache);
1682
1683         /* build up a index for the href entry */
1684         for (citer = cobjs; citer; citer = g_list_next (citer)) {
1685                 ECalComponent *ccomp = E_CAL_COMPONENT (citer->data);
1686                 gchar *href;
1687
1688                 href = ecalcomp_get_href (ccomp);
1689
1690                 if (href == NULL) {
1691                         g_warning ("href of object NULL :(");
1692                         continue;
1693                 }
1694
1695                 /* prefer master object from a detached instance */
1696                 if (g_hash_table_lookup (hindex, href) && e_cal_component_is_instance (ccomp)) {
1697                         g_free (href);
1698                         continue;
1699                 }
1700
1701                 g_hash_table_insert (hindex, (gpointer) href, ccomp);
1702         }
1703
1704         /* see if we have to update or add some objects */
1705         for (i = 0, object = sobjs; i < len; i++, object++) {
1706                 ECalComponent *ccomp;
1707                 gchar *etag = NULL;
1708
1709                 if (object->status != 200) {
1710                         /* just continue here, so that the object
1711                          * doesnt get removed from the cobjs list
1712                          * - therefore it will be removed */
1713                         continue;
1714                 }
1715
1716                 res = TRUE;
1717                 ccomp = g_hash_table_lookup (hindex, object->href);
1718
1719                 if (ccomp != NULL) {
1720                         etag = ecalcomp_get_etag (ccomp);
1721                 }
1722
1723                 if (!etag || !etags_match (etag, object->etag)) {
1724                         res = synchronize_object (cbdav, object, ccomp, &created, &modified, &put_to_cache);
1725                 }
1726
1727                 if (res) {
1728                         cobjs = g_list_remove (cobjs, ccomp);
1729                         if (ccomp)
1730                                 g_object_unref (ccomp);
1731                 }
1732
1733                 caldav_object_free (object, FALSE);
1734                 g_free (etag);
1735         }
1736
1737         /* remove old (not on server anymore) items from cache... */
1738         for (citer = cobjs; citer; citer = g_list_next (citer)) {
1739                 ECalComponent *comp = E_CAL_COMPONENT (citer->data);
1740
1741                 /* keep detached instances in a cache, they will be removed with the master object */
1742                 if (!e_cal_component_is_instance (comp)) {
1743                         const gchar *uid = NULL;
1744
1745                         e_cal_component_get_uid (comp, &uid);
1746
1747                         if (remove_comp_from_cache (cbdav, uid, NULL)) {
1748                                 gchar *str = e_cal_component_get_as_string (comp);
1749                                 ECalComponentId *id = e_cal_component_get_id (comp);
1750
1751                                 e_cal_backend_notify_object_removed (E_CAL_BACKEND (cbdav), id, str, NULL);
1752                                 e_cal_component_free_id (id);
1753                                 g_free (str);
1754                         }
1755                 }
1756
1757                 g_object_unref (comp);
1758         }
1759
1760         /* ... then update cache with new objects... */
1761         g_list_foreach (put_to_cache, put_to_cache_data_and_free_cb, cbdav);
1762
1763         /* ... then notify created ... */
1764         for (citer = created; citer; citer = citer->next) {
1765                 ECalComponent *comp = citer->data;
1766                 gchar *comp_str = e_cal_component_get_as_string (comp);
1767
1768                 e_cal_backend_notify_object_created (bkend, comp_str);
1769
1770                 g_free (comp_str);
1771                 g_object_unref (comp);
1772         }
1773
1774         /* ... and modified components */
1775         for (citer = modified; citer; citer = citer->next) {
1776                 ECalComponent *comp, *old_comp;
1777                 gchar *new_str, *old_str;
1778
1779                 /* always even number of items in the 'modified' list */
1780                 comp = citer->data;
1781                 citer = citer->next;
1782                 old_comp = citer->data;
1783
1784                 new_str = e_cal_component_get_as_string (comp);
1785                 old_str = e_cal_component_get_as_string (old_comp);
1786
1787                 e_cal_backend_notify_object_modified (bkend, old_str, new_str);
1788
1789                 g_free (new_str);
1790                 g_free (old_str);
1791                 g_object_unref (comp);
1792                 g_object_unref (old_comp);
1793         }
1794
1795         g_hash_table_destroy (hindex);
1796         g_list_free (cobjs);
1797         g_list_free (created);
1798         g_list_free (modified);
1799         g_list_free (put_to_cache);
1800 }
1801
1802 /* ************************************************************************* */
1803 static gpointer
1804 synch_slave_loop (gpointer data)
1805 {
1806         ECalBackendCalDAVPrivate *priv;
1807         ECalBackendCalDAV        *cbdav;
1808
1809         cbdav = E_CAL_BACKEND_CALDAV (data);
1810         priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1811
1812         g_mutex_lock (priv->lock);
1813
1814         while (priv->slave_cmd != SLAVE_SHOULD_DIE) {
1815                 GTimeVal alarm_clock;
1816                 if (priv->slave_cmd == SLAVE_SHOULD_SLEEP) {
1817                         /* just sleep until we get woken up again */
1818                         g_cond_wait (priv->cond, priv->lock);
1819
1820                         /* check if we should die, work or sleep again */
1821                         continue;
1822                 }
1823
1824                 /* Ok here we go, do some real work
1825                  * Synch it baby one more time ...
1826                  */
1827                 synchronize_cache (cbdav);
1828
1829                 /* puhh that was hard, get some rest :) */
1830                 g_get_current_time (&alarm_clock);
1831                 alarm_clock.tv_sec += priv->refresh_time.tv_sec;
1832                 g_cond_timed_wait (priv->cond,
1833                                    priv->lock,
1834                                    &alarm_clock);
1835
1836         }
1837
1838         /* signal we are done */
1839         g_cond_signal (priv->slave_gone_cond);
1840
1841         priv->synch_slave = NULL;
1842
1843         /* we got killed ... */
1844         g_mutex_unlock (priv->lock);
1845         return NULL;
1846 }
1847
1848 /* ************************************************************************* */
1849 /* ********** ECalBackendSync virtual function implementation *************  */
1850
1851 static ECalBackendSyncStatus
1852 caldav_is_read_only (ECalBackendSync *backend,
1853                      EDataCal        *cal,
1854                      gboolean        *read_only)
1855 {
1856         ECalBackendCalDAV        *cbdav;
1857         ECalBackendCalDAVPrivate *priv;
1858
1859         cbdav = E_CAL_BACKEND_CALDAV (backend);
1860         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1861
1862         /* no write support in offline mode yet! */
1863         if (priv->mode == CAL_MODE_LOCAL) {
1864                 *read_only = TRUE;
1865         } else {
1866                 *read_only = priv->read_only;
1867         }
1868
1869         return GNOME_Evolution_Calendar_Success;
1870 }
1871
1872
1873 static ECalBackendSyncStatus
1874 caldav_get_cal_address (ECalBackendSync  *backend,
1875                         EDataCal         *cal,
1876                         gchar            **address)
1877 {
1878         *address = NULL;
1879         return GNOME_Evolution_Calendar_Success;
1880 }
1881
1882
1883
1884 static ECalBackendSyncStatus
1885 caldav_get_ldap_attribute (ECalBackendSync  *backend,
1886                            EDataCal         *cal,
1887                            gchar           **attribute)
1888 {
1889         *attribute = NULL;
1890         return GNOME_Evolution_Calendar_Success;
1891 }
1892
1893 static ECalBackendSyncStatus
1894 caldav_get_alarm_email_address (ECalBackendSync  *backend,
1895                                 EDataCal         *cal,
1896                                 gchar            **address)
1897 {
1898         *address = NULL;
1899         return GNOME_Evolution_Calendar_Success;
1900 }
1901
1902 static ECalBackendSyncStatus
1903 caldav_get_static_capabilities (ECalBackendSync  *backend,
1904                                 EDataCal         *cal,
1905                                 gchar            **capabilities)
1906 {
1907         *capabilities = g_strdup (CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS ","
1908                                   CAL_STATIC_CAPABILITY_NO_THISANDFUTURE ","
1909                                   CAL_STATIC_CAPABILITY_NO_THISANDPRIOR);
1910
1911         return GNOME_Evolution_Calendar_Success;
1912 }
1913
1914 static ECalBackendSyncStatus
1915 initialize_backend (ECalBackendCalDAV *cbdav)
1916 {
1917         ECalBackendSyncStatus     result;
1918         ECalBackendCalDAVPrivate *priv;
1919         ESource                  *source;
1920         const gchar              *os_val;
1921         const gchar               *uri;
1922         gsize                     len;
1923         const gchar               *refresh;
1924
1925         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
1926
1927         result = GNOME_Evolution_Calendar_Success;
1928         source = e_cal_backend_get_source (E_CAL_BACKEND (cbdav));
1929
1930         os_val = e_source_get_property (source, "offline_sync");
1931
1932         if (!os_val || !g_str_equal (os_val, "1")) {
1933                 priv->do_offline = FALSE;
1934         }
1935
1936         os_val = e_source_get_property (source, "auth");
1937         priv->need_auth = os_val != NULL;
1938
1939         os_val = e_source_get_property(source, "ssl");
1940         uri = e_cal_backend_get_uri (E_CAL_BACKEND (cbdav));
1941
1942         g_free (priv->uri);
1943         priv->uri = NULL;
1944         if (g_str_has_prefix (uri, "caldav://")) {
1945                 const gchar *proto;
1946
1947                 if (os_val && os_val[0] == '1') {
1948                         proto = "https://";
1949                 } else {
1950                         proto = "http://";
1951                 }
1952
1953                 priv->uri = g_strconcat (proto, uri + 9, NULL);
1954         } else {
1955                 priv->uri = g_strdup (uri);
1956         }
1957
1958         if (priv->uri) {
1959                 SoupURI *suri = soup_uri_new (priv->uri);
1960
1961                 /* properly encode uri */
1962                 if (suri && suri->path) {
1963                         gchar *tmp = soup_uri_encode (suri->path, NULL);
1964                         gchar *path = soup_uri_normalize (tmp, "/");
1965
1966                         soup_uri_set_path (suri, path);
1967
1968                         g_free (tmp);
1969                         g_free (path);
1970                         g_free (priv->uri);
1971
1972                         priv->uri = soup_uri_to_string (suri, FALSE);
1973                 }
1974
1975                 soup_uri_free (suri);
1976         }
1977
1978         /* remove trailing slashes... */
1979         len = strlen (priv->uri);
1980         while (len--) {
1981                 if (priv->uri[len] == '/') {
1982                         priv->uri[len] = '\0';
1983                 } else {
1984                         break;
1985                 }
1986         }
1987
1988         /* ...and append exactly one slash */
1989         if (priv->uri && *priv->uri) {
1990                 gchar *tmp = priv->uri;
1991
1992                 priv->uri = g_strconcat (priv->uri, "/", NULL);
1993
1994                 g_free (tmp);
1995         }
1996
1997         if (priv->cache == NULL) {
1998                 ECalSourceType source_type;
1999
2000                 switch (e_cal_backend_get_kind (E_CAL_BACKEND (cbdav))) {
2001                         default:
2002                         case ICAL_VEVENT_COMPONENT:
2003                                 source_type = E_CAL_SOURCE_TYPE_EVENT;
2004                                 break;
2005                         case ICAL_VTODO_COMPONENT:
2006                                 source_type = E_CAL_SOURCE_TYPE_TODO;
2007                                 break;
2008                         case ICAL_VJOURNAL_COMPONENT:
2009                                 source_type = E_CAL_SOURCE_TYPE_JOURNAL;
2010                                 break;
2011                 }
2012
2013                 priv->cache = e_cal_backend_cache_new (priv->uri, source_type);
2014
2015                 if (priv->cache == NULL) {
2016                         result = GNOME_Evolution_Calendar_OtherError;
2017                         goto out;
2018                 }
2019
2020         }
2021
2022         refresh = e_source_get_property (source, "refresh");
2023         priv->refresh_time.tv_sec  = (refresh && atoi (refresh) > 0) ? (60 * atoi (refresh)) : (DEFAULT_REFRESH_TIME);
2024
2025         if (!priv->synch_slave) {
2026                 GThread *slave;
2027
2028                 priv->slave_cmd = SLAVE_SHOULD_SLEEP;
2029                 slave = g_thread_create (synch_slave_loop, cbdav, FALSE, NULL);
2030
2031                 if (slave == NULL) {
2032                         g_warning ("Could not create synch slave");
2033                         result = GNOME_Evolution_Calendar_OtherError;
2034                 }
2035
2036                 priv->synch_slave = slave;
2037         }
2038 out:
2039         return result;
2040 }
2041
2042 static void
2043 proxy_settings_changed (EProxy *proxy, gpointer user_data)
2044 {
2045         SoupURI *proxy_uri = NULL;
2046         ECalBackendCalDAVPrivate *priv = (ECalBackendCalDAVPrivate *) user_data;
2047
2048         if (!priv || !priv->uri || !priv->session)
2049                 return;
2050
2051         /* use proxy if necessary */
2052         if (e_proxy_require_proxy_for_uri (proxy, priv->uri)) {
2053                 proxy_uri = e_proxy_peek_uri_for (proxy, priv->uri);
2054         }
2055
2056         g_object_set (priv->session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL);
2057 }
2058
2059 static ECalBackendSyncStatus
2060 caldav_do_open (ECalBackendSync *backend,
2061                 EDataCal        *cal,
2062                 gboolean         only_if_exists,
2063                 const gchar      *username,
2064                 const gchar      *password)
2065 {
2066         ECalBackendCalDAV        *cbdav;
2067         ECalBackendCalDAVPrivate *priv;
2068         ECalBackendSyncStatus     status;
2069
2070         cbdav = E_CAL_BACKEND_CALDAV (backend);
2071         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2072
2073         status = GNOME_Evolution_Calendar_Success;
2074
2075         g_mutex_lock (priv->lock);
2076
2077         /* let it decide the 'getctag' extension availability again */
2078         priv->ctag_supported = TRUE;
2079
2080         if (!priv->loaded) {
2081                 status = initialize_backend (cbdav);
2082         }
2083
2084         if (status != GNOME_Evolution_Calendar_Success) {
2085                 g_mutex_unlock (priv->lock);
2086                 return status;
2087         }
2088
2089         if (priv->need_auth) {
2090                 if ((username == NULL || password == NULL)) {
2091                         g_mutex_unlock (priv->lock);
2092                         return GNOME_Evolution_Calendar_AuthenticationRequired;
2093                 }
2094
2095                 g_free (priv->username);
2096                 priv->username = g_strdup (username);
2097                 g_free (priv->password);
2098                 priv->password = g_strdup (password);
2099         }
2100
2101         if (!priv->do_offline && priv->mode == CAL_MODE_LOCAL) {
2102                 g_mutex_unlock (priv->lock);
2103                 return GNOME_Evolution_Calendar_RepositoryOffline;
2104         }
2105
2106         priv->loaded = TRUE;
2107
2108         if (priv->mode == CAL_MODE_REMOTE) {
2109                 /* set forward proxy */
2110                 proxy_settings_changed (priv->proxy, priv);
2111
2112                 status = caldav_server_open_calendar (cbdav);
2113
2114                 if (status == GNOME_Evolution_Calendar_Success) {
2115                         priv->slave_cmd = SLAVE_SHOULD_WORK;
2116                         g_cond_signal (priv->cond);
2117                 }
2118         } else {
2119                 priv->read_only = TRUE;
2120         }
2121
2122         g_mutex_unlock (priv->lock);
2123
2124         return status;
2125 }
2126
2127 static ECalBackendSyncStatus
2128 caldav_remove (ECalBackendSync *backend,
2129                EDataCal        *cal)
2130 {
2131         ECalBackendCalDAV        *cbdav;
2132         ECalBackendCalDAVPrivate *priv;
2133         ECalBackendSyncStatus     status;
2134         gboolean                  online;
2135
2136         cbdav = E_CAL_BACKEND_CALDAV (backend);
2137         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2138
2139         g_mutex_lock (priv->lock);
2140
2141         if (!priv->loaded) {
2142                 g_mutex_unlock (priv->lock);
2143                 return GNOME_Evolution_Calendar_Success;
2144         }
2145
2146         status = check_state (cbdav, &online);
2147
2148         /* lie here a bit, but otherwise the calendar will not be removed, even it should */
2149         if (status != GNOME_Evolution_Calendar_Success)
2150                 g_print (G_STRLOC ": %s", e_cal_backend_status_to_string (status));
2151
2152         e_file_cache_remove (E_FILE_CACHE (priv->cache));
2153         priv->cache  = NULL;
2154         priv->loaded = FALSE;
2155         priv->slave_cmd = SLAVE_SHOULD_DIE;
2156
2157         if (priv->synch_slave) {
2158                 g_cond_signal (priv->cond);
2159
2160                 /* wait until the slave died */
2161                 g_cond_wait (priv->slave_gone_cond, priv->lock);
2162         }
2163
2164         g_mutex_unlock (priv->lock);
2165
2166         return GNOME_Evolution_Calendar_Success;
2167 }
2168
2169 static void
2170 remove_comp_from_cache_cb (gpointer value, gpointer user_data)
2171 {
2172         ECalComponent *comp = value;
2173         ECalBackendCache *cache = user_data;
2174         ECalComponentId *id;
2175
2176         g_return_if_fail (comp != NULL);
2177         g_return_if_fail (cache != NULL);
2178
2179         id = e_cal_component_get_id (comp);
2180         g_return_if_fail (id != NULL);
2181
2182         e_cal_backend_cache_remove_component (cache, id->uid, id->rid);
2183         e_cal_component_free_id (id);
2184 }
2185
2186 static gboolean
2187 remove_comp_from_cache (ECalBackendCalDAV *cbdav, const gchar *uid, const gchar *rid)
2188 {
2189         ECalBackendCalDAVPrivate *priv;
2190         gboolean res = FALSE;
2191
2192         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2193
2194         if (!rid || !*rid) {
2195                 /* get with detached instances */
2196                 GSList *objects = e_cal_backend_cache_get_components_by_uid (priv->cache, uid);
2197
2198                 if (objects) {
2199                         g_slist_foreach (objects, (GFunc)remove_comp_from_cache_cb, priv->cache);
2200                         g_slist_foreach (objects, (GFunc)g_object_unref, NULL);
2201                         g_slist_free (objects);
2202
2203                         res = TRUE;
2204                 }
2205         } else {
2206                 res = e_cal_backend_cache_remove_component (priv->cache, uid, rid);
2207         }
2208
2209         return res;
2210 }
2211
2212 static void
2213 add_detached_recur_to_vcalendar_cb (gpointer value, gpointer user_data)
2214 {
2215         icalcomponent *recurrence = e_cal_component_get_icalcomponent (value);
2216         icalcomponent *vcalendar = user_data;
2217
2218         icalcomponent_add_component (
2219                 vcalendar,
2220                 icalcomponent_new_clone (recurrence));
2221 }
2222
2223 static gint
2224 sort_master_first (gconstpointer a, gconstpointer b)
2225 {
2226         icalcomponent *ca, *cb;
2227
2228         ca = e_cal_component_get_icalcomponent ((ECalComponent *)a);
2229         cb = e_cal_component_get_icalcomponent ((ECalComponent *)b);
2230
2231         if (!ca) {
2232                 if (!cb)
2233                         return 0;
2234                 else
2235                         return -1;
2236         } else if (!cb) {
2237                 return 1;
2238         }
2239
2240         return icaltime_compare (icalcomponent_get_recurrenceid (ca), icalcomponent_get_recurrenceid (cb));
2241 }
2242
2243 /* Returns new icalcomponent, with all detached instances stored in a cache.
2244    The cache lock should be locked when called this function.
2245 */
2246 static icalcomponent *
2247 get_comp_from_cache (ECalBackendCalDAV *cbdav, const gchar *uid, const gchar *rid, gchar **href, gchar **etag)
2248 {
2249         ECalBackendCalDAVPrivate *priv;
2250         icalcomponent *icalcomp = NULL;
2251
2252         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2253
2254         if (rid == NULL || !*rid) {
2255                 /* get with detached instances */
2256                 GSList *objects = e_cal_backend_cache_get_components_by_uid (priv->cache, uid);
2257
2258                 if (!objects) {
2259                         return NULL;
2260                 }
2261
2262                 if (g_slist_length (objects) == 1) {
2263                         ECalComponent *comp = objects->data;
2264
2265                         /* will be unreffed a bit later */
2266                         if (comp)
2267                                 icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
2268                 } else {
2269                         /* if we have detached recurrences, return a VCALENDAR */
2270                         icalcomp = e_cal_util_new_top_level ();
2271
2272                         objects = g_slist_sort (objects, sort_master_first);
2273
2274                         /* add all detached recurrences and the master object */
2275                         g_slist_foreach (objects, add_detached_recur_to_vcalendar_cb, icalcomp);
2276                 }
2277
2278                 /* every component has set same href and etag, thus it doesn't matter where it will be read */
2279                 if (href)
2280                         *href = ecalcomp_get_href (objects->data);
2281                 if (etag)
2282                         *etag = ecalcomp_get_etag (objects->data);
2283
2284                 g_slist_foreach (objects, (GFunc)g_object_unref, NULL);
2285                 g_slist_free (objects);
2286         } else {
2287                 /* get the exact object */
2288                 ECalComponent *comp = e_cal_backend_cache_get_component (priv->cache, uid, rid);
2289
2290                 if (comp) {
2291                         icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
2292                         if (href)
2293                                 *href = ecalcomp_get_href (comp);
2294                         if (etag)
2295                                 *etag = ecalcomp_get_etag (comp);
2296                         g_object_unref (comp);
2297                 }
2298         }
2299
2300         return icalcomp;
2301 }
2302
2303 static gboolean
2304 put_comp_to_cache (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp, const gchar *href, const gchar *etag)
2305 {
2306         ECalBackendCalDAVPrivate *priv;
2307         icalcomponent_kind my_kind;
2308         ECalComponent *comp;
2309         gboolean res = FALSE;
2310
2311         g_return_val_if_fail (cbdav != NULL, FALSE);
2312         g_return_val_if_fail (icalcomp != NULL, FALSE);
2313
2314         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2315
2316         my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
2317         comp = e_cal_component_new ();
2318
2319         if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
2320                 icalcomponent *subcomp;
2321
2322                 /* remove all old components from the cache first */
2323                 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
2324                      subcomp;
2325                      subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
2326                         remove_comp_from_cache (cbdav, icalcomponent_get_uid (subcomp), NULL);
2327                 }
2328
2329                 /* then put new. It's because some detached instances could be removed on the server. */
2330                 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
2331                      subcomp;
2332                      subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
2333                         /* because reusing the same comp doesn't clear recur_id member properly */
2334                         g_object_unref (comp);
2335                         comp = e_cal_component_new ();
2336
2337                         if (e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (subcomp))) {
2338                                 if (href)
2339                                         ecalcomp_set_href (comp, href);
2340                                 if (etag)
2341                                         ecalcomp_set_etag (comp, etag);
2342
2343                                 if (e_cal_backend_cache_put_component (priv->cache, comp))
2344                                         res = TRUE;
2345                         }
2346                 }
2347         } else if (icalcomponent_isa (icalcomp) == my_kind) {
2348                 remove_comp_from_cache (cbdav, icalcomponent_get_uid (icalcomp), NULL);
2349
2350                 if (e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (icalcomp))) {
2351                         if (href)
2352                                 ecalcomp_set_href (comp, href);
2353                         if (etag)
2354                                 ecalcomp_set_etag (comp, etag);
2355
2356                         res = e_cal_backend_cache_put_component (priv->cache, comp);
2357                 }
2358         }
2359
2360         g_object_unref (comp);
2361
2362         return res;
2363 }
2364
2365 static void
2366 remove_property (gpointer prop, gpointer icomp)
2367 {
2368         icalcomponent_remove_property (icomp, prop);
2369         icalproperty_free (prop);
2370 }
2371
2372 static void
2373 strip_x_evolution_caldav (icalcomponent *icomp)
2374 {
2375         icalproperty *prop;
2376         GSList *to_remove = NULL;
2377
2378         g_return_if_fail (icomp != NULL);
2379         g_return_if_fail (icalcomponent_isa (icomp) != ICAL_VCALENDAR_COMPONENT);
2380
2381         for (prop = icalcomponent_get_first_property (icomp, ICAL_X_PROPERTY);
2382              prop;
2383              prop = icalcomponent_get_next_property (icomp, ICAL_X_PROPERTY)) {
2384                 if (g_str_has_prefix (icalproperty_get_x_name (prop), X_E_CALDAV)) {
2385                         to_remove = g_slist_prepend (to_remove, prop);
2386                 }
2387         }
2388
2389         g_slist_foreach (to_remove, remove_property, icomp);
2390         g_slist_free (to_remove);
2391 }
2392
2393 /* callback for icalcomponent_foreach_tzid */
2394 typedef struct {
2395         ECalBackendCache *cache;
2396         icalcomponent *vcal_comp;
2397         icalcomponent *icalcomp;
2398 } ForeachTzidData;
2399
2400 static void
2401 add_timezone_cb (icalparameter *param, gpointer data)
2402 {
2403         icaltimezone *tz;
2404         const gchar *tzid;
2405         icalcomponent *vtz_comp;
2406         ForeachTzidData *f_data = (ForeachTzidData *) data;
2407
2408         tzid = icalparameter_get_tzid (param);
2409         if (!tzid)
2410                 return;
2411
2412         tz = icalcomponent_get_timezone (f_data->vcal_comp, tzid);
2413         if (tz)
2414                 return;
2415
2416         tz = icalcomponent_get_timezone (f_data->icalcomp, tzid);
2417         if (!tz) {
2418                 tz = icaltimezone_get_builtin_timezone_from_tzid (tzid);
2419                 if (!tz)
2420                         tz = (icaltimezone *) e_cal_backend_cache_get_timezone (f_data->cache, tzid);
2421                 if (!tz)
2422                         return;
2423         }
2424
2425         vtz_comp = icaltimezone_get_component (tz);
2426         if (!vtz_comp)
2427                 return;
2428
2429         icalcomponent_add_component (f_data->vcal_comp,
2430                                      icalcomponent_new_clone (vtz_comp));
2431 }
2432
2433 static void
2434 add_timezones_from_component (ECalBackendCalDAV *cbdav, icalcomponent *vcal_comp, icalcomponent *icalcomp)
2435 {
2436         ForeachTzidData f_data;
2437         ECalBackendCalDAVPrivate *priv;
2438
2439         g_return_if_fail (cbdav != NULL);
2440         g_return_if_fail (vcal_comp != NULL);
2441         g_return_if_fail (icalcomp != NULL);
2442
2443         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2444
2445         f_data.cache = priv->cache;
2446         f_data.vcal_comp = vcal_comp;
2447         f_data.icalcomp = icalcomp;
2448
2449         icalcomponent_foreach_tzid (icalcomp, add_timezone_cb, &f_data);
2450 }
2451
2452 /* also removes X-EVOLUTION-CALDAV from all the components */
2453 static gchar *
2454 pack_cobj (ECalBackendCalDAV *cbdav, icalcomponent *icomp)
2455 {
2456         ECalBackendCalDAVPrivate *priv;
2457         icalcomponent *calcomp;
2458         gchar          *objstr;
2459
2460         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2461
2462         if (icalcomponent_isa (icomp) != ICAL_VCALENDAR_COMPONENT) {
2463                 icalcomponent *cclone;
2464
2465                 calcomp = e_cal_util_new_top_level ();
2466
2467                 cclone = icalcomponent_new_clone (icomp);
2468                 strip_x_evolution_caldav (cclone);
2469                 icalcomponent_add_component (calcomp, cclone);
2470                 add_timezones_from_component (cbdav, calcomp, cclone);
2471         } else {
2472                 icalcomponent *subcomp;
2473                 icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
2474
2475                 calcomp = icalcomponent_new_clone (icomp);
2476                 for (subcomp = icalcomponent_get_first_component (calcomp, my_kind);
2477                      subcomp;
2478                      subcomp = icalcomponent_get_next_component (calcomp, my_kind)) {
2479                         strip_x_evolution_caldav (subcomp);
2480                         add_timezones_from_component (cbdav, calcomp, subcomp);
2481                 }
2482         }
2483
2484         objstr = icalcomponent_as_ical_string_r (calcomp);
2485         icalcomponent_free (calcomp);
2486
2487         g_assert (objstr);
2488
2489         return objstr;
2490
2491 }
2492
2493 static void
2494 sanitize_component (ECalBackend *cb, ECalComponent *comp)
2495 {
2496         ECalComponentDateTime dt;
2497         icaltimezone *zone, *default_zone;
2498
2499         /* Check dtstart, dtend and due's timezone, and convert it to local
2500          * default timezone if the timezone is not in our builtin timezone
2501          * list */
2502         e_cal_component_get_dtstart (comp, &dt);
2503         if (dt.value && dt.tzid) {
2504                 zone = caldav_internal_get_timezone (cb, dt.tzid);
2505                 if (!zone) {
2506                         default_zone = caldav_internal_get_default_timezone (cb);
2507                         g_free ((gchar *)dt.tzid);
2508                         dt.tzid = g_strdup (icaltimezone_get_tzid (default_zone));
2509                         e_cal_component_set_dtstart (comp, &dt);
2510                 }
2511         }
2512         e_cal_component_free_datetime (&dt);
2513
2514         e_cal_component_get_dtend (comp, &dt);
2515         if (dt.value && dt.tzid) {
2516                 zone = caldav_internal_get_timezone (cb, dt.tzid);
2517                 if (!zone) {
2518                         default_zone = caldav_internal_get_default_timezone (cb);
2519                         g_free ((gchar *)dt.tzid);
2520                         dt.tzid = g_strdup (icaltimezone_get_tzid (default_zone));
2521                         e_cal_component_set_dtend (comp, &dt);
2522                 }
2523         }
2524         e_cal_component_free_datetime (&dt);
2525
2526         e_cal_component_get_due (comp, &dt);
2527         if (dt.value && dt.tzid) {
2528                 zone = caldav_internal_get_timezone (cb, dt.tzid);
2529                 if (!zone) {
2530                         default_zone = caldav_internal_get_default_timezone (cb);
2531                         g_free ((gchar *)dt.tzid);
2532                         dt.tzid = g_strdup (icaltimezone_get_tzid (default_zone));
2533                         e_cal_component_set_due (comp, &dt);
2534                 }
2535         }
2536         e_cal_component_free_datetime (&dt);
2537         e_cal_component_abort_sequence (comp);
2538 }
2539
2540 static gboolean
2541 cache_contains (ECalBackendCalDAV *cbdav, const gchar *uid, const gchar *rid)
2542 {
2543         ECalBackendCalDAVPrivate *priv;
2544         gboolean res;
2545         ECalComponent *comp;
2546
2547         g_return_val_if_fail (cbdav != NULL, FALSE);
2548         g_return_val_if_fail (uid != NULL, FALSE);
2549
2550         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2551         g_return_val_if_fail (priv != NULL && priv->cache != NULL, FALSE);
2552
2553         comp = e_cal_backend_cache_get_component (priv->cache, uid, rid);
2554         res = comp != NULL;
2555
2556         if (comp)
2557                 g_object_unref (comp);
2558
2559         return res;
2560 }
2561
2562 /* Returns subcomponent of icalcomp, which is a master object, or icalcomp itself, if it's not a VCALENDAR;
2563    Do not free returned pointer, it'll be freed together with the icalcomp.
2564 */
2565 static icalcomponent *
2566 get_master_comp (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp)
2567 {
2568         icalcomponent *master = icalcomp;
2569
2570         g_return_val_if_fail (cbdav != NULL, NULL);
2571         g_return_val_if_fail (icalcomp != NULL, NULL);
2572
2573         if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
2574                 icalcomponent *subcomp;
2575                 icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
2576
2577                 master = NULL;
2578
2579                 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
2580                      subcomp;
2581                      subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
2582                         struct icaltimetype sub_rid = icalcomponent_get_recurrenceid (subcomp);
2583
2584                         if (icaltime_is_null_time (sub_rid)) {
2585                                 master = subcomp;
2586                                 break;
2587                         }
2588                 }
2589         }
2590
2591         return master;
2592 }
2593
2594 static gboolean
2595 remove_instance (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp, struct icaltimetype rid, CalObjModType mod, gboolean also_exdate)
2596 {
2597         icalcomponent *master = icalcomp;
2598         gboolean res = FALSE;
2599
2600         g_return_val_if_fail (icalcomp != NULL, res);
2601         g_return_val_if_fail (!icaltime_is_null_time (rid), res);
2602
2603         /* remove an instance only */
2604         if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
2605                 icalcomponent *subcomp;
2606                 icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
2607                 gint left = 0;
2608                 gboolean start_first = FALSE;
2609
2610                 master = NULL;
2611
2612                 /* remove old instance first */
2613                 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
2614                      subcomp;
2615                      subcomp = start_first ? icalcomponent_get_first_component (icalcomp, my_kind) : icalcomponent_get_next_component (icalcomp, my_kind)) {
2616                         struct icaltimetype sub_rid = icalcomponent_get_recurrenceid (subcomp);
2617
2618                         start_first = FALSE;
2619
2620                         if (icaltime_is_null_time (sub_rid)) {
2621                                 master = subcomp;
2622                                 left++;
2623                         } else if (icaltime_compare (sub_rid, rid) == 0) {
2624                                 icalcomponent_remove_component (icalcomp, subcomp);
2625                                 icalcomponent_free (subcomp);
2626                                 if (master) {
2627                                         break;
2628                                 } else {
2629                                         /* either no master or master not as the first component, thus rescan */
2630                                         left = 0;
2631                                         start_first = TRUE;
2632                                 }
2633                         } else {
2634                                 left++;
2635                         }
2636                 }
2637
2638                 /* whether left at least one instance or a master object */
2639                 res = left > 0;
2640         } else {
2641                 res = TRUE;
2642         }
2643
2644         if (master && also_exdate) {
2645                 e_cal_util_remove_instances (master, rid, mod);
2646         }
2647
2648         return res;
2649 }
2650
2651 static icalcomponent *
2652 replace_master (ECalBackendCalDAV *cbdav, icalcomponent *old_comp, icalcomponent *new_master)
2653 {
2654         icalcomponent *old_master;
2655         if (icalcomponent_isa (old_comp) != ICAL_VCALENDAR_COMPONENT) {
2656                 icalcomponent_free (old_comp);
2657                 return new_master;
2658         }
2659
2660         old_master = get_master_comp (cbdav, old_comp);
2661         if (!old_master) {
2662                 /* no master, strange */
2663                 icalcomponent_free (new_master);
2664         } else {
2665                 icalcomponent_remove_component (old_comp, old_master);
2666                 icalcomponent_free (old_master);
2667
2668                 icalcomponent_add_component (old_comp, new_master);
2669         }
2670
2671         return old_comp;
2672 }
2673
2674 /* priv->lock is supposed to be locked already, when calling this function */
2675 static ECalBackendSyncStatus
2676 do_create_object (ECalBackendCalDAV *cbdav, gchar **calobj, gchar **uid)
2677 {
2678         ECalBackendCalDAVPrivate *priv;
2679         ECalBackendSyncStatus     status;
2680         ECalComponent            *comp;
2681         gboolean                  online;
2682         struct icaltimetype current;
2683         icalcomponent *icalcomp;
2684         const gchar *comp_uid;
2685
2686         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2687
2688         status = check_state (cbdav, &online);
2689
2690         if (status != GNOME_Evolution_Calendar_Success) {
2691                 return status;
2692         }
2693
2694         comp = e_cal_component_new_from_string (*calobj);
2695
2696         if (comp == NULL) {
2697                 return GNOME_Evolution_Calendar_InvalidObject;
2698         }
2699
2700         icalcomp = e_cal_component_get_icalcomponent (comp);
2701         if (icalcomp == NULL) {
2702                 g_object_unref (comp);
2703                 return GNOME_Evolution_Calendar_InvalidObject;
2704         }
2705
2706         comp_uid = icalcomponent_get_uid (icalcomp);
2707         if (!comp_uid) {
2708                 gchar *new_uid;
2709
2710                 new_uid = e_cal_component_gen_uid ();
2711                 if (!new_uid) {
2712                         g_object_unref (comp);
2713                         return GNOME_Evolution_Calendar_InvalidObject;
2714                 }
2715
2716                 icalcomponent_set_uid (icalcomp, new_uid);
2717                 comp_uid = icalcomponent_get_uid (icalcomp);
2718
2719                 g_free (new_uid);
2720         }
2721
2722         /* check the object is not in our cache */
2723         if (cache_contains (cbdav, comp_uid, NULL)) {
2724                 g_object_unref (comp);
2725                 return GNOME_Evolution_Calendar_ObjectIdAlreadyExists;
2726         }
2727
2728         /* Set the created and last modified times on the component */
2729         current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
2730         e_cal_component_set_created (comp, &current);
2731         e_cal_component_set_last_modified (comp, &current);
2732
2733         /* sanitize the component*/
2734         sanitize_component ((ECalBackend *)cbdav, comp);
2735
2736         if (online) {
2737                 CalDAVObject object;
2738
2739                 object.href  = ecalcomp_gen_href (comp);
2740                 object.etag  = NULL;
2741                 object.cdata = pack_cobj (cbdav, icalcomp);
2742
2743                 status = caldav_server_put_object (cbdav, &object, icalcomp);
2744
2745                 caldav_object_free (&object, FALSE);
2746         } else {
2747                 /* mark component as out of synch */
2748                 /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_CREATED); */
2749         }
2750
2751         if (status == GNOME_Evolution_Calendar_Success) {
2752                 if (uid)
2753                         *uid = g_strdup (comp_uid);
2754
2755                 icalcomp = get_comp_from_cache (cbdav, comp_uid, NULL, NULL, NULL);
2756
2757                 if (icalcomp) {
2758                         icalcomponent *master = get_master_comp (cbdav, icalcomp);
2759
2760                         if (!master)
2761                                 *calobj = e_cal_component_get_as_string (comp);
2762                         else
2763                                 *calobj = icalcomponent_as_ical_string_r (master);
2764
2765                         icalcomponent_free (icalcomp);
2766                 } else {
2767                         *calobj = e_cal_component_get_as_string (comp);
2768                 }
2769         }
2770
2771         g_object_unref (comp);
2772
2773         return status;
2774 }
2775
2776 /* priv->lock is supposed to be locked already, when calling this function */
2777 static ECalBackendSyncStatus
2778 do_modify_object (ECalBackendCalDAV *cbdav, const gchar *calobj, CalObjModType mod, gchar **old_object, gchar **new_object)
2779 {
2780         ECalBackendCalDAVPrivate *priv;
2781         ECalBackendSyncStatus     status;
2782         ECalComponent            *comp;
2783         icalcomponent            *cache_comp;
2784         gboolean                  online;
2785         ECalComponentId          *id;
2786         struct icaltimetype current;
2787         gchar *href = NULL, *etag = NULL;
2788
2789         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2790
2791         if (new_object)
2792                 *new_object = NULL;
2793
2794         status = check_state (cbdav, &online);
2795         if (status != GNOME_Evolution_Calendar_Success) {
2796                 return status;
2797         }
2798
2799         comp = e_cal_component_new_from_string (calobj);
2800
2801         if (comp == NULL) {
2802                 return GNOME_Evolution_Calendar_InvalidObject;
2803         }
2804
2805         if (!e_cal_component_get_icalcomponent (comp) ||
2806             icalcomponent_isa (e_cal_component_get_icalcomponent (comp)) != e_cal_backend_get_kind (E_CAL_BACKEND (cbdav))) {
2807                 g_object_unref (comp);
2808                 return GNOME_Evolution_Calendar_InvalidObject;
2809         }
2810
2811         /* Set the last modified time on the component */
2812         current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
2813         e_cal_component_set_last_modified (comp, &current);
2814
2815         /* sanitize the component */
2816         sanitize_component ((ECalBackend *)cbdav, comp);
2817
2818         id = e_cal_component_get_id (comp);
2819         g_return_val_if_fail (id != NULL, GNOME_Evolution_Calendar_OtherError);
2820
2821         /* fetch full component from cache, it will be pushed to the server */
2822         cache_comp = get_comp_from_cache (cbdav, id->uid, NULL, &href, &etag);
2823
2824         if (cache_comp == NULL) {
2825                 e_cal_component_free_id (id);
2826                 g_object_unref (comp);
2827                 g_free (href);
2828                 g_free (etag);
2829                 return GNOME_Evolution_Calendar_ObjectNotFound;
2830         }
2831
2832         if (!online) {
2833                 /* mark component as out of synch */
2834                 /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_MODIFIED);*/
2835         }
2836
2837         if (old_object) {
2838                 *old_object = NULL;
2839
2840                 if (e_cal_component_is_instance (comp)) {
2841                         /* set detached instance as the old object, if any */
2842                         ECalComponent *old_instance = e_cal_backend_cache_get_component (priv->cache, id->uid, id->rid);
2843
2844                         if (old_instance) {
2845                                 *old_object = e_cal_component_get_as_string (old_instance);
2846                                 g_object_unref (old_instance);
2847                         }
2848                 }
2849
2850                 if (!*old_object) {
2851                         icalcomponent *master = get_master_comp (cbdav, cache_comp);
2852
2853                         if (master) {
2854                                 /* set full component as the old object */
2855                                 *old_object = icalcomponent_as_ical_string_r (master);
2856                         }
2857                 }
2858         }
2859
2860         switch (mod) {
2861         case CALOBJ_MOD_THIS:
2862                 if (e_cal_component_is_instance (comp)) {
2863                         icalcomponent *new_comp = e_cal_component_get_icalcomponent (comp);
2864
2865                         /* new object is only this instance */
2866                         if (new_object)
2867                                 *new_object = e_cal_component_get_as_string (comp);
2868
2869                         /* add the detached instance */
2870                         if (icalcomponent_isa (cache_comp) == ICAL_VCALENDAR_COMPONENT) {
2871                                 /* do not modify the EXDATE, as the component will be put back */
2872                                 remove_instance (cbdav, cache_comp, icalcomponent_get_recurrenceid (new_comp), mod, FALSE);
2873                         } else {
2874                                 /* this is only a master object, thus make is a VCALENDAR component */
2875                                 icalcomponent *icomp;
2876
2877                                 icomp = e_cal_util_new_top_level();
2878                                 icalcomponent_add_component (icomp, cache_comp);
2879
2880                                 /* no need to free the cache_comp, as it is inside icomp */
2881                                 cache_comp = icomp;
2882                         }
2883
2884                         /* add the detached instance finally */
2885                         icalcomponent_add_component (cache_comp, icalcomponent_new_clone (new_comp));
2886                 } else {
2887                         cache_comp = replace_master (cbdav, cache_comp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
2888                 }
2889                 break;
2890         case CALOBJ_MOD_ALL:
2891                 cache_comp = replace_master (cbdav, cache_comp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
2892                 break;
2893         case CALOBJ_MOD_THISANDPRIOR:
2894         case CALOBJ_MOD_THISANDFUTURE:
2895                 break;
2896         }
2897
2898         if (online) {
2899                 CalDAVObject object;
2900
2901                 object.href  = href;
2902                 object.etag  = etag;
2903                 object.cdata = pack_cobj (cbdav, cache_comp);
2904
2905                 status = caldav_server_put_object (cbdav, &object, cache_comp);
2906
2907                 caldav_object_free (&object, FALSE);
2908                 href = NULL;
2909                 etag = NULL;
2910         } else {
2911                 /* mark component as out of synch */
2912                 /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_MODIFIED);*/
2913         }
2914
2915         if (status == GNOME_Evolution_Calendar_Success) {
2916                 if (new_object && !*new_object) {
2917                         icalcomponent *master = get_master_comp (cbdav, cache_comp);
2918
2919                         if (master)
2920                                 *new_object = icalcomponent_as_ical_string_r (master);
2921                 }
2922         }
2923
2924         e_cal_component_free_id (id);
2925         icalcomponent_free (cache_comp);
2926         g_object_unref (comp);
2927         g_free (href);
2928         g_free (etag);
2929
2930         return status;
2931 }
2932
2933 /* priv->lock is supposed to be locked already, when calling this function */
2934 static ECalBackendSyncStatus
2935 do_remove_object (ECalBackendCalDAV *cbdav, const gchar *uid, const gchar *rid, CalObjModType mod, gchar **old_object, gchar **object)
2936 {
2937         ECalBackendCalDAVPrivate *priv;
2938         ECalBackendSyncStatus     status;
2939         icalcomponent            *cache_comp;
2940         gboolean                  online;
2941         gchar *href = NULL, *etag = NULL;
2942
2943         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
2944
2945         if (object)
2946                 *object = NULL;
2947
2948         status = check_state (cbdav, &online);
2949         if (status != GNOME_Evolution_Calendar_Success) {
2950                 return status;
2951         }
2952
2953         cache_comp = get_comp_from_cache (cbdav, uid, NULL, &href, &etag);
2954
2955         if (cache_comp == NULL) {
2956                 return GNOME_Evolution_Calendar_ObjectNotFound;
2957         }
2958
2959         if (old_object) {
2960                 ECalComponent *old = e_cal_backend_cache_get_component (priv->cache, uid, rid);
2961
2962                 if (old) {
2963                         *old_object = e_cal_component_get_as_string (old);
2964                         g_object_unref (old);
2965                 } else {
2966                         icalcomponent *master = get_master_comp (cbdav, cache_comp);
2967
2968                         if (master)
2969                                 *old_object = icalcomponent_as_ical_string_r (master);
2970                 }
2971         }
2972
2973         switch (mod) {
2974         case CALOBJ_MOD_THIS:
2975                 if (rid && *rid) {
2976                         /* remove one instance from the component */
2977                         if (remove_instance (cbdav, cache_comp, icaltime_from_string (rid), mod, TRUE)) {
2978                                 if (object) {
2979                                         icalcomponent *master = get_master_comp (cbdav, cache_comp);
2980
2981                                         if (master)
2982                                                 *object = icalcomponent_as_ical_string_r (master);
2983                                 }
2984                         } else {
2985                                 /* this was the last instance, thus delete whole component */
2986                                 rid = NULL;
2987                                 remove_comp_from_cache (cbdav, uid, NULL);
2988                         }
2989                 } else {
2990                         /* remove whole object */
2991                         remove_comp_from_cache (cbdav, uid, NULL);
2992                 }
2993                 break;
2994         case CALOBJ_MOD_ALL:
2995                 remove_comp_from_cache (cbdav, uid, NULL);
2996                 break;
2997         case CALOBJ_MOD_THISANDPRIOR:
2998         case CALOBJ_MOD_THISANDFUTURE:
2999                 break;
3000         }
3001
3002         if (online) {
3003                 CalDAVObject caldav_object;
3004
3005                 caldav_object.href  = href;
3006                 caldav_object.etag  = etag;
3007                 caldav_object.cdata = NULL;
3008
3009                 if (mod == CALOBJ_MOD_THIS && rid && *rid) {
3010                         caldav_object.cdata = pack_cobj (cbdav, cache_comp);
3011
3012                         status = caldav_server_put_object (cbdav, &caldav_object, cache_comp);
3013                 } else
3014                         status = caldav_server_delete_object (cbdav, &caldav_object);
3015
3016                 caldav_object_free (&caldav_object, FALSE);
3017                 href = NULL;
3018                 etag = NULL;
3019         } else {
3020                 /* mark component as out of synch */
3021                 /*if (mod == CALOBJ_MOD_THIS && rid && *rid)
3022                         ecalcomp_set_synch_state (cache_comp_master, ECALCOMP_LOCALLY_MODIFIED);
3023                 else
3024                         ecalcomp_set_synch_state (cache_comp_master, ECALCOMP_LOCALLY_DELETED);*/
3025         }
3026
3027         icalcomponent_free (cache_comp);
3028         g_free (href);
3029         g_free (etag);
3030
3031         return status;
3032 }
3033
3034 static ECalBackendSyncStatus
3035 caldav_create_object (ECalBackendSync *backend, EDataCal *cal, gchar **calobj, gchar **uid)
3036 {
3037         ECalBackendCalDAV        *cbdav;
3038         ECalBackendCalDAVPrivate *priv;
3039         ECalBackendSyncStatus     status;
3040
3041         cbdav = E_CAL_BACKEND_CALDAV (backend);
3042         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3043
3044         g_mutex_lock (priv->lock);
3045         status = do_create_object (cbdav, calobj, uid);
3046         g_mutex_unlock (priv->lock);
3047
3048         return status;
3049 }
3050
3051 static ECalBackendSyncStatus
3052 caldav_modify_object (ECalBackendSync *backend, EDataCal *cal, const gchar *calobj, CalObjModType mod, gchar **old_object, gchar **new_object)
3053 {
3054         ECalBackendCalDAV        *cbdav;
3055         ECalBackendCalDAVPrivate *priv;
3056         ECalBackendSyncStatus     status;
3057
3058         cbdav = E_CAL_BACKEND_CALDAV (backend);
3059         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3060
3061         g_mutex_lock (priv->lock);
3062         status = do_modify_object (cbdav, calobj, mod, old_object, new_object);
3063         g_mutex_unlock (priv->lock);
3064
3065         return status;
3066 }
3067
3068 static ECalBackendSyncStatus
3069 caldav_remove_object (ECalBackendSync *backend, EDataCal *cal, const gchar *uid, const gchar *rid, CalObjModType mod, gchar **old_object, gchar **object)
3070 {
3071         ECalBackendCalDAV        *cbdav;
3072         ECalBackendCalDAVPrivate *priv;
3073         ECalBackendSyncStatus     status;
3074
3075         cbdav = E_CAL_BACKEND_CALDAV (backend);
3076         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3077
3078         g_mutex_lock (priv->lock);
3079         status = do_remove_object (cbdav, uid, rid, mod, old_object, object);
3080         g_mutex_unlock (priv->lock);
3081
3082         return status;
3083 }
3084
3085 static ECalBackendSyncStatus
3086 caldav_discard_alarm (ECalBackendSync *backend,
3087                       EDataCal        *cal,
3088                       const gchar      *uid,
3089                       const gchar      *auid)
3090 {
3091         return GNOME_Evolution_Calendar_Success;
3092 }
3093
3094 static ECalBackendSyncStatus
3095 extract_objects (icalcomponent       *icomp,
3096                  icalcomponent_kind   ekind,
3097                  GList              **objects)
3098 {
3099         icalcomponent         *scomp;
3100         icalcomponent_kind     kind;
3101
3102         g_return_val_if_fail (icomp, GNOME_Evolution_Calendar_OtherError);
3103         g_return_val_if_fail (objects, GNOME_Evolution_Calendar_OtherError);
3104
3105         kind = icalcomponent_isa (icomp);
3106
3107         if (kind == ekind) {
3108                 *objects = g_list_prepend (NULL, icomp);
3109                 return GNOME_Evolution_Calendar_Success;
3110         }
3111
3112         if (kind != ICAL_VCALENDAR_COMPONENT) {
3113                 return GNOME_Evolution_Calendar_InvalidObject;
3114         }
3115
3116         *objects = NULL;
3117         scomp = icalcomponent_get_first_component (icomp,
3118                                                    ekind);
3119
3120         while (scomp) {
3121                 /* Remove components from toplevel here */
3122                 *objects = g_list_prepend (*objects, scomp);
3123                 icalcomponent_remove_component (icomp, scomp);
3124
3125                 scomp = icalcomponent_get_next_component (icomp, ekind);
3126         }
3127
3128         return GNOME_Evolution_Calendar_Success;
3129 }
3130
3131 static gboolean
3132 extract_timezones (ECalBackendCalDAV *cbdav, icalcomponent *icomp)
3133 {
3134         ECalBackendCalDAVPrivate *priv;
3135         GList *timezones = NULL, *iter;
3136         icaltimezone *zone;
3137
3138         g_return_val_if_fail (cbdav != NULL, FALSE);
3139         g_return_val_if_fail (icomp != NULL, FALSE);
3140
3141         if (extract_objects (icomp, ICAL_VTIMEZONE_COMPONENT, &timezones) != GNOME_Evolution_Calendar_Success) {
3142                 return FALSE;
3143         }
3144
3145         priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3146
3147         zone = icaltimezone_new ();
3148         for (iter = timezones; iter; iter = iter->next) {
3149                 if (icaltimezone_set_component (zone, iter->data)) {
3150                         e_cal_backend_cache_put_timezone (priv->cache, zone);
3151                 } else {
3152                         icalcomponent_free (iter->data);
3153                 }
3154         }
3155
3156         icaltimezone_free (zone, TRUE);
3157         g_list_free (timezones);
3158
3159         return TRUE;
3160 }
3161
3162 #define is_error(__status) (__status != GNOME_Evolution_Calendar_Success)
3163
3164 static ECalBackendSyncStatus
3165 process_object (ECalBackendCalDAV   *cbdav,
3166                 ECalComponent       *ecomp,
3167                 gboolean             online,
3168                 icalproperty_method  method)
3169 {
3170         ECalBackendCalDAVPrivate *priv;
3171         ECalBackendSyncStatus     status;
3172         ECalBackend              *backend;
3173         struct icaltimetype       now;
3174         gchar *new_obj_str;
3175         gboolean is_declined, is_in_cache;
3176         CalObjModType mod;
3177         ECalComponentId *id = e_cal_component_get_id (ecomp);
3178
3179         priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3180         backend = E_CAL_BACKEND (cbdav);
3181
3182         g_return_val_if_fail (id != NULL, GNOME_Evolution_Calendar_InvalidObject);
3183
3184         /* ctime, mtime */
3185         now = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
3186         e_cal_component_set_created (ecomp, &now);
3187         e_cal_component_set_last_modified (ecomp, &now);
3188
3189         /* just to check whether component exists in a cache */
3190         is_in_cache = cache_contains (cbdav, id->uid, NULL) || cache_contains (cbdav, id->uid, id->rid);
3191
3192         new_obj_str = e_cal_component_get_as_string (ecomp);
3193         mod = e_cal_component_is_instance (ecomp) ? CALOBJ_MOD_THIS : CALOBJ_MOD_ALL;
3194         status = GNOME_Evolution_Calendar_Success;
3195
3196         switch (method) {
3197         case ICAL_METHOD_PUBLISH:
3198         case ICAL_METHOD_REQUEST:
3199         case ICAL_METHOD_REPLY:
3200                 is_declined = e_cal_backend_user_declined (e_cal_component_get_icalcomponent (ecomp));
3201                 if (is_in_cache) {
3202                         if (!is_declined) {
3203                                 gchar *new_object = NULL, *old_object = NULL;
3204
3205                                 status = do_modify_object (cbdav, new_obj_str, mod, &old_object, &new_object);
3206                                 if (status == GNOME_Evolution_Calendar_Success) {
3207                                         if (!old_object)
3208                                                 e_cal_backend_notify_object_created (backend, new_object);
3209                                         else
3210                                                 e_cal_backend_notify_object_modified (backend, old_object, new_object);
3211                                 }
3212
3213                                 g_free (new_object);
3214                                 g_free (old_object);
3215                         } else {
3216                                 gchar *new_object = NULL, *old_object = NULL;
3217
3218                                 status = do_remove_object (cbdav, id->uid, id->rid, mod, &old_object, &new_object);
3219                                 if (status == GNOME_Evolution_Calendar_Success) {
3220                                         if (new_object) {
3221                                                 e_cal_backend_notify_object_modified (backend, old_object, new_object);
3222                                         } else {
3223                                                 e_cal_backend_notify_object_removed (backend, id, old_object, NULL);
3224                                         }
3225                                 }
3226
3227                                 g_free (new_object);
3228                                 g_free (old_object);
3229                         }
3230                 } else if (!is_declined) {
3231                         gchar *new_object = new_obj_str;
3232
3233                         status = do_create_object (cbdav, &new_object, NULL);
3234                         if (status == GNOME_Evolution_Calendar_Success) {
3235                                 e_cal_backend_notify_object_created (backend, new_object);
3236                         }
3237
3238                         if (new_object != new_obj_str)
3239                                 g_free (new_object);
3240                 }
3241                 break;
3242         case ICAL_METHOD_CANCEL:
3243                 if (is_in_cache) {
3244                         gchar *old_object = NULL, *new_object = NULL;
3245
3246                         status = do_remove_object (cbdav, id->uid, id->rid, CALOBJ_MOD_THIS, &old_object, &new_object);
3247                         if (status == GNOME_Evolution_Calendar_Success) {
3248                                 if (new_object) {
3249                                         e_cal_backend_notify_object_modified (backend, old_object, new_object);
3250                                 } else {
3251                                         e_cal_backend_notify_object_removed (backend, id, old_object, NULL);
3252                                 }
3253                         }
3254
3255                         g_free (old_object);
3256                         g_free (new_object);
3257                 } else {
3258                         status = GNOME_Evolution_Calendar_ObjectNotFound;
3259                 }
3260                 break;
3261
3262         default:
3263                 status = GNOME_Evolution_Calendar_UnsupportedMethod;
3264                 break;
3265         }
3266
3267         e_cal_component_free_id (id);
3268         g_free (new_obj_str);
3269
3270         return status;
3271 }
3272
3273 static ECalBackendSyncStatus
3274 caldav_receive_objects (ECalBackendSync *backend,
3275                         EDataCal        *cal,
3276                         const gchar      *calobj)
3277 {
3278         ECalBackendCalDAV        *cbdav;
3279         ECalBackendCalDAVPrivate *priv;
3280         ECalBackendSyncStatus     status;
3281         icalcomponent            *icomp;
3282         icalcomponent_kind        kind;
3283         icalproperty_method       tmethod;
3284         gboolean                  online;
3285         GList                    *objects;
3286         GList                    *iter;
3287
3288         cbdav = E_CAL_BACKEND_CALDAV (backend);
3289         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3290
3291         icomp = icalparser_parse_string (calobj);
3292
3293         /* Try to parse cal object string */
3294         if (icomp == NULL) {
3295                 return GNOME_Evolution_Calendar_InvalidObject;
3296         }
3297
3298         kind = e_cal_backend_get_kind (E_CAL_BACKEND (backend));
3299         status = extract_objects (icomp, kind, &objects);
3300
3301         if (status != GNOME_Evolution_Calendar_Success) {
3302                 icalcomponent_free (icomp);
3303                 return status;
3304         }
3305
3306         /* Extract optional timezone compnents */
3307         extract_timezones (cbdav, icomp);
3308
3309         /*   */
3310         g_mutex_lock (priv->lock);
3311
3312         status = check_state (cbdav, &online);
3313
3314         if (status != GNOME_Evolution_Calendar_Success) {
3315                 /* FIXME: free components here */
3316                 g_mutex_unlock (priv->lock);
3317                 icalcomponent_free (icomp);
3318                 return status;
3319         }
3320
3321         tmethod = icalcomponent_get_method (icomp);
3322
3323         for (iter = objects; iter && ! is_error (status); iter = iter->next) {
3324                 icalcomponent       *scomp;
3325                 ECalComponent       *ecomp;
3326                 icalproperty_method  method;
3327
3328                 scomp = (icalcomponent *) iter->data;
3329                 ecomp = e_cal_component_new ();
3330
3331                 e_cal_component_set_icalcomponent (ecomp, scomp);
3332
3333                 if (icalcomponent_get_first_property (scomp, ICAL_METHOD_PROPERTY)) {
3334                         method = icalcomponent_get_method (scomp);
3335                 } else {
3336                         method = tmethod;
3337                 }
3338
3339                 status = process_object (cbdav, ecomp, online, method);
3340
3341                 g_object_unref (ecomp);
3342         }
3343
3344         g_list_free (objects);
3345
3346         g_mutex_unlock (priv->lock);
3347         icalcomponent_free (icomp);
3348
3349         return status;
3350 }
3351
3352 static ECalBackendSyncStatus
3353 caldav_send_objects (ECalBackendSync  *backend,
3354                      EDataCal         *cal,
3355                      const gchar       *calobj,
3356                      GList           **users,
3357                      gchar            **modified_calobj)
3358 {
3359         *users = NULL;
3360         *modified_calobj = g_strdup (calobj);
3361
3362         return GNOME_Evolution_Calendar_Success;
3363 }
3364
3365 static ECalBackendSyncStatus
3366 caldav_get_default_object (ECalBackendSync  *backend,
3367                            EDataCal         *cal,
3368                            gchar            **object)
3369 {
3370         ECalComponent *comp;
3371
3372         comp = e_cal_component_new ();
3373
3374         switch (e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
3375         case ICAL_VEVENT_COMPONENT:
3376                 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
3377                 break;
3378         case ICAL_VTODO_COMPONENT:
3379                 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
3380                 break;
3381         case ICAL_VJOURNAL_COMPONENT:
3382                 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
3383                 break;
3384         default:
3385                 g_object_unref (comp);
3386                 return GNOME_Evolution_Calendar_ObjectNotFound;
3387         }
3388
3389         *object = e_cal_component_get_as_string (comp);
3390         g_object_unref (comp);
3391
3392         return GNOME_Evolution_Calendar_Success;
3393 }
3394
3395 static ECalBackendSyncStatus
3396 caldav_get_object (ECalBackendSync  *backend,
3397                    EDataCal         *cal,
3398                    const gchar       *uid,
3399                    const gchar       *rid,
3400                    gchar           **object)
3401 {
3402         ECalBackendCalDAV        *cbdav;
3403         ECalBackendCalDAVPrivate *priv;
3404         icalcomponent            *icalcomp;
3405
3406         cbdav = E_CAL_BACKEND_CALDAV (backend);
3407         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3408
3409         g_mutex_lock (priv->lock);
3410
3411         *object = NULL;
3412         icalcomp = get_comp_from_cache (cbdav, uid, rid, NULL, NULL);
3413
3414         g_mutex_unlock (priv->lock);
3415
3416         if (!icalcomp) {
3417                 return GNOME_Evolution_Calendar_ObjectNotFound;
3418         }
3419
3420         *object = icalcomponent_as_ical_string_r (icalcomp);
3421         icalcomponent_free (icalcomp);
3422
3423         return GNOME_Evolution_Calendar_Success;
3424 }
3425
3426 static ECalBackendSyncStatus
3427 caldav_get_timezone (ECalBackendSync  *backend,
3428                      EDataCal         *cal,
3429                      const gchar       *tzid,
3430                      gchar            **object)
3431 {
3432         ECalBackendCalDAV        *cbdav;
3433         ECalBackendCalDAVPrivate *priv;
3434         const icaltimezone       *zone;
3435         icalcomponent            *icalcomp;
3436
3437         cbdav = E_CAL_BACKEND_CALDAV (backend);
3438         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3439
3440         g_return_val_if_fail (tzid, GNOME_Evolution_Calendar_ObjectNotFound);
3441
3442         /* first try to get the timezone from the cache */
3443         g_mutex_lock (priv->lock);
3444         zone = e_cal_backend_cache_get_timezone (priv->cache, tzid);
3445         g_mutex_unlock (priv->lock);
3446
3447         if (!zone) {
3448                 zone = icaltimezone_get_builtin_timezone_from_tzid (tzid);
3449                 if (!zone) {
3450                         return GNOME_Evolution_Calendar_ObjectNotFound;
3451                 }
3452         }
3453
3454         icalcomp = icaltimezone_get_component ((icaltimezone *) zone);
3455
3456         if (!icalcomp) {
3457                 return GNOME_Evolution_Calendar_InvalidObject;
3458         }
3459
3460         *object = icalcomponent_as_ical_string_r (icalcomp);
3461
3462         return GNOME_Evolution_Calendar_Success;
3463 }
3464
3465 static ECalBackendSyncStatus
3466 caldav_add_timezone (ECalBackendSync *backend,
3467                      EDataCal        *cal,
3468                      const gchar      *tzobj)
3469 {
3470         icalcomponent *tz_comp;
3471         ECalBackendCalDAV *cbdav;
3472         ECalBackendCalDAVPrivate *priv;
3473
3474         cbdav = E_CAL_BACKEND_CALDAV (backend);
3475
3476         g_return_val_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav), GNOME_Evolution_Calendar_OtherError);
3477         g_return_val_if_fail (tzobj != NULL, GNOME_Evolution_Calendar_OtherError);
3478
3479         priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3480
3481         tz_comp = icalparser_parse_string (tzobj);
3482         if (!tz_comp)
3483                 return GNOME_Evolution_Calendar_InvalidObject;
3484
3485         if (icalcomponent_isa (tz_comp) == ICAL_VTIMEZONE_COMPONENT) {
3486                 icaltimezone *zone;
3487
3488                 zone = icaltimezone_new ();
3489                 icaltimezone_set_component (zone, tz_comp);
3490
3491                 g_mutex_lock (priv->lock);
3492                 e_cal_backend_cache_put_timezone (priv->cache, zone);
3493                 g_mutex_unlock (priv->lock);
3494
3495                 icaltimezone_free (zone, TRUE);
3496         } else {
3497                 icalcomponent_free (tz_comp);
3498         }
3499
3500         return GNOME_Evolution_Calendar_Success;
3501 }
3502
3503 static ECalBackendSyncStatus
3504 caldav_set_default_zone (ECalBackendSync *backend,
3505                              EDataCal        *cal,
3506                              const gchar      *tzobj)
3507 {
3508         icalcomponent *tz_comp;
3509         ECalBackendCalDAV *cbdav;
3510         ECalBackendCalDAVPrivate *priv;
3511         icaltimezone *zone;
3512
3513         g_return_val_if_fail (E_IS_CAL_BACKEND_CALDAV (backend), GNOME_Evolution_Calendar_OtherError);
3514         g_return_val_if_fail (tzobj != NULL, GNOME_Evolution_Calendar_OtherError);
3515
3516         cbdav = E_CAL_BACKEND_CALDAV (backend);
3517         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3518
3519         tz_comp = icalparser_parse_string (tzobj);
3520         if (!tz_comp)
3521                 return GNOME_Evolution_Calendar_InvalidObject;
3522
3523         zone = icaltimezone_new ();
3524         icaltimezone_set_component (zone, tz_comp);
3525
3526         if (priv->default_zone != icaltimezone_get_utc_timezone ())
3527                 icaltimezone_free (priv->default_zone, 1);
3528
3529         /* Set the default timezone to it. */
3530         priv->default_zone = zone;
3531
3532         return GNOME_Evolution_Calendar_Success;
3533 }
3534
3535 static ECalBackendSyncStatus
3536 caldav_get_object_list (ECalBackendSync  *backend,
3537                         EDataCal         *cal,
3538                         const gchar       *sexp_string,
3539                         GList           **objects)
3540 {
3541         ECalBackendCalDAV        *cbdav;
3542         ECalBackendCalDAVPrivate *priv;
3543         ECalBackendSExp  *sexp;
3544         ECalBackendCache         *bcache;
3545         ECalBackend              *bkend;
3546         gboolean                  do_search;
3547         GList                    *list, *iter;
3548
3549         cbdav = E_CAL_BACKEND_CALDAV (backend);
3550         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3551
3552         sexp = e_cal_backend_sexp_new (sexp_string);
3553
3554         if (sexp == NULL) {
3555                 return GNOME_Evolution_Calendar_InvalidQuery;
3556         }
3557
3558         if (g_str_equal (sexp_string, "#t")) {
3559                 do_search = FALSE;
3560         } else {
3561                 do_search = TRUE;
3562         }
3563
3564         *objects = NULL;
3565         bcache = priv->cache;
3566
3567         g_mutex_lock (priv->lock);
3568
3569         list = e_cal_backend_cache_get_components (bcache);
3570         bkend = E_CAL_BACKEND (backend);
3571
3572         for (iter = list; iter; iter = g_list_next (iter)) {
3573                 ECalComponent *comp = E_CAL_COMPONENT (iter->data);
3574
3575                 if (!do_search ||
3576                     e_cal_backend_sexp_match_comp (sexp, comp, bkend)) {
3577                         gchar *str = e_cal_component_get_as_string (comp);
3578                         *objects = g_list_prepend (*objects, str);
3579                 }
3580
3581                 g_object_unref (comp);
3582         }
3583
3584         g_object_unref (sexp);
3585         g_list_free (list);
3586
3587         g_mutex_unlock (priv->lock);
3588
3589         return GNOME_Evolution_Calendar_Success;
3590 }
3591
3592 static void
3593 caldav_start_query (ECalBackend  *backend,
3594                     EDataCalView *query)
3595 {
3596         ECalBackendCalDAV        *cbdav;
3597         ECalBackendCalDAVPrivate *priv;
3598         ECalBackendSExp  *sexp;
3599         ECalBackend              *bkend;
3600         gboolean                  do_search;
3601         GList                    *list, *iter;
3602         const gchar               *sexp_string;
3603
3604         cbdav = E_CAL_BACKEND_CALDAV (backend);
3605         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3606
3607         sexp_string = e_data_cal_view_get_text (query);
3608         sexp = e_cal_backend_sexp_new (sexp_string);
3609
3610         /* FIXME:check invalid sexp */
3611
3612         if (g_str_equal (sexp_string, "#t")) {
3613                 do_search = FALSE;
3614         } else {
3615                 do_search = TRUE;
3616         }
3617
3618         g_mutex_lock (priv->lock);
3619
3620         list = e_cal_backend_cache_get_components (priv->cache);
3621         bkend = E_CAL_BACKEND (backend);
3622
3623         for (iter = list; iter; iter = g_list_next (iter)) {
3624                 ECalComponent *comp = E_CAL_COMPONENT (iter->data);
3625
3626                 if (!do_search ||
3627                     e_cal_backend_sexp_match_comp (sexp, comp, bkend)) {
3628                         gchar *str = e_cal_component_get_as_string (comp);
3629                         e_data_cal_view_notify_objects_added_1 (query, str);
3630                         g_free (str);
3631                 }
3632
3633                 g_object_unref (comp);
3634         }
3635
3636         g_object_unref (sexp);
3637         g_list_free (list);
3638
3639
3640         e_data_cal_view_notify_done (query, GNOME_Evolution_Calendar_Success);
3641         g_mutex_unlock (priv->lock);
3642         return;
3643 }
3644
3645 static ECalBackendSyncStatus
3646 caldav_get_free_busy (ECalBackendSync  *backend,
3647                       EDataCal         *cal,
3648                       GList            *users,
3649                       time_t            start,
3650                       time_t            end,
3651                       GList           **freebusy)
3652 {
3653         ECalBackendCalDAV *cbdav;
3654         ECalBackendCalDAVPrivate *priv;
3655         ECalBackendSyncStatus status;
3656         icalcomponent *icalcomp;
3657         ECalComponent *comp;
3658         ECalComponentDateTime dt;
3659         struct icaltimetype dtvalue;
3660         icaltimezone *utc;
3661         gchar *str;
3662         GList *u;
3663         GSList *attendees = NULL, *to_free = NULL;
3664
3665         cbdav = E_CAL_BACKEND_CALDAV (backend);
3666         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3667
3668         g_return_val_if_fail (priv != NULL, GNOME_Evolution_Calendar_OtherError);
3669         g_return_val_if_fail (users != NULL, GNOME_Evolution_Calendar_OtherError);
3670         g_return_val_if_fail (freebusy != NULL, GNOME_Evolution_Calendar_OtherError);
3671         g_return_val_if_fail (start < end, GNOME_Evolution_Calendar_OtherError);
3672
3673         if (!priv->calendar_schedule) {
3674                 return GNOME_Evolution_Calendar_OtherError;
3675         }
3676
3677         if (!priv->schedule_outbox_url) {
3678                 caldav_receive_schedule_outbox_url (cbdav);
3679                 if (!priv->schedule_outbox_url) {
3680                         priv->calendar_schedule = FALSE;
3681                         return GNOME_Evolution_Calendar_OtherError;
3682                 }
3683         }
3684
3685         comp = e_cal_component_new ();
3686         e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_FREEBUSY);
3687
3688         str = e_cal_component_gen_uid ();
3689         e_cal_component_set_uid (comp, str);
3690         g_free (str);
3691
3692         utc = icaltimezone_get_utc_timezone ();
3693         dt.value = &dtvalue;
3694         dt.tzid = icaltimezone_get_tzid (utc);
3695
3696         dtvalue = icaltime_current_time_with_zone (utc);
3697         e_cal_component_set_dtstamp (comp, &dtvalue);
3698
3699         dtvalue = icaltime_from_timet_with_zone (start, FALSE, utc);
3700         e_cal_component_set_dtstart (comp, &dt);
3701
3702         dtvalue = icaltime_from_timet_with_zone (end, FALSE, utc);
3703         e_cal_component_set_dtend (comp, &dt);
3704
3705         if (priv->username) {
3706                 ECalComponentOrganizer organizer = {0};
3707
3708                 organizer.value = priv->username;
3709                 e_cal_component_set_organizer (comp, &organizer);
3710         }
3711
3712         for (u = users; u; u = u->next) {
3713                 ECalComponentAttendee *ca;
3714                 gchar *temp = g_strconcat ("mailto:", (const gchar *)u->data, NULL);
3715
3716                 ca = g_new0 (ECalComponentAttendee, 1);
3717
3718                 ca->value = temp;
3719                 ca->cutype = ICAL_CUTYPE_INDIVIDUAL;
3720                 ca->status = ICAL_PARTSTAT_NEEDSACTION;
3721                 ca->role = ICAL_ROLE_CHAIR;
3722
3723                 to_free = g_slist_prepend (to_free, temp);
3724                 attendees = g_slist_append (attendees, ca);
3725         }
3726
3727         e_cal_component_set_attendee_list (comp, attendees);
3728
3729         g_slist_foreach (attendees, (GFunc) g_free, NULL);
3730         g_slist_free (attendees);
3731
3732         g_slist_foreach (to_free, (GFunc) g_free, NULL);
3733         g_slist_free (to_free);
3734
3735         e_cal_component_abort_sequence (comp);
3736
3737         /* put the free/busy request to a VCALENDAR */
3738         icalcomp = e_cal_util_new_top_level ();
3739         icalcomponent_set_method (icalcomp, ICAL_METHOD_REQUEST);
3740         icalcomponent_add_component (icalcomp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
3741
3742         str = icalcomponent_as_ical_string_r (icalcomp);
3743
3744         icalcomponent_free (icalcomp);
3745         g_object_unref (comp);
3746
3747         g_return_val_if_fail (str != NULL, GNOME_Evolution_Calendar_OtherError);
3748
3749         status = caldav_post_freebusy (cbdav, priv->schedule_outbox_url, &str);
3750
3751         if (status == GNOME_Evolution_Calendar_Success) {
3752                 /* parse returned xml */
3753                 xmlDocPtr doc;
3754
3755                 doc = xmlReadMemory (str, strlen (str), "response.xml", NULL, 0);
3756                 if (doc != NULL) {
3757                         xmlXPathContextPtr xpctx;
3758                         xmlXPathObjectPtr result;
3759
3760                         xpctx = xmlXPathNewContext (doc);
3761                         xmlXPathRegisterNs (xpctx, (xmlChar *) "D", (xmlChar *) "DAV:");
3762                         xmlXPathRegisterNs (xpctx, (xmlChar *) "C", (xmlChar *) "urn:ietf:params:xml:ns:caldav");
3763
3764                         result = xpath_eval (xpctx, "/C:schedule-response/C:response");
3765
3766                         if (result == NULL || result->type != XPATH_NODESET) {
3767                                 status = GNOME_Evolution_Calendar_OtherError;
3768                         } else {
3769                                 gint i, n;
3770
3771                                 n = xmlXPathNodeSetGetLength (result->nodesetval);
3772                                 for (i = 0; i < n; i++) {
3773                                         gchar *tmp;
3774
3775                                         tmp = xp_object_get_string (xpath_eval (xpctx, "string(/C:schedule-response/C:response[%d]/C:calendar-data)", i + 1));
3776                                         if (tmp && *tmp) {
3777                                                 GList *objects = NULL, *o;
3778
3779                                                 icalcomp = icalparser_parse_string (tmp);
3780                                                 if (icalcomp && extract_objects (icalcomp, ICAL_VFREEBUSY_COMPONENT, &objects) == GNOME_Evolution_Calendar_Success) {
3781                                                         for (o = objects; o; o = o->next) {
3782                                                                 gchar *obj_str = icalcomponent_as_ical_string_r (o->data);
3783
3784                                                                 if (obj_str && *obj_str)
3785                                                                         *freebusy = g_list_append (*freebusy, obj_str);
3786                                                                 else
3787                                                                         g_free (obj_str);
3788                                                         }
3789                                                 }
3790
3791                                                 g_list_foreach (objects, (GFunc)icalcomponent_free, NULL);
3792                                                 g_list_free (objects);
3793
3794                                                 if (icalcomp)
3795                                                         icalcomponent_free (icalcomp);
3796                                         }
3797
3798                                         g_free (tmp);
3799
3800                                 }
3801                         }
3802
3803                         if (result != NULL)
3804                                 xmlXPathFreeObject (result);
3805                         xmlXPathFreeContext (xpctx);
3806                         xmlFreeDoc (doc);
3807                 }
3808         }
3809
3810         g_free (str);
3811
3812         return status;
3813 }
3814
3815 static ECalBackendSyncStatus
3816 caldav_get_changes (ECalBackendSync  *backend,
3817                     EDataCal         *cal,
3818                     const gchar       *change_id,
3819                     GList           **adds,
3820                     GList           **modifies,
3821                     GList **deletes)
3822 {
3823         /* FIXME: implement me! */
3824         g_warning ("function not implemented %s", G_STRFUNC);
3825         return GNOME_Evolution_Calendar_OtherError;
3826 }
3827
3828 static gboolean
3829 caldav_is_loaded (ECalBackend *backend)
3830 {
3831         ECalBackendCalDAV        *cbdav;
3832         ECalBackendCalDAVPrivate *priv;
3833
3834         cbdav = E_CAL_BACKEND_CALDAV (backend);
3835         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3836
3837         return priv->loaded;
3838 }
3839
3840 static CalMode
3841 caldav_get_mode (ECalBackend *backend)
3842 {
3843         ECalBackendCalDAV        *cbdav;
3844         ECalBackendCalDAVPrivate *priv;
3845
3846         cbdav = E_CAL_BACKEND_CALDAV (backend);
3847         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3848
3849         return priv->mode;
3850 }
3851
3852 static void
3853 caldav_set_mode (ECalBackend *backend, CalMode mode)
3854 {
3855         ECalBackendCalDAV        *cbdav;
3856         ECalBackendCalDAVPrivate *priv;
3857
3858         cbdav = E_CAL_BACKEND_CALDAV (backend);
3859         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3860
3861         /*g_mutex_lock (priv->lock);*/
3862
3863         /* We only support online and offline */
3864         if (mode != CAL_MODE_REMOTE &&
3865             mode != CAL_MODE_LOCAL) {
3866                 e_cal_backend_notify_mode (backend,
3867                                            GNOME_Evolution_Calendar_CalListener_MODE_NOT_SUPPORTED,
3868                                            cal_mode_to_corba (mode));
3869                 /*g_mutex_unlock (priv->lock);*/
3870                 return;
3871         }
3872
3873         if (priv->mode == mode || !priv->loaded) {
3874                 priv->mode = mode;
3875                 e_cal_backend_notify_mode (backend,
3876                                            GNOME_Evolution_Calendar_CalListener_MODE_SET,
3877                                            cal_mode_to_corba (mode));
3878                 /*g_mutex_unlock (priv->lock);*/
3879                 return;
3880         }
3881
3882         priv->mode = mode;
3883
3884         if (mode == CAL_MODE_REMOTE) {
3885                 /* Wake up the slave thread */
3886                 priv->slave_cmd = SLAVE_SHOULD_WORK;
3887                 g_cond_signal (priv->cond);
3888         } else {
3889                 soup_session_abort (priv->session);
3890                 priv->slave_cmd = SLAVE_SHOULD_SLEEP;
3891         }
3892
3893         e_cal_backend_notify_mode (backend,
3894                                    GNOME_Evolution_Calendar_CalListener_MODE_SET,
3895                                    cal_mode_to_corba (mode));
3896
3897         /*g_mutex_unlock (priv->lock);*/
3898 }
3899
3900 static icaltimezone *
3901 caldav_internal_get_default_timezone (ECalBackend *backend)
3902 {
3903         ECalBackendCalDAV *cbdav;
3904         ECalBackendCalDAVPrivate *priv;
3905
3906         g_return_val_if_fail (E_IS_CAL_BACKEND_CALDAV (backend), NULL);
3907
3908         cbdav = E_CAL_BACKEND_CALDAV (backend);
3909         priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3910
3911         g_return_val_if_fail (priv->default_zone != NULL, NULL);
3912
3913         return priv->default_zone;
3914 }
3915
3916 static icaltimezone *
3917 caldav_internal_get_timezone (ECalBackend *backend,
3918                               const gchar *tzid)
3919 {
3920         icaltimezone *zone;
3921
3922         zone = icaltimezone_get_builtin_timezone_from_tzid (tzid);
3923
3924         if (!zone && E_CAL_BACKEND_CLASS (parent_class)->internal_get_timezone)
3925                 zone = E_CAL_BACKEND_CLASS (parent_class)->internal_get_timezone (backend, tzid);
3926
3927         if (!zone) {
3928                 zone = icaltimezone_get_utc_timezone ();
3929         }
3930
3931         return zone;
3932 }
3933
3934 /* ************************************************************************* */
3935 /* ***************************** GObject Foo ******************************* */
3936
3937 G_DEFINE_TYPE (ECalBackendCalDAV, e_cal_backend_caldav, E_TYPE_CAL_BACKEND_SYNC)
3938
3939 static void
3940 e_cal_backend_caldav_dispose (GObject *object)
3941 {
3942         ECalBackendCalDAV        *cbdav;
3943         ECalBackendCalDAVPrivate *priv;
3944
3945         cbdav = E_CAL_BACKEND_CALDAV (object);
3946         priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3947
3948         g_mutex_lock (priv->lock);
3949
3950         if (priv->disposed) {
3951                 g_mutex_unlock (priv->lock);
3952                 return;
3953         }
3954
3955         /* stop the slave  */
3956         priv->slave_cmd = SLAVE_SHOULD_DIE;
3957         if (priv->synch_slave) {
3958                 g_cond_signal (priv->cond);
3959
3960                 /* wait until the slave died */
3961                 g_cond_wait (priv->slave_gone_cond, priv->lock);
3962         }
3963
3964         g_object_unref (priv->session);
3965         g_object_unref (priv->proxy);
3966
3967         g_free (priv->username);
3968         g_free (priv->password);
3969         g_free (priv->uri);
3970         g_free (priv->schedule_outbox_url);
3971
3972         if (priv->cache != NULL) {
3973                 g_object_unref (priv->cache);
3974         }
3975
3976         priv->disposed = TRUE;
3977         g_mutex_unlock (priv->lock);
3978
3979         if (G_OBJECT_CLASS (parent_class)->dispose)
3980                 (* G_OBJECT_CLASS (parent_class)->dispose) (object);
3981 }
3982
3983 static void
3984 e_cal_backend_caldav_finalize (GObject *object)
3985 {
3986         ECalBackendCalDAV        *cbdav;
3987         ECalBackendCalDAVPrivate *priv;
3988
3989         cbdav = E_CAL_BACKEND_CALDAV (object);
3990         priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
3991
3992         g_mutex_free (priv->lock);
3993         g_cond_free (priv->cond);
3994         g_cond_free (priv->slave_gone_cond);
3995
3996         if (priv->default_zone && priv->default_zone != icaltimezone_get_utc_timezone ()) {
3997                 icaltimezone_free (priv->default_zone, 1);
3998         }
3999         priv->default_zone = NULL;
4000
4001         if (G_OBJECT_CLASS (parent_class)->finalize)
4002                 (* G_OBJECT_CLASS (parent_class)->finalize) (object);
4003 }
4004
4005 static void
4006 e_cal_backend_caldav_init (ECalBackendCalDAV *cbdav)
4007 {
4008         ECalBackendCalDAVPrivate *priv;
4009         priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
4010
4011         priv->session = soup_session_sync_new ();
4012         priv->proxy = e_proxy_new ();
4013         e_proxy_setup_proxy (priv->proxy);
4014         g_signal_connect (priv->proxy, "changed", G_CALLBACK (proxy_settings_changed), priv);
4015
4016         if (G_UNLIKELY (caldav_debug_show (DEBUG_MESSAGE)))
4017                 caldav_debug_setup (priv->session);
4018
4019         priv->default_zone = icaltimezone_get_utc_timezone ();
4020
4021         priv->disposed = FALSE;
4022         priv->do_synch = FALSE;
4023         priv->loaded   = FALSE;
4024
4025         /* Thinks the 'getctag' extension is available the first time, but unset it when realizes it isn't. */
4026         priv->ctag_supported = TRUE;
4027
4028         priv->schedule_outbox_url = NULL;
4029
4030         priv->lock = g_mutex_new ();
4031         priv->cond = g_cond_new ();
4032         priv->slave_gone_cond = g_cond_new ();
4033
4034         /* Slave control ... */
4035         priv->slave_cmd = SLAVE_SHOULD_SLEEP;
4036         priv->refresh_time.tv_usec = 0;
4037         priv->refresh_time.tv_sec  = DEFAULT_REFRESH_TIME;
4038
4039         g_signal_connect (priv->session, "authenticate",
4040                           G_CALLBACK (soup_authenticate), cbdav);
4041
4042         e_cal_backend_sync_set_lock (E_CAL_BACKEND_SYNC (cbdav), FALSE);
4043 }
4044
4045
4046 static void
4047 e_cal_backend_caldav_class_init (ECalBackendCalDAVClass *class)
4048 {
4049         GObjectClass *object_class;
4050         ECalBackendClass *backend_class;
4051         ECalBackendSyncClass *sync_class;
4052
4053         object_class = (GObjectClass *) class;
4054         backend_class = (ECalBackendClass *) class;
4055         sync_class = (ECalBackendSyncClass *) class;
4056
4057         caldav_debug_init ();
4058
4059         parent_class = (ECalBackendSyncClass *) g_type_class_peek_parent (class);
4060         g_type_class_add_private (class, sizeof (ECalBackendCalDAVPrivate));
4061
4062         object_class->dispose  = e_cal_backend_caldav_dispose;
4063         object_class->finalize = e_cal_backend_caldav_finalize;
4064
4065         sync_class->is_read_only_sync            = caldav_is_read_only;
4066         sync_class->get_cal_address_sync         = caldav_get_cal_address;
4067         sync_class->get_alarm_email_address_sync = caldav_get_alarm_email_address;
4068         sync_class->get_ldap_attribute_sync      = caldav_get_ldap_attribute;
4069         sync_class->get_static_capabilities_sync = caldav_get_static_capabilities;
4070
4071         sync_class->open_sync                    = caldav_do_open;
4072         sync_class->remove_sync                  = caldav_remove;
4073
4074         sync_class->create_object_sync = caldav_create_object;
4075         sync_class->modify_object_sync = caldav_modify_object;
4076         sync_class->remove_object_sync = caldav_remove_object;
4077
4078         sync_class->discard_alarm_sync        = caldav_discard_alarm;
4079         sync_class->receive_objects_sync      = caldav_receive_objects;
4080         sync_class->send_objects_sync         = caldav_send_objects;
4081         sync_class->get_default_object_sync   = caldav_get_default_object;
4082         sync_class->get_object_sync           = caldav_get_object;
4083         sync_class->get_object_list_sync      = caldav_get_object_list;
4084         sync_class->get_timezone_sync         = caldav_get_timezone;
4085         sync_class->add_timezone_sync         = caldav_add_timezone;
4086         sync_class->set_default_zone_sync = caldav_set_default_zone;
4087         sync_class->get_freebusy_sync         = caldav_get_free_busy;
4088         sync_class->get_changes_sync          = caldav_get_changes;
4089
4090         backend_class->is_loaded   = caldav_is_loaded;
4091         backend_class->start_query = caldav_start_query;
4092         backend_class->get_mode    = caldav_get_mode;
4093         backend_class->set_mode    = caldav_set_mode;
4094
4095         backend_class->internal_get_default_timezone = caldav_internal_get_default_timezone;
4096         backend_class->internal_get_timezone         = caldav_internal_get_timezone;
4097 }