cal_client_get_backend_property_sync: Add missing returns.
[platform/upstream/evolution-data-server.git] / calendar / libecal / e-cal-client.c
1 /*
2  * e-cal-client.c
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  * Copyright (C) 2011 Red Hat, Inc. (www.redhat.com)
19  *
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <glib/gi18n-lib.h>
27 #include <gio/gio.h>
28
29 /* Private D-Bus classes. */
30 #include <e-dbus-calendar.h>
31 #include <e-dbus-calendar-factory.h>
32
33 #include <libedataserver/e-client-private.h>
34
35 #include "e-cal-client.h"
36 #include "e-cal-component.h"
37 #include "e-cal-check-timezones.h"
38 #include "e-cal-enumtypes.h"
39 #include "e-cal-time-util.h"
40 #include "e-cal-types.h"
41 #include "e-timezone-cache.h"
42
43 #define E_CAL_CLIENT_GET_PRIVATE(obj) \
44         (G_TYPE_INSTANCE_GET_PRIVATE \
45         ((obj), E_TYPE_CAL_CLIENT, ECalClientPrivate))
46
47 /* Set this to a sufficiently large value
48  * to cover most long-running operations. */
49 #define DBUS_PROXY_TIMEOUT_MS (3 * 60 * 1000)  /* 3 minutes */
50
51 typedef struct _AsyncContext AsyncContext;
52 typedef struct _SignalClosure SignalClosure;
53 typedef struct _ConnectClosure ConnectClosure;
54 typedef struct _RunInThreadClosure RunInThreadClosure;
55
56 struct _ECalClientPrivate {
57         EDBusCalendar *dbus_proxy;
58         guint name_watcher_id;
59
60         ECalClientSourceType source_type;
61         icaltimezone *default_zone;
62
63         GMutex zone_cache_lock;
64         GHashTable *zone_cache;
65
66         gulong dbus_proxy_error_handler_id;
67         gulong dbus_proxy_notify_handler_id;
68         gulong dbus_proxy_free_busy_data_handler_id;
69 };
70
71 struct _AsyncContext {
72         ECalClientView *client_view;
73         icalcomponent *in_comp;
74         icalcomponent *out_comp;
75         icaltimezone *zone;
76         GSList *comp_list;
77         GSList *object_list;
78         GSList *string_list;
79         gchar *sexp;
80         gchar *tzid;
81         gchar *uid;
82         gchar *rid;
83         gchar *auid;
84         CalObjModType mod;
85         time_t start;
86         time_t end;
87 };
88
89 struct _SignalClosure {
90         GWeakRef client;
91         gchar *property_name;
92         gchar *error_message;
93         gchar **free_busy_data;
94         icaltimezone *cached_zone;
95 };
96
97 struct _ConnectClosure {
98         ESource *source;
99         GCancellable *cancellable;
100 };
101
102 struct _RunInThreadClosure {
103         GSimpleAsyncThreadFunc func;
104         GSimpleAsyncResult *simple;
105         GCancellable *cancellable;
106 };
107
108 enum {
109         PROP_0,
110         PROP_DEFAULT_TIMEZONE,
111         PROP_SOURCE_TYPE
112 };
113
114 enum {
115         FREE_BUSY_DATA,
116         LAST_SIGNAL
117 };
118
119 /* Forward Declarations */
120 static void     e_cal_client_initable_init
121                                         (GInitableIface *interface);
122 static void     e_cal_client_async_initable_init
123                                         (GAsyncInitableIface *interface);
124 static void     e_cal_client_timezone_cache_init
125                                         (ETimezoneCacheInterface *interface);
126
127 static guint signals[LAST_SIGNAL];
128
129 G_DEFINE_TYPE_WITH_CODE (
130         ECalClient,
131         e_cal_client,
132         E_TYPE_CLIENT,
133         G_IMPLEMENT_INTERFACE (
134                 G_TYPE_INITABLE,
135                 e_cal_client_initable_init)
136         G_IMPLEMENT_INTERFACE (
137                 G_TYPE_ASYNC_INITABLE,
138                 e_cal_client_async_initable_init)
139         G_IMPLEMENT_INTERFACE (
140                 E_TYPE_TIMEZONE_CACHE,
141                 e_cal_client_timezone_cache_init))
142
143 static void
144 async_context_free (AsyncContext *async_context)
145 {
146         if (async_context->client_view != NULL)
147                 g_object_unref (async_context->client_view);
148
149         if (async_context->in_comp != NULL)
150                 icalcomponent_free (async_context->in_comp);
151
152         if (async_context->out_comp != NULL)
153                 icalcomponent_free (async_context->out_comp);
154
155         if (async_context->zone != NULL)
156                 icaltimezone_free (async_context->zone, 1);
157
158         g_slist_free_full (
159                 async_context->comp_list,
160                 (GDestroyNotify) icalcomponent_free);
161
162         g_slist_free_full (
163                 async_context->object_list,
164                 (GDestroyNotify) g_object_unref);
165
166         g_slist_free_full (
167                 async_context->string_list,
168                 (GDestroyNotify) g_free);
169
170         g_free (async_context->sexp);
171         g_free (async_context->tzid);
172         g_free (async_context->uid);
173         g_free (async_context->rid);
174         g_free (async_context->auid);
175
176         g_slice_free (AsyncContext, async_context);
177 }
178
179 static void
180 signal_closure_free (SignalClosure *signal_closure)
181 {
182         g_weak_ref_set (&signal_closure->client, NULL);
183
184         g_free (signal_closure->property_name);
185         g_free (signal_closure->error_message);
186
187         g_strfreev (signal_closure->free_busy_data);
188
189         /* The icaltimezone is cached in ECalClient's internal
190          * "zone_cache" hash table and must not be freed here. */
191
192         g_slice_free (SignalClosure, signal_closure);
193 }
194
195 static void
196 connect_closure_free (ConnectClosure *connect_closure)
197 {
198         if (connect_closure->source != NULL)
199                 g_object_unref (connect_closure->source);
200
201         if (connect_closure->cancellable != NULL)
202                 g_object_unref (connect_closure->cancellable);
203
204         g_slice_free (ConnectClosure, connect_closure);
205 }
206
207 static void
208 run_in_thread_closure_free (RunInThreadClosure *run_in_thread_closure)
209 {
210         if (run_in_thread_closure->simple != NULL)
211                 g_object_unref (run_in_thread_closure->simple);
212
213         if (run_in_thread_closure->cancellable != NULL)
214                 g_object_unref (run_in_thread_closure->cancellable);
215
216         g_slice_free (RunInThreadClosure, run_in_thread_closure);
217 }
218
219 static void
220 free_zone_cb (gpointer zone)
221 {
222         icaltimezone_free (zone, 1);
223 }
224
225 /*
226  * Well-known calendar backend properties:
227  * @CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS: Contains default calendar's email
228  *   address suggested by the backend.
229  * @CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS: Contains default alarm email
230  *   address suggested by the backend.
231  * @CAL_BACKEND_PROPERTY_DEFAULT_OBJECT: Contains iCal component string
232  *   of an #icalcomponent with the default values for properties needed.
233  *   Preferred way of retrieving this property is by
234  *   calling e_cal_client_get_default_object().
235  *
236  * See also: @CLIENT_BACKEND_PROPERTY_OPENED, @CLIENT_BACKEND_PROPERTY_OPENING,
237  *   @CLIENT_BACKEND_PROPERTY_ONLINE, @CLIENT_BACKEND_PROPERTY_READONLY
238  *   @CLIENT_BACKEND_PROPERTY_CACHE_DIR, @CLIENT_BACKEND_PROPERTY_CAPABILITIES
239  */
240
241 G_DEFINE_QUARK (e-cal-client-error-quark, e_cal_client_error)
242
243 /**
244  * e_cal_client_error_to_string:
245  *
246  * FIXME: Document me.
247  *
248  * Since: 3.2
249  **/
250 const gchar *
251 e_cal_client_error_to_string (ECalClientError code)
252 {
253         switch (code) {
254         case E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR:
255                 return _("No such calendar");
256         case E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND:
257                 return _("Object not found");
258         case E_CAL_CLIENT_ERROR_INVALID_OBJECT:
259                 return _("Invalid object");
260         case E_CAL_CLIENT_ERROR_UNKNOWN_USER:
261                 return _("Unknown user");
262         case E_CAL_CLIENT_ERROR_OBJECT_ID_ALREADY_EXISTS:
263                 return _("Object ID already exists");
264         case E_CAL_CLIENT_ERROR_INVALID_RANGE:
265                 return _("Invalid range");
266         }
267
268         return _("Unknown error");
269 }
270
271 /**
272  * e_cal_client_error_create:
273  * @code: an #ECalClientError code to create
274  * @custom_msg: custom message to use for the error; can be %NULL
275  *
276  * Returns: a new #GError containing an E_CAL_CLIENT_ERROR of the given
277  * @code. If the @custom_msg is NULL, then the error message is
278  * the one returned from e_cal_client_error_to_string() for the @code,
279  * otherwise the given message is used.
280  *
281  * Returned pointer should be freed with g_error_free().
282  *
283  * Since: 3.2
284  *
285  * Deprecated: 3.8: Just use the #GError API directly.
286  **/
287 GError *
288 e_cal_client_error_create (ECalClientError code,
289                            const gchar *custom_msg)
290 {
291         if (custom_msg == NULL)
292                 custom_msg = e_cal_client_error_to_string (code);
293
294         return g_error_new_literal (E_CAL_CLIENT_ERROR, code, custom_msg);
295 }
296
297 static gpointer
298 cal_client_dbus_thread (gpointer user_data)
299 {
300         GMainContext *main_context = user_data;
301         GMainLoop *main_loop;
302
303         g_main_context_push_thread_default (main_context);
304
305         main_loop = g_main_loop_new (main_context, FALSE);
306         g_main_loop_run (main_loop);
307         g_main_loop_unref (main_loop);
308
309         g_main_context_pop_thread_default (main_context);
310
311         g_main_context_unref (main_context);
312
313         return NULL;
314 }
315
316 static gpointer
317 cal_client_dbus_thread_init (gpointer unused)
318 {
319         GMainContext *main_context;
320
321         main_context = g_main_context_new ();
322
323         /* This thread terminates when the process itself terminates, so
324          * no need to worry about unreferencing the returned GThread. */
325         g_thread_new (
326                 "cal-client-dbus-thread",
327                 cal_client_dbus_thread,
328                 g_main_context_ref (main_context));
329
330         return main_context;
331 }
332
333 static GMainContext *
334 cal_client_ref_dbus_main_context (void)
335 {
336         static GOnce cal_client_dbus_thread_once = G_ONCE_INIT;
337
338         g_once (
339                 &cal_client_dbus_thread_once,
340                 cal_client_dbus_thread_init, NULL);
341
342         return g_main_context_ref (cal_client_dbus_thread_once.retval);
343 }
344
345 static gboolean
346 cal_client_run_in_dbus_thread_idle_cb (gpointer user_data)
347 {
348         RunInThreadClosure *closure = user_data;
349         GObject *source_object;
350         GAsyncResult *result;
351
352         result = G_ASYNC_RESULT (closure->simple);
353         source_object = g_async_result_get_source_object (result);
354
355         closure->func (
356                 closure->simple,
357                 source_object,
358                 closure->cancellable);
359
360         if (source_object != NULL)
361                 g_object_unref (source_object);
362
363         g_simple_async_result_complete_in_idle (closure->simple);
364
365         return FALSE;
366 }
367
368 static void
369 cal_client_run_in_dbus_thread (GSimpleAsyncResult *simple,
370                                GSimpleAsyncThreadFunc func,
371                                gint io_priority,
372                                GCancellable *cancellable)
373 {
374         RunInThreadClosure *closure;
375         GMainContext *main_context;
376         GSource *idle_source;
377
378         main_context = cal_client_ref_dbus_main_context ();
379
380         closure = g_slice_new0 (RunInThreadClosure);
381         closure->func = func;
382         closure->simple = g_object_ref (simple);
383
384         if (G_IS_CANCELLABLE (cancellable))
385                 closure->cancellable = g_object_ref (cancellable);
386
387         idle_source = g_idle_source_new ();
388         g_source_set_priority (idle_source, io_priority);
389         g_source_set_callback (
390                 idle_source, cal_client_run_in_dbus_thread_idle_cb,
391                 closure, (GDestroyNotify) run_in_thread_closure_free);
392         g_source_attach (idle_source, main_context);
393         g_source_unref (idle_source);
394
395         g_main_context_unref (main_context);
396 }
397
398 static gboolean
399 cal_client_emit_backend_died_idle_cb (gpointer user_data)
400 {
401         SignalClosure *signal_closure = user_data;
402         EClient *client;
403
404         client = g_weak_ref_get (&signal_closure->client);
405
406         if (client != NULL) {
407                 g_signal_emit_by_name (client, "backend-died");
408                 g_object_unref (client);
409         }
410
411         return FALSE;
412 }
413
414 static gboolean
415 cal_client_emit_backend_error_idle_cb (gpointer user_data)
416 {
417         SignalClosure *signal_closure = user_data;
418         EClient *client;
419
420         client = g_weak_ref_get (&signal_closure->client);
421
422         if (client != NULL) {
423                 g_signal_emit_by_name (
424                         client, "backend-error",
425                         signal_closure->error_message);
426                 g_object_unref (client);
427         }
428
429         return FALSE;
430 }
431
432 static gboolean
433 cal_client_emit_backend_property_changed_idle_cb (gpointer user_data)
434 {
435         SignalClosure *signal_closure = user_data;
436         EClient *client;
437
438         client = g_weak_ref_get (&signal_closure->client);
439
440         if (client != NULL) {
441                 gchar *prop_value = NULL;
442
443                 /* XXX Despite appearances, this function does not block. */
444                 e_client_get_backend_property_sync (
445                         client,
446                         signal_closure->property_name,
447                         &prop_value, NULL, NULL);
448
449                 if (prop_value != NULL) {
450                         g_signal_emit_by_name (
451                                 client,
452                                 "backend-property-changed",
453                                 signal_closure->property_name,
454                                 prop_value);
455                         g_free (prop_value);
456                 }
457
458                 g_object_unref (client);
459         }
460
461         return FALSE;
462 }
463
464 static gboolean
465 cal_client_emit_free_busy_data_idle_cb (gpointer user_data)
466 {
467         SignalClosure *signal_closure = user_data;
468         EClient *client;
469
470         client = g_weak_ref_get (&signal_closure->client);
471
472         if (client != NULL) {
473                 GSList *list = NULL;
474                 gchar **strv;
475                 gint ii;
476
477                 strv = signal_closure->free_busy_data;
478
479                 for (ii = 0; strv[ii] != NULL; ii++) {
480                         ECalComponent *comp;
481                         icalcomponent *icalcomp;
482                         icalcomponent_kind kind;
483
484                         icalcomp = icalcomponent_new_from_string (strv[ii]);
485                         if (icalcomp == NULL)
486                                 continue;
487
488                         kind = icalcomponent_isa (icalcomp);
489                         if (kind != ICAL_VFREEBUSY_COMPONENT) {
490                                 icalcomponent_free (icalcomp);
491                                 continue;
492                         }
493
494                         comp = e_cal_component_new ();
495                         if (!e_cal_component_set_icalcomponent (comp, icalcomp)) {
496                                 icalcomponent_free (icalcomp);
497                                 g_object_unref (comp);
498                                 continue;
499                         }
500
501                         list = g_slist_prepend (list, comp);
502                 }
503
504                 list = g_slist_reverse (list);
505
506                 g_signal_emit (client, signals[FREE_BUSY_DATA], 0, list);
507
508                 g_slist_free_full (list, (GDestroyNotify) g_object_unref);
509
510                 g_object_unref (client);
511         }
512
513         return FALSE;
514 }
515
516 static gboolean
517 cal_client_emit_timezone_added_idle_cb (gpointer user_data)
518 {
519         SignalClosure *signal_closure = user_data;
520         EClient *client;
521
522         client = g_weak_ref_get (&signal_closure->client);
523
524         if (client != NULL) {
525                 g_signal_emit_by_name (
526                         client, "timezone-added",
527                         signal_closure->cached_zone);
528                 g_object_unref (client);
529         }
530
531         return FALSE;
532 }
533
534 static void
535 cal_client_dbus_proxy_error_cb (EDBusCalendar *dbus_proxy,
536                                 const gchar *error_message,
537                                 EClient *client)
538 {
539         GSource *idle_source;
540         GMainContext *main_context;
541         SignalClosure *signal_closure;
542
543         signal_closure = g_slice_new0 (SignalClosure);
544         g_weak_ref_set (&signal_closure->client, client);
545         signal_closure->error_message = g_strdup (error_message);
546
547         main_context = e_client_ref_main_context (client);
548
549         idle_source = g_idle_source_new ();
550         g_source_set_callback (
551                 idle_source,
552                 cal_client_emit_backend_error_idle_cb,
553                 signal_closure,
554                 (GDestroyNotify) signal_closure_free);
555         g_source_attach (idle_source, main_context);
556         g_source_unref (idle_source);
557
558         g_main_context_unref (main_context);
559 }
560
561 static void
562 cal_client_dbus_proxy_notify_cb (EDBusCalendar *dbus_proxy,
563                                  GParamSpec *pspec,
564                                  EClient *client)
565 {
566         const gchar *backend_prop_name = NULL;
567
568         if (g_str_equal (pspec->name, "alarm-email-address")) {
569                 backend_prop_name = CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS;
570         }
571
572         if (g_str_equal (pspec->name, "cache-dir")) {
573                 backend_prop_name = CLIENT_BACKEND_PROPERTY_CACHE_DIR;
574         }
575
576         if (g_str_equal (pspec->name, "cal-email-address")) {
577                 backend_prop_name = CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS;
578         }
579
580         if (g_str_equal (pspec->name, "capabilities")) {
581                 gchar **strv;
582                 gchar *csv = NULL;
583
584                 backend_prop_name = CLIENT_BACKEND_PROPERTY_CAPABILITIES;
585
586                 strv = e_dbus_calendar_dup_capabilities (dbus_proxy);
587                 if (strv != NULL) {
588                         csv = g_strjoinv (",", strv);
589                         g_strfreev (strv);
590                 }
591                 e_client_set_capabilities (client, csv);
592                 g_free (csv);
593         }
594
595         if (g_str_equal (pspec->name, "default-object")) {
596                 backend_prop_name = CAL_BACKEND_PROPERTY_DEFAULT_OBJECT;
597         }
598
599         if (g_str_equal (pspec->name, "online")) {
600                 gboolean online;
601
602                 backend_prop_name = CLIENT_BACKEND_PROPERTY_ONLINE;
603
604                 online = e_dbus_calendar_get_online (dbus_proxy);
605                 e_client_set_online (client, online);
606         }
607
608         if (g_str_equal (pspec->name, "revision")) {
609                 backend_prop_name = CLIENT_BACKEND_PROPERTY_REVISION;
610         }
611
612         if (g_str_equal (pspec->name, "writable")) {
613                 gboolean writable;
614
615                 backend_prop_name = CLIENT_BACKEND_PROPERTY_READONLY;
616
617                 writable = e_dbus_calendar_get_writable (dbus_proxy);
618                 e_client_set_readonly (client, !writable);
619         }
620
621         if (backend_prop_name != NULL) {
622                 GSource *idle_source;
623                 GMainContext *main_context;
624                 SignalClosure *signal_closure;
625
626                 signal_closure = g_slice_new0 (SignalClosure);
627                 g_weak_ref_set (&signal_closure->client, client);
628                 signal_closure->property_name = g_strdup (backend_prop_name);
629
630                 main_context = e_client_ref_main_context (client);
631
632                 idle_source = g_idle_source_new ();
633                 g_source_set_callback (
634                         idle_source,
635                         cal_client_emit_backend_property_changed_idle_cb,
636                         signal_closure,
637                         (GDestroyNotify) signal_closure_free);
638                 g_source_attach (idle_source, main_context);
639                 g_source_unref (idle_source);
640
641                 g_main_context_unref (main_context);
642         }
643 }
644
645 static void
646 cal_client_dbus_proxy_free_busy_data_cb (EDBusCalendar *dbus_proxy,
647                                          gchar **free_busy_data,
648                                          EClient *client)
649 {
650         GSource *idle_source;
651         GMainContext *main_context;
652         SignalClosure *signal_closure;
653
654         signal_closure = g_slice_new0 (SignalClosure);
655         g_weak_ref_set (&signal_closure->client, client);
656         signal_closure->free_busy_data = g_strdupv (free_busy_data);
657
658         main_context = e_client_ref_main_context (client);
659
660         idle_source = g_idle_source_new ();
661         g_source_set_callback (
662                 idle_source,
663                 cal_client_emit_free_busy_data_idle_cb,
664                 signal_closure,
665                 (GDestroyNotify) signal_closure_free);
666         g_source_attach (idle_source, main_context);
667         g_source_unref (idle_source);
668
669         g_main_context_unref (main_context);
670 }
671
672 static void
673 cal_client_name_vanished_cb (GDBusConnection *connection,
674                              const gchar *name,
675                              EClient *client)
676 {
677         GSource *idle_source;
678         GMainContext *main_context;
679         SignalClosure *signal_closure;
680
681         signal_closure = g_slice_new0 (SignalClosure);
682         g_weak_ref_set (&signal_closure->client, client);
683
684         main_context = e_client_ref_main_context (client);
685
686         idle_source = g_idle_source_new ();
687         g_source_set_callback (
688                 idle_source,
689                 cal_client_emit_backend_died_idle_cb,
690                 signal_closure,
691                 (GDestroyNotify) signal_closure_free);
692         g_source_attach (idle_source, main_context);
693         g_source_unref (idle_source);
694
695         g_main_context_unref (main_context);
696 }
697
698 static void
699 cal_client_close_cb (GObject *source_object,
700                      GAsyncResult *result,
701                      gpointer user_data)
702 {
703         GError *error = NULL;
704
705         e_dbus_calendar_call_close_finish (
706                 E_DBUS_CALENDAR (source_object), result, &error);
707
708         if (error != NULL) {
709                 g_warning ("%s: %s", G_STRFUNC, error->message);
710                 g_error_free (error);
711         }
712 }
713
714 static void
715 cal_client_set_source_type (ECalClient *cal_client,
716                             ECalClientSourceType source_type)
717 {
718         cal_client->priv->source_type = source_type;
719 }
720
721 static void
722 cal_client_set_property (GObject *object,
723                          guint property_id,
724                          const GValue *value,
725                          GParamSpec *pspec)
726 {
727         switch (property_id) {
728                 case PROP_DEFAULT_TIMEZONE:
729                         e_cal_client_set_default_timezone (
730                                 E_CAL_CLIENT (object),
731                                 g_value_get_pointer (value));
732                         return;
733
734                 case PROP_SOURCE_TYPE:
735                         cal_client_set_source_type (
736                                 E_CAL_CLIENT (object),
737                                 g_value_get_enum (value));
738                         return;
739         }
740
741         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
742 }
743
744 static void
745 cal_client_get_property (GObject *object,
746                          guint property_id,
747                          GValue *value,
748                          GParamSpec *pspec)
749 {
750         switch (property_id) {
751                 case PROP_DEFAULT_TIMEZONE:
752                         g_value_set_pointer (
753                                 value,
754                                 e_cal_client_get_default_timezone (
755                                 E_CAL_CLIENT (object)));
756                         return;
757
758                 case PROP_SOURCE_TYPE:
759                         g_value_set_enum (
760                                 value,
761                                 e_cal_client_get_source_type (
762                                 E_CAL_CLIENT (object)));
763                         return;
764         }
765
766         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
767 }
768
769 static void
770 cal_client_dispose (GObject *object)
771 {
772         ECalClientPrivate *priv;
773
774         priv = E_CAL_CLIENT_GET_PRIVATE (object);
775
776         if (priv->dbus_proxy_error_handler_id > 0) {
777                 g_signal_handler_disconnect (
778                         priv->dbus_proxy,
779                         priv->dbus_proxy_error_handler_id);
780                 priv->dbus_proxy_error_handler_id = 0;
781         }
782
783         if (priv->dbus_proxy_notify_handler_id > 0) {
784                 g_signal_handler_disconnect (
785                         priv->dbus_proxy,
786                         priv->dbus_proxy_notify_handler_id);
787                 priv->dbus_proxy_notify_handler_id = 0;
788         }
789
790         if (priv->dbus_proxy_free_busy_data_handler_id > 0) {
791                 g_signal_handler_disconnect (
792                         priv->dbus_proxy,
793                         priv->dbus_proxy_free_busy_data_handler_id);
794                 priv->dbus_proxy_free_busy_data_handler_id = 0;
795         }
796
797         if (priv->dbus_proxy != NULL) {
798                 /* Call close() asynchronously
799                  * so we don't block dispose(). */
800                 e_dbus_calendar_call_close (
801                         priv->dbus_proxy, NULL,
802                         cal_client_close_cb, NULL);
803                 g_object_unref (priv->dbus_proxy);
804                 priv->dbus_proxy = NULL;
805         }
806
807         /* Chain up to parent's dispose() method. */
808         G_OBJECT_CLASS (e_cal_client_parent_class)->dispose (object);
809 }
810
811 static void
812 cal_client_finalize (GObject *object)
813 {
814         ECalClientPrivate *priv;
815
816         priv = E_CAL_CLIENT_GET_PRIVATE (object);
817
818         if (priv->name_watcher_id > 0)
819                 g_bus_unwatch_name (priv->name_watcher_id);
820
821         if (priv->default_zone && priv->default_zone != icaltimezone_get_utc_timezone ())
822                 icaltimezone_free (priv->default_zone, 1);
823
824         g_mutex_lock (&priv->zone_cache_lock);
825         g_hash_table_destroy (priv->zone_cache);
826         g_mutex_unlock (&priv->zone_cache_lock);
827
828         g_mutex_clear (&priv->zone_cache_lock);
829
830         /* Chain up to parent's finalize() method. */
831         G_OBJECT_CLASS (e_cal_client_parent_class)->finalize (object);
832 }
833
834 static GDBusProxy *
835 cal_client_get_dbus_proxy (EClient *client)
836 {
837         ECalClientPrivate *priv;
838
839         priv = E_CAL_CLIENT_GET_PRIVATE (client);
840
841         return G_DBUS_PROXY (priv->dbus_proxy);
842 }
843
844 static gboolean
845 cal_client_get_backend_property_sync (EClient *client,
846                                       const gchar *prop_name,
847                                       gchar **prop_value,
848                                       GCancellable *cancellable,
849                                       GError **error)
850 {
851         ECalClient *cal_client;
852         EDBusCalendar *dbus_proxy;
853         gchar **strv;
854
855         cal_client = E_CAL_CLIENT (client);
856         dbus_proxy = cal_client->priv->dbus_proxy;
857
858         if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_OPENED)) {
859                 *prop_value = g_strdup ("TRUE");
860                 return TRUE;
861         }
862
863         if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_OPENING)) {
864                 *prop_value = g_strdup ("FALSE");
865                 return TRUE;
866         }
867
868         if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_ONLINE)) {
869                 if (e_dbus_calendar_get_online (dbus_proxy))
870                         *prop_value = g_strdup ("TRUE");
871                 else
872                         *prop_value = g_strdup ("FALSE");
873                 return TRUE;
874         }
875
876         if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_READONLY)) {
877                 if (e_dbus_calendar_get_writable (dbus_proxy))
878                         *prop_value = g_strdup ("FALSE");
879                 else
880                         *prop_value = g_strdup ("TRUE");
881                 return TRUE;
882         }
883
884         if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CACHE_DIR)) {
885                 *prop_value = e_dbus_calendar_dup_cache_dir (dbus_proxy);
886                 return TRUE;
887         }
888
889         if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_REVISION)) {
890                 *prop_value = e_dbus_calendar_dup_revision (dbus_proxy);
891                 return TRUE;
892         }
893
894         if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
895                 strv = e_dbus_calendar_dup_capabilities (dbus_proxy);
896                 if (strv != NULL)
897                         *prop_value = g_strjoinv (",", strv);
898                 else
899                         *prop_value = g_strdup ("");
900                 g_strfreev (strv);
901                 return TRUE;
902         }
903
904         if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS)) {
905                 *prop_value = e_dbus_calendar_dup_alarm_email_address (dbus_proxy);
906                 return TRUE;
907         }
908
909         if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS)) {
910                 *prop_value = e_dbus_calendar_dup_cal_email_address (dbus_proxy);
911                 return TRUE;
912         }
913
914         if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_DEFAULT_OBJECT)) {
915                 *prop_value = e_dbus_calendar_dup_default_object (dbus_proxy);
916                 return TRUE;
917         }
918
919         g_set_error (
920                 error, E_CLIENT_ERROR, E_CLIENT_ERROR_NOT_SUPPORTED,
921                 _("Unknown calendar property '%s'"), prop_name);
922
923         return FALSE;
924 }
925
926 static gboolean
927 cal_client_set_backend_property_sync (EClient *client,
928                                       const gchar *prop_name,
929                                       const gchar *prop_value,
930                                       GCancellable *cancellable,
931                                       GError **error)
932 {
933         g_set_error (
934                 error, E_CLIENT_ERROR,
935                 E_CLIENT_ERROR_NOT_SUPPORTED,
936                 _("Cannot change value of calendar property '%s'"),
937                 prop_name);
938
939         return FALSE;
940 }
941
942 static gboolean
943 cal_client_open_sync (EClient *client,
944                       gboolean only_if_exists,
945                       GCancellable *cancellable,
946                       GError **error)
947 {
948         ECalClient *cal_client;
949
950         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
951
952         cal_client = E_CAL_CLIENT (client);
953
954         return e_dbus_calendar_call_open_sync (
955                 cal_client->priv->dbus_proxy, cancellable, error);
956 }
957
958 static gboolean
959 cal_client_refresh_sync (EClient *client,
960                          GCancellable *cancellable,
961                          GError **error)
962 {
963         ECalClient *cal_client;
964
965         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
966
967         cal_client = E_CAL_CLIENT (client);
968
969         return e_dbus_calendar_call_refresh_sync (
970                 cal_client->priv->dbus_proxy, cancellable, error);
971 }
972
973 static void
974 cal_client_init_in_dbus_thread (GSimpleAsyncResult *simple,
975                                 GObject *source_object,
976                                 GCancellable *cancellable)
977 {
978         ECalClientPrivate *priv;
979         EDBusCalendarFactory *factory_proxy;
980         GDBusConnection *connection;
981         GDBusProxy *proxy;
982         EClient *client;
983         ESource *source;
984         const gchar *uid;
985         gchar *object_path = NULL;
986         gulong handler_id;
987         GError *error = NULL;
988
989         priv = E_CAL_CLIENT_GET_PRIVATE (source_object);
990
991         client = E_CLIENT (source_object);
992         source = e_client_get_source (client);
993         uid = e_source_get_uid (source);
994
995         connection = g_bus_get_sync (G_BUS_TYPE_SESSION, cancellable, &error);
996
997         /* Sanity check. */
998         g_return_if_fail (
999                 ((connection != NULL) && (error == NULL)) ||
1000                 ((connection == NULL) && (error != NULL)));
1001
1002         if (error != NULL) {
1003                 g_simple_async_result_take_error (simple, error);
1004                 return;
1005         }
1006
1007         factory_proxy = e_dbus_calendar_factory_proxy_new_sync (
1008                 connection,
1009                 G_DBUS_PROXY_FLAGS_NONE,
1010                 CALENDAR_DBUS_SERVICE_NAME,
1011                 "/org/gnome/evolution/dataserver/CalendarFactory",
1012                 cancellable, &error);
1013
1014         /* Sanity check. */
1015         g_return_if_fail (
1016                 ((factory_proxy != NULL) && (error == NULL)) ||
1017                 ((factory_proxy == NULL) && (error != NULL)));
1018
1019         if (error != NULL) {
1020                 g_simple_async_result_take_error (simple, error);
1021                 g_object_unref (connection);
1022                 return;
1023         }
1024
1025         switch (e_cal_client_get_source_type (E_CAL_CLIENT (client))) {
1026                 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
1027                         e_dbus_calendar_factory_call_open_calendar_sync (
1028                                 factory_proxy, uid, &object_path,
1029                                 cancellable, &error);
1030                         break;
1031                 case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
1032                         e_dbus_calendar_factory_call_open_task_list_sync (
1033                                 factory_proxy, uid, &object_path,
1034                                 cancellable, &error);
1035                         break;
1036                 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
1037                         e_dbus_calendar_factory_call_open_memo_list_sync (
1038                                 factory_proxy, uid, &object_path,
1039                                 cancellable, &error);
1040                         break;
1041                 default:
1042                         g_return_if_reached ();
1043         }
1044
1045         g_object_unref (factory_proxy);
1046
1047         /* Sanity check. */
1048         g_return_if_fail (
1049                 ((object_path != NULL) && (error == NULL)) ||
1050                 ((object_path == NULL) && (error != NULL)));
1051
1052         if (object_path == NULL) {
1053                 g_simple_async_result_take_error (simple, error);
1054                 g_object_unref (connection);
1055                 return;
1056         }
1057
1058         priv->dbus_proxy = e_dbus_calendar_proxy_new_sync (
1059                 connection,
1060                 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
1061                 CALENDAR_DBUS_SERVICE_NAME,
1062                 object_path, cancellable, &error);
1063
1064         g_free (object_path);
1065
1066         /* Sanity check. */
1067         g_return_if_fail (
1068                 ((priv->dbus_proxy != NULL) && (error == NULL)) ||
1069                 ((priv->dbus_proxy == NULL) && (error != NULL)));
1070
1071         if (error != NULL) {
1072                 g_simple_async_result_take_error (simple, error);
1073                 g_object_unref (connection);
1074                 return;
1075         }
1076
1077         /* Configure our new GDBusProxy. */
1078
1079         proxy = G_DBUS_PROXY (priv->dbus_proxy);
1080
1081         g_dbus_proxy_set_default_timeout (proxy, DBUS_PROXY_TIMEOUT_MS);
1082
1083         priv->name_watcher_id = g_bus_watch_name_on_connection (
1084                 connection,
1085                 g_dbus_proxy_get_name (proxy),
1086                 G_BUS_NAME_WATCHER_FLAGS_NONE,
1087                 (GBusNameAppearedCallback) NULL,
1088                 (GBusNameVanishedCallback) cal_client_name_vanished_cb,
1089                 client, (GDestroyNotify) NULL);
1090
1091         handler_id = g_signal_connect_object (
1092                 proxy, "error",
1093                 G_CALLBACK (cal_client_dbus_proxy_error_cb),
1094                 client, 0);
1095         priv->dbus_proxy_error_handler_id = handler_id;
1096
1097         handler_id = g_signal_connect_object (
1098                 proxy, "notify",
1099                 G_CALLBACK (cal_client_dbus_proxy_notify_cb),
1100                 client, 0);
1101         priv->dbus_proxy_notify_handler_id = handler_id;
1102
1103         handler_id = g_signal_connect_object (
1104                 proxy, "free-busy-data",
1105                 G_CALLBACK (cal_client_dbus_proxy_free_busy_data_cb),
1106                 client, 0);
1107         priv->dbus_proxy_free_busy_data_handler_id = handler_id;
1108
1109         /* Initialize our public-facing GObject properties. */
1110         g_object_notify (G_OBJECT (proxy), "online");
1111         g_object_notify (G_OBJECT (proxy), "writable");
1112         g_object_notify (G_OBJECT (proxy), "capabilities");
1113
1114         g_object_unref (connection);
1115 }
1116
1117 static gboolean
1118 cal_client_initable_init (GInitable *initable,
1119                           GCancellable *cancellable,
1120                           GError **error)
1121 {
1122         EAsyncClosure *closure;
1123         GAsyncResult *result;
1124         gboolean success;
1125
1126         closure = e_async_closure_new ();
1127
1128         g_async_initable_init_async (
1129                 G_ASYNC_INITABLE (initable),
1130                 G_PRIORITY_DEFAULT, cancellable,
1131                 e_async_closure_callback, closure);
1132
1133         result = e_async_closure_wait (closure);
1134
1135         success = g_async_initable_init_finish (
1136                 G_ASYNC_INITABLE (initable), result, error);
1137
1138         e_async_closure_free (closure);
1139
1140         return success;
1141 }
1142
1143 static void
1144 cal_client_initable_init_async (GAsyncInitable *initable,
1145                                 gint io_priority,
1146                                 GCancellable *cancellable,
1147                                 GAsyncReadyCallback callback,
1148                                 gpointer user_data)
1149 {
1150         GSimpleAsyncResult *simple;
1151
1152         simple = g_simple_async_result_new (
1153                 G_OBJECT (initable), callback, user_data,
1154                 cal_client_initable_init_async);
1155
1156         g_simple_async_result_set_check_cancellable (simple, cancellable);
1157
1158         cal_client_run_in_dbus_thread (
1159                 simple, cal_client_init_in_dbus_thread,
1160                 io_priority, cancellable);
1161
1162         g_object_unref (simple);
1163 }
1164
1165 static gboolean
1166 cal_client_initable_init_finish (GAsyncInitable *initable,
1167                                  GAsyncResult *result,
1168                                  GError **error)
1169 {
1170         GSimpleAsyncResult *simple;
1171
1172         g_return_val_if_fail (
1173                 g_simple_async_result_is_valid (
1174                 result, G_OBJECT (initable),
1175                 cal_client_initable_init_async), FALSE);
1176
1177         simple = G_SIMPLE_ASYNC_RESULT (result);
1178
1179         /* Assume success unless a GError is set. */
1180         return !g_simple_async_result_propagate_error (simple, error);
1181 }
1182
1183 static void
1184 cal_client_add_cached_timezone (ETimezoneCache *cache,
1185                                 icaltimezone *zone)
1186 {
1187         ECalClientPrivate *priv;
1188         const gchar *tzid;
1189
1190         priv = E_CAL_CLIENT_GET_PRIVATE (cache);
1191
1192         g_mutex_lock (&priv->zone_cache_lock);
1193
1194         tzid = icaltimezone_get_tzid (zone);
1195
1196         /* Avoid replacing an existing cache entry.  We don't want to
1197          * invalidate any icaltimezone pointers that may have already
1198          * been returned through e_timezone_cache_get_timezone(). */
1199         if (!g_hash_table_contains (priv->zone_cache, tzid)) {
1200                 GSource *idle_source;
1201                 GMainContext *main_context;
1202                 SignalClosure *signal_closure;
1203
1204                 icalcomponent *icalcomp;
1205                 icaltimezone *cached_zone;
1206
1207                 cached_zone = icaltimezone_new ();
1208                 icalcomp = icaltimezone_get_component (zone);
1209                 icalcomp = icalcomponent_new_clone (icalcomp);
1210                 icaltimezone_set_component (cached_zone, icalcomp);
1211
1212                 g_hash_table_insert (
1213                         priv->zone_cache,
1214                         g_strdup (tzid), cached_zone);
1215
1216                 /* The closure's client reference will keep the
1217                  * internally cached icaltimezone alive for the
1218                  * duration of the idle callback. */
1219                 signal_closure = g_slice_new0 (SignalClosure);
1220                 g_weak_ref_set (&signal_closure->client, cache);
1221                 signal_closure->cached_zone = cached_zone;
1222
1223                 main_context = e_client_ref_main_context (E_CLIENT (cache));
1224
1225                 idle_source = g_idle_source_new ();
1226                 g_source_set_callback (
1227                         idle_source,
1228                         cal_client_emit_timezone_added_idle_cb,
1229                         signal_closure,
1230                         (GDestroyNotify) signal_closure_free);
1231                 g_source_attach (idle_source, main_context);
1232                 g_source_unref (idle_source);
1233
1234                 g_main_context_unref (main_context);
1235         }
1236
1237         g_mutex_unlock (&priv->zone_cache_lock);
1238 }
1239
1240 static icaltimezone *
1241 cal_client_get_cached_timezone (ETimezoneCache *cache,
1242                                 const gchar *tzid)
1243 {
1244         ECalClientPrivate *priv;
1245         icaltimezone *zone = NULL;
1246         icaltimezone *builtin_zone = NULL;
1247         icalcomponent *icalcomp;
1248         icalproperty *prop;
1249         const gchar *builtin_tzid;
1250
1251         priv = E_CAL_CLIENT_GET_PRIVATE (cache);
1252
1253         if (g_str_equal (tzid, "UTC"))
1254                 return icaltimezone_get_utc_timezone ();
1255
1256         g_mutex_lock (&priv->zone_cache_lock);
1257
1258         /* See if we already have it in the cache. */
1259         zone = g_hash_table_lookup (priv->zone_cache, tzid);
1260
1261         if (zone != NULL)
1262                 goto exit;
1263
1264         /* Try to replace the original time zone with a more complete
1265          * and/or potentially updated built-in time zone.  Note this also
1266          * applies to TZIDs which match built-in time zones exactly: they
1267          * are extracted via icaltimezone_get_builtin_timezone_from_tzid()
1268          * below without a roundtrip to the backend. */
1269
1270         builtin_tzid = e_cal_match_tzid (tzid);
1271
1272         if (builtin_tzid != NULL)
1273                 builtin_zone = icaltimezone_get_builtin_timezone_from_tzid (
1274                         builtin_tzid);
1275
1276         if (builtin_zone == NULL)
1277                 goto exit;
1278
1279         /* Use the built-in time zone *and* rename it.  Likely the caller
1280          * is asking for a specific TZID because it has an event with such
1281          * a TZID.  Returning an icaltimezone with a different TZID would
1282          * lead to broken VCALENDARs in the caller. */
1283
1284         icalcomp = icaltimezone_get_component (builtin_zone);
1285         icalcomp = icalcomponent_new_clone (icalcomp);
1286
1287         prop = icalcomponent_get_first_property (
1288                 icalcomp, ICAL_ANY_PROPERTY);
1289
1290         while (prop != NULL) {
1291                 if (icalproperty_isa (prop) == ICAL_TZID_PROPERTY) {
1292                         icalproperty_set_value_from_string (prop, tzid, "NO");
1293                         break;
1294                 }
1295
1296                 prop = icalcomponent_get_next_property (
1297                         icalcomp, ICAL_ANY_PROPERTY);
1298         }
1299
1300         if (icalcomp != NULL) {
1301                 zone = icaltimezone_new ();
1302                 if (icaltimezone_set_component (zone, icalcomp)) {
1303                         tzid = icaltimezone_get_tzid (zone);
1304                         g_hash_table_insert (
1305                                 priv->zone_cache,
1306                                 g_strdup (tzid), zone);
1307                 } else {
1308                         icalcomponent_free (icalcomp);
1309                         icaltimezone_free (zone, 1);
1310                         zone = NULL;
1311                 }
1312         }
1313
1314 exit:
1315         g_mutex_unlock (&priv->zone_cache_lock);
1316
1317         return zone;
1318 }
1319
1320 static GList *
1321 cal_client_list_cached_timezones (ETimezoneCache *cache)
1322 {
1323         ECalClientPrivate *priv;
1324         GList *list;
1325
1326         priv = E_CAL_CLIENT_GET_PRIVATE (cache);
1327
1328         g_mutex_lock (&priv->zone_cache_lock);
1329
1330         list = g_hash_table_get_values (priv->zone_cache);
1331
1332         g_mutex_unlock (&priv->zone_cache_lock);
1333
1334         return list;
1335 }
1336
1337 static void
1338 e_cal_client_class_init (ECalClientClass *class)
1339 {
1340         GObjectClass *object_class;
1341         EClientClass *client_class;
1342
1343         g_type_class_add_private (class, sizeof (ECalClientPrivate));
1344
1345         object_class = G_OBJECT_CLASS (class);
1346         object_class->set_property = cal_client_set_property;
1347         object_class->get_property = cal_client_get_property;
1348         object_class->dispose = cal_client_dispose;
1349         object_class->finalize = cal_client_finalize;
1350
1351         client_class = E_CLIENT_CLASS (class);
1352         client_class->get_dbus_proxy = cal_client_get_dbus_proxy;
1353         client_class->get_backend_property_sync = cal_client_get_backend_property_sync;
1354         client_class->set_backend_property_sync = cal_client_set_backend_property_sync;
1355         client_class->open_sync = cal_client_open_sync;
1356         client_class->refresh_sync = cal_client_refresh_sync;
1357
1358         g_object_class_install_property (
1359                 object_class,
1360                 PROP_DEFAULT_TIMEZONE,
1361                 g_param_spec_pointer (
1362                         "default-timezone",
1363                         "Default Timezone",
1364                         "Timezone used to resolve DATE "
1365                         "and floating DATE-TIME values",
1366                         G_PARAM_READWRITE |
1367                         G_PARAM_STATIC_STRINGS));
1368
1369         g_object_class_install_property (
1370                 object_class,
1371                 PROP_SOURCE_TYPE,
1372                 g_param_spec_enum (
1373                         "source-type",
1374                         "Source Type",
1375                         "The iCalendar data type",
1376                         E_TYPE_CAL_CLIENT_SOURCE_TYPE,
1377                         E_CAL_CLIENT_SOURCE_TYPE_EVENTS,
1378                         G_PARAM_READWRITE |
1379                         G_PARAM_CONSTRUCT_ONLY |
1380                         G_PARAM_STATIC_STRINGS));
1381
1382         signals[FREE_BUSY_DATA] = g_signal_new (
1383                 "free-busy-data",
1384                 G_OBJECT_CLASS_TYPE (class),
1385                 G_SIGNAL_RUN_FIRST,
1386                 G_STRUCT_OFFSET (ECalClientClass, free_busy_data),
1387                 NULL, NULL,
1388                 g_cclosure_marshal_VOID__POINTER,
1389                 G_TYPE_NONE, 1,
1390                 G_TYPE_POINTER);
1391 }
1392
1393 static void
1394 e_cal_client_initable_init (GInitableIface *interface)
1395 {
1396         interface->init = cal_client_initable_init;
1397 }
1398
1399 static void
1400 e_cal_client_async_initable_init (GAsyncInitableIface *interface)
1401 {
1402         interface->init_async = cal_client_initable_init_async;
1403         interface->init_finish = cal_client_initable_init_finish;
1404 }
1405
1406 static void
1407 e_cal_client_timezone_cache_init (ETimezoneCacheInterface *interface)
1408 {
1409         interface->add_timezone = cal_client_add_cached_timezone;
1410         interface->get_timezone = cal_client_get_cached_timezone;
1411         interface->list_timezones = cal_client_list_cached_timezones;
1412 }
1413
1414 static void
1415 e_cal_client_init (ECalClient *client)
1416 {
1417         GHashTable *zone_cache;
1418
1419         zone_cache = g_hash_table_new_full (
1420                 (GHashFunc) g_str_hash,
1421                 (GEqualFunc) g_str_equal,
1422                 (GDestroyNotify) g_free,
1423                 (GDestroyNotify) free_zone_cb);
1424
1425         client->priv = E_CAL_CLIENT_GET_PRIVATE (client);
1426         client->priv->source_type = E_CAL_CLIENT_SOURCE_TYPE_LAST;
1427         client->priv->default_zone = icaltimezone_get_utc_timezone ();
1428         g_mutex_init (&client->priv->zone_cache_lock);
1429         client->priv->zone_cache = zone_cache;
1430 }
1431
1432 /**
1433  * e_cal_client_connect_sync:
1434  * @source: an #ESource
1435  * @source_type: source type of the calendar
1436  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
1437  * @error: return location for a #GError, or %NULL
1438  *
1439  * Creates a new #ECalClient for @source and @source_type.  If an error
1440  * occurs, the function will set @error and return %FALSE.
1441  *
1442  * Unlike with e_cal_client_new(), there is no need to call
1443  * e_client_open_sync() after obtaining the #ECalClient.
1444  *
1445  * For error handling convenience, any error message returned by this
1446  * function will have a descriptive prefix that includes the display
1447  * name of @source.
1448  *
1449  * Returns: a new #ECalClient, or %NULL
1450  *
1451  * Since: 3.8
1452  **/
1453 EClient *
1454 e_cal_client_connect_sync (ESource *source,
1455                            ECalClientSourceType source_type,
1456                            GCancellable *cancellable,
1457                            GError **error)
1458 {
1459         ECalClient *client;
1460         gboolean success;
1461
1462         g_return_val_if_fail (E_IS_SOURCE (source), NULL);
1463         g_return_val_if_fail (
1464                 source_type == E_CAL_CLIENT_SOURCE_TYPE_EVENTS ||
1465                 source_type == E_CAL_CLIENT_SOURCE_TYPE_TASKS ||
1466                 source_type == E_CAL_CLIENT_SOURCE_TYPE_MEMOS, NULL);
1467
1468         client = g_object_new (
1469                 E_TYPE_CAL_CLIENT,
1470                 "source", source,
1471                 "source-type", source_type, NULL);
1472
1473         success = g_initable_init (
1474                 G_INITABLE (client), cancellable, error);
1475
1476         if (success)
1477                 success = e_dbus_calendar_call_open_sync (
1478                         client->priv->dbus_proxy, cancellable, error);
1479
1480         if (!success) {
1481                 g_prefix_error (
1482                         error,_("Unable to connect to '%s': "),
1483                         e_source_get_display_name (source));
1484                 g_object_unref (client);
1485                 return NULL;
1486         }
1487
1488         return E_CLIENT (client);
1489 }
1490
1491 /* Helper for e_cal_client_connect() */
1492 static void
1493 cal_client_connect_open_cb (GObject *source_object,
1494                             GAsyncResult *result,
1495                             gpointer user_data)
1496 {
1497         GSimpleAsyncResult *simple;
1498         GError *error = NULL;
1499
1500         simple = G_SIMPLE_ASYNC_RESULT (user_data);
1501
1502         e_dbus_calendar_call_open_finish (
1503                 E_DBUS_CALENDAR (source_object), result, &error);
1504
1505         if (error != NULL)
1506                 g_simple_async_result_take_error (simple, error);
1507
1508         g_simple_async_result_complete (simple);
1509
1510         g_object_unref (simple);
1511 }
1512
1513 /* Helper for e_cal_client_connect() */
1514 static void
1515 cal_client_connect_init_cb (GObject *source_object,
1516                             GAsyncResult *result,
1517                             gpointer user_data)
1518 {
1519         GSimpleAsyncResult *simple;
1520         ECalClientPrivate *priv;
1521         ConnectClosure *closure;
1522         GError *error = NULL;
1523
1524         simple = G_SIMPLE_ASYNC_RESULT (user_data);
1525
1526         g_async_initable_init_finish (
1527                 G_ASYNC_INITABLE (source_object), result, &error);
1528
1529         if (error != NULL) {
1530                 g_simple_async_result_take_error (simple, error);
1531                 g_simple_async_result_complete (simple);
1532                 goto exit;
1533         }
1534
1535         /* Note, we're repurposing some function parameters. */
1536
1537         result = G_ASYNC_RESULT (simple);
1538         source_object = g_async_result_get_source_object (result);
1539         closure = g_simple_async_result_get_op_res_gpointer (simple);
1540
1541         priv = E_CAL_CLIENT_GET_PRIVATE (source_object);
1542
1543         e_dbus_calendar_call_open (
1544                 priv->dbus_proxy,
1545                 closure->cancellable,
1546                 cal_client_connect_open_cb,
1547                 g_object_ref (simple));
1548
1549         g_object_unref (source_object);
1550
1551 exit:
1552         g_object_unref (simple);
1553 }
1554
1555 /**
1556  * e_cal_client_connect:
1557  * @source: an #ESource
1558  * @source_type: source tpe of the calendar
1559  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
1560  * @callback: (scope async): a #GAsyncReadyCallback to call when the request
1561  *            is satisfied
1562  * @user_data: (closure): data to pass to the callback function
1563  *
1564  * Asynchronously creates a new #ECalClient for @source and @source_type.
1565  *
1566  * Unlike with e_cal_client_new(), there is no need to call e_client_open()
1567  * after obtaining the #ECalClient.
1568  *
1569  * When the operation is finished, @callback will be called.  You can then
1570  * call e_cal_client_connect_finish() to get the result of the operation.
1571  *
1572  * Since: 3.8
1573  **/
1574 void
1575 e_cal_client_connect (ESource *source,
1576                       ECalClientSourceType source_type,
1577                       GCancellable *cancellable,
1578                       GAsyncReadyCallback callback,
1579                       gpointer user_data)
1580 {
1581         GSimpleAsyncResult *simple;
1582         ConnectClosure *closure;
1583         ECalClient *client;
1584
1585         g_return_if_fail (E_IS_SOURCE (source));
1586         g_return_if_fail (
1587                 source_type == E_CAL_CLIENT_SOURCE_TYPE_EVENTS ||
1588                 source_type == E_CAL_CLIENT_SOURCE_TYPE_TASKS ||
1589                 source_type == E_CAL_CLIENT_SOURCE_TYPE_MEMOS);
1590
1591         /* Two things with this: 1) instantiate the client object
1592          * immediately to make sure the thread-default GMainContext
1593          * gets plucked, and 2) do not call the D-Bus open() method
1594          * from our designated D-Bus thread -- it may take a long
1595          * time and block other clients from receiving signals. */
1596
1597         closure = g_slice_new0 (ConnectClosure);
1598         closure->source = g_object_ref (source);
1599
1600         if (G_IS_CANCELLABLE (cancellable))
1601                 closure->cancellable = g_object_ref (cancellable);
1602
1603         client = g_object_new (
1604                 E_TYPE_CAL_CLIENT,
1605                 "source", source,
1606                 "source-type", source_type, NULL);
1607
1608         simple = g_simple_async_result_new (
1609                 G_OBJECT (client), callback,
1610                 user_data, e_cal_client_connect);
1611
1612         g_simple_async_result_set_check_cancellable (simple, cancellable);
1613
1614         g_simple_async_result_set_op_res_gpointer (
1615                 simple, closure, (GDestroyNotify) connect_closure_free);
1616
1617         g_async_initable_init_async (
1618                 G_ASYNC_INITABLE (client),
1619                 G_PRIORITY_DEFAULT, cancellable,
1620                 cal_client_connect_init_cb,
1621                 g_object_ref (simple));
1622
1623         g_object_unref (simple);
1624         g_object_unref (client);
1625 }
1626
1627 /**
1628  * e_cal_client_connect_finish:
1629  * @result: a #GAsyncResult
1630  * @error: return location for a #GError, or %NULL
1631  *
1632  * Finishes the operation started with e_cal_client_connect().  If an
1633  * error occurs in connecting to the D-Bus service, the function sets
1634  * @error and returns %NULL.
1635  *
1636  * For error handling convenience, any error message returned by this
1637  * function will have a descriptive prefix that includes the display
1638  * name of the #ESource passed to e_cal_client_connect().
1639  *
1640  * Returns: a new #ECalClient, or %NULL
1641  *
1642  * Since: 3.8
1643  **/
1644 EClient *
1645 e_cal_client_connect_finish (GAsyncResult *result,
1646                              GError **error)
1647 {
1648         GSimpleAsyncResult *simple;
1649         ConnectClosure *closure;
1650         gpointer source_tag;
1651
1652         g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), NULL);
1653
1654         simple = G_SIMPLE_ASYNC_RESULT (result);
1655         closure = g_simple_async_result_get_op_res_gpointer (simple);
1656
1657         source_tag = g_simple_async_result_get_source_tag (simple);
1658         g_return_val_if_fail (source_tag == e_cal_client_connect, NULL);
1659
1660         if (g_simple_async_result_propagate_error (simple, error)) {
1661                 g_prefix_error (
1662                         error, _("Unable to connect to '%s': "),
1663                         e_source_get_display_name (closure->source));
1664                 return NULL;
1665         }
1666
1667         return E_CLIENT (g_async_result_get_source_object (result));
1668 }
1669
1670 /**
1671  * e_cal_client_new:
1672  * @source: An #ESource pointer
1673  * @source_type: source type of the calendar
1674  * @error: A #GError pointer
1675  *
1676  * Creates a new #ECalClient corresponding to the given source.  There are
1677  * only two operations that are valid on this calendar at this point:
1678  * e_client_open(), and e_client_remove().
1679  *
1680  * Returns: a new but unopened #ECalClient.
1681  *
1682  * Since: 3.2
1683  *
1684  * Deprecated: 3.8: It covertly makes synchronous D-Bus calls, with no
1685  *                  way to cancel.  Use e_cal_client_connect() instead,
1686  *                  which combines e_cal_client_new() and e_client_open()
1687  *                  into one step.
1688  **/
1689 ECalClient *
1690 e_cal_client_new (ESource *source,
1691                   ECalClientSourceType source_type,
1692                   GError **error)
1693 {
1694         g_return_val_if_fail (E_IS_SOURCE (source), NULL);
1695         g_return_val_if_fail (
1696                 source_type == E_CAL_CLIENT_SOURCE_TYPE_EVENTS ||
1697                 source_type == E_CAL_CLIENT_SOURCE_TYPE_TASKS ||
1698                 source_type == E_CAL_CLIENT_SOURCE_TYPE_MEMOS, NULL);
1699
1700         return g_initable_new (
1701                 E_TYPE_CAL_CLIENT, NULL, error,
1702                 "source", source,
1703                 "source-type", source_type, NULL);
1704 }
1705
1706 /**
1707  * e_cal_client_get_source_type:
1708  * @client: A calendar client.
1709  *
1710  * Gets the source type of the calendar client.
1711  *
1712  * Returns: an #ECalClientSourceType value corresponding
1713  * to the source type of the calendar client.
1714  *
1715  * Since: 3.2
1716  **/
1717 ECalClientSourceType
1718 e_cal_client_get_source_type (ECalClient *client)
1719 {
1720         g_return_val_if_fail (
1721                 E_IS_CAL_CLIENT (client),
1722                 E_CAL_CLIENT_SOURCE_TYPE_LAST);
1723
1724         return client->priv->source_type;
1725 }
1726
1727 /**
1728  * e_cal_client_get_local_attachment_store:
1729  * @client: A calendar client.
1730  *
1731  * Queries the URL where the calendar attachments are
1732  * serialized in the local filesystem. This enable clients
1733  * to operate with the reference to attachments rather than the data itself
1734  * unless it specifically uses the attachments for open/sending
1735  * operations.
1736  *
1737  * Returns: The URL where the attachments are serialized in the
1738  * local filesystem.
1739  *
1740  * Since: 3.2
1741  **/
1742 const gchar *
1743 e_cal_client_get_local_attachment_store (ECalClient *client)
1744 {
1745         g_return_val_if_fail (E_IS_CAL_CLIENT (client), NULL);
1746
1747         return e_dbus_calendar_get_cache_dir (client->priv->dbus_proxy);
1748 }
1749
1750 /* icaltimezone_copy does a shallow copy while icaltimezone_free tries to
1751  * free the entire the contents inside the structure with libical 0.43.
1752  * Use this, till eds allows older libical.
1753  */
1754 static icaltimezone *
1755 copy_timezone (icaltimezone *ozone)
1756 {
1757         icaltimezone *zone = NULL;
1758         const gchar *tzid;
1759
1760         tzid = icaltimezone_get_tzid (ozone);
1761
1762         if (g_strcmp0 (tzid, "UTC") != 0) {
1763                 icalcomponent *comp;
1764
1765                 comp = icaltimezone_get_component (ozone);
1766                 if (comp != NULL) {
1767                         zone = icaltimezone_new ();
1768                         icaltimezone_set_component (
1769                                 zone, icalcomponent_new_clone (comp));
1770                 }
1771         }
1772
1773         if (zone == NULL)
1774                 zone = icaltimezone_get_utc_timezone ();
1775
1776         return zone;
1777 }
1778
1779 /**
1780  * e_cal_client_set_default_timezone:
1781  * @client: A calendar client.
1782  * @zone: A timezone object.
1783  *
1784  * Sets the default timezone to use to resolve DATE and floating DATE-TIME
1785  * values. This will typically be from the user's timezone setting. Call this
1786  * before using any other object fetching functions.
1787  *
1788  * Since: 3.2
1789  **/
1790 void
1791 e_cal_client_set_default_timezone (ECalClient *client,
1792                                    icaltimezone *zone)
1793 {
1794         g_return_if_fail (E_IS_CAL_CLIENT (client));
1795         g_return_if_fail (zone != NULL);
1796
1797         if (zone == client->priv->default_zone)
1798                 return;
1799
1800         if (client->priv->default_zone != icaltimezone_get_utc_timezone ())
1801                 icaltimezone_free (client->priv->default_zone, 1);
1802
1803         if (zone == icaltimezone_get_utc_timezone ())
1804                 client->priv->default_zone = zone;
1805         else
1806                 client->priv->default_zone = copy_timezone (zone);
1807
1808         g_object_notify (G_OBJECT (client), "default-timezone");
1809 }
1810
1811 /**
1812  * e_cal_client_get_default_timezone:
1813  * @client: A calendar client.
1814  *
1815  * Returns the default timezone previously set with
1816  * e_cal_client_set_default_timezone().  The returned pointer is owned by
1817  * the @client and should not be freed.
1818  *
1819  * Returns: an #icaltimezone
1820  *
1821  * Since: 3.2
1822  **/
1823 icaltimezone *
1824 e_cal_client_get_default_timezone (ECalClient *client)
1825 {
1826         g_return_val_if_fail (E_IS_CAL_CLIENT (client), NULL);
1827
1828         return client->priv->default_zone;
1829 }
1830
1831 /**
1832  * e_cal_client_check_one_alarm_only:
1833  * @client: A calendar client.
1834  *
1835  * Checks if a calendar supports only one alarm per component.
1836  *
1837  * Returns: TRUE if the calendar allows only one alarm, FALSE otherwise.
1838  *
1839  * Since: 3.2
1840  **/
1841 gboolean
1842 e_cal_client_check_one_alarm_only (ECalClient *client)
1843 {
1844         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
1845
1846         return e_client_check_capability (
1847                 E_CLIENT (client),
1848                 CAL_STATIC_CAPABILITY_ONE_ALARM_ONLY);
1849 }
1850
1851 /**
1852  * e_cal_client_check_save_schedules:
1853  * @client: A calendar client.
1854  *
1855  * Checks whether the calendar saves schedules.
1856  *
1857  * Returns: TRUE if it saves schedules, FALSE otherwise.
1858  *
1859  * Since: 3.2
1860  **/
1861 gboolean
1862 e_cal_client_check_save_schedules (ECalClient *client)
1863 {
1864         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
1865
1866         return e_client_check_capability (
1867                 E_CLIENT (client),
1868                 CAL_STATIC_CAPABILITY_SAVE_SCHEDULES);
1869 }
1870
1871 /**
1872  * e_cal_client_check_organizer_must_attend:
1873  * @client: A calendar client.
1874  *
1875  * Checks if a calendar forces organizers of meetings to be also attendees.
1876  *
1877  * Returns: TRUE if the calendar forces organizers to attend meetings,
1878  * FALSE otherwise.
1879  *
1880  * Since: 3.2
1881  **/
1882 gboolean
1883 e_cal_client_check_organizer_must_attend (ECalClient *client)
1884 {
1885         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
1886
1887         return e_client_check_capability (
1888                 E_CLIENT (client),
1889                 CAL_STATIC_CAPABILITY_ORGANIZER_MUST_ATTEND);
1890 }
1891
1892 /**
1893  * e_cal_client_check_organizer_must_accept:
1894  * @client: A calendar client.
1895  *
1896  * Checks whether a calendar requires organizer to accept their attendance to
1897  * meetings.
1898  *
1899  * Returns: TRUE if the calendar requires organizers to accept, FALSE
1900  * otherwise.
1901  *
1902  * Since: 3.2
1903  **/
1904 gboolean
1905 e_cal_client_check_organizer_must_accept (ECalClient *client)
1906 {
1907         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
1908
1909         return e_client_check_capability (
1910                 E_CLIENT (client),
1911                 CAL_STATIC_CAPABILITY_ORGANIZER_MUST_ACCEPT);
1912 }
1913
1914 /**
1915  * e_cal_client_check_recurrences_no_master:
1916  * @client: A calendar client.
1917  *
1918  * Checks if the calendar has a master object for recurrences.
1919  *
1920  * Returns: TRUE if the calendar has a master object for recurrences,
1921  * FALSE otherwise.
1922  *
1923  * Since: 3.2
1924  **/
1925 gboolean
1926 e_cal_client_check_recurrences_no_master (ECalClient *client)
1927 {
1928         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
1929
1930         return e_client_check_capability (
1931                 E_CLIENT (client),
1932                 CAL_STATIC_CAPABILITY_RECURRENCES_NO_MASTER);
1933 }
1934
1935 /**
1936  * e_cal_client_free_icalcomp_slist:
1937  * @icalcomps: (element-type icalcomponent): list of icalcomponent objects
1938  *
1939  * Frees each element of the @icalcomps list and the list itself.
1940  * Each element is an object of type #icalcomponent.
1941  *
1942  * Since: 3.2
1943  **/
1944 void
1945 e_cal_client_free_icalcomp_slist (GSList *icalcomps)
1946 {
1947         g_slist_foreach (icalcomps, (GFunc) icalcomponent_free, NULL);
1948         g_slist_free (icalcomps);
1949 }
1950
1951 /**
1952  * e_cal_client_free_ecalcomp_slist:
1953  * @ecalcomps: (element-type ECalComponent): list of #ECalComponent objects
1954  *
1955  * Frees each element of the @ecalcomps list and the list itself.
1956  * Each element is an object of type #ECalComponent.
1957  *
1958  * Since: 3.2
1959  **/
1960 void
1961 e_cal_client_free_ecalcomp_slist (GSList *ecalcomps)
1962 {
1963         g_slist_foreach (ecalcomps, (GFunc) g_object_unref, NULL);
1964         g_slist_free (ecalcomps);
1965 }
1966
1967 /**
1968  * e_cal_client_resolve_tzid_cb:
1969  * @tzid: ID of the timezone to resolve.
1970  * @data: Closure data for the callback, in this case #ECalClient.
1971  *
1972  * Resolves TZIDs for the recurrence generator.
1973  *
1974  * Returns: The timezone identified by the @tzid argument, or %NULL if
1975  * it could not be found.
1976  *
1977  * Since: 3.2
1978  */
1979 icaltimezone *
1980 e_cal_client_resolve_tzid_cb (const gchar *tzid,
1981                               gpointer data)
1982 {
1983         ECalClient *client = data;
1984         icaltimezone *zone = NULL;
1985         GError *error = NULL;
1986
1987         g_return_val_if_fail (E_IS_CAL_CLIENT (client), NULL);
1988
1989         e_cal_client_get_timezone_sync (client, tzid, &zone, NULL, &error);
1990
1991         if (error != NULL) {
1992                 g_debug (
1993                         "%s: Failed to find '%s' timezone: %s",
1994                         G_STRFUNC, tzid, error->message);
1995                 g_error_free (error);
1996         }
1997
1998         return zone;
1999 }
2000
2001 struct comp_instance {
2002         ECalComponent *comp;
2003         time_t start;
2004         time_t end;
2005 };
2006
2007 struct instances_info {
2008         GSList **instances;
2009         icaltimezone *start_zone;
2010         icaltimezone *end_zone;
2011 };
2012
2013 /* Called from cal_recur_generate_instances(); adds an instance to the list */
2014 static gboolean
2015 add_instance (ECalComponent *comp,
2016               time_t start,
2017               time_t end,
2018               gpointer data)
2019 {
2020         GSList **list;
2021         struct comp_instance *ci;
2022         icalcomponent *icalcomp;
2023         struct instances_info *instances_hold;
2024
2025         instances_hold = data;
2026         list = instances_hold->instances;
2027
2028         ci = g_new (struct comp_instance, 1);
2029
2030         icalcomp = icalcomponent_new_clone (
2031                 e_cal_component_get_icalcomponent (comp));
2032
2033         /* add the instance to the list */
2034         ci->comp = e_cal_component_new ();
2035         e_cal_component_set_icalcomponent (ci->comp, icalcomp);
2036
2037         /* make sure we return an instance */
2038         if (e_cal_util_component_has_recurrences (icalcomp) &&
2039             !(icalcomponent_get_first_property (icalcomp, ICAL_RECURRENCEID_PROPERTY))) {
2040                 ECalComponentRange *range;
2041                 struct icaltimetype itt;
2042                 ECalComponentDateTime dtstart, dtend;
2043
2044                 /* update DTSTART */
2045                 dtstart.value = NULL;
2046                 dtstart.tzid = NULL;
2047
2048                 e_cal_component_get_dtstart (comp, &dtstart);
2049
2050                 if (instances_hold->start_zone) {
2051                         itt = icaltime_from_timet_with_zone (
2052                                 start, dtstart.value && dtstart.value->is_date,
2053                                 instances_hold->start_zone);
2054                         g_free ((gchar *) dtstart.tzid);
2055                         dtstart.tzid = g_strdup (icaltimezone_get_tzid (
2056                                 instances_hold->start_zone));
2057                 } else {
2058                         itt = icaltime_from_timet (
2059                                 start, dtstart.value && dtstart.value->is_date);
2060                         if (dtstart.tzid) {
2061                                 g_free ((gchar *) dtstart.tzid);
2062                                 dtstart.tzid = NULL;
2063                         }
2064                 }
2065
2066                 g_free (dtstart.value);
2067                 dtstart.value = &itt;
2068                 e_cal_component_set_dtstart (ci->comp, &dtstart);
2069
2070                 /* set the RECUR-ID for the instance */
2071                 range = g_new0 (ECalComponentRange, 1);
2072                 range->type = E_CAL_COMPONENT_RANGE_SINGLE;
2073                 range->datetime = dtstart;
2074
2075                 e_cal_component_set_recurid (ci->comp, range);
2076
2077                 g_free (range);
2078                 g_free ((gchar *) dtstart.tzid);
2079
2080                 /* Update DTEND */
2081                 dtend.value = NULL;
2082                 dtend.tzid = NULL;
2083
2084                 e_cal_component_get_dtend (comp, &dtend);
2085
2086                 if (instances_hold->end_zone) {
2087                         itt = icaltime_from_timet_with_zone (
2088                                 end, dtend.value && dtend.value->is_date,
2089                                 instances_hold->end_zone);
2090                         g_free ((gchar *) dtend.tzid);
2091                         dtend.tzid = g_strdup (icaltimezone_get_tzid (
2092                                 instances_hold->end_zone));
2093                 } else {
2094                         itt = icaltime_from_timet (
2095                                 end, dtend.value && dtend.value->is_date);
2096                         if (dtend.tzid) {
2097                                 g_free ((gchar *) dtend.tzid);
2098                                 dtend.tzid = NULL;
2099                         }
2100                 }
2101
2102                 g_free (dtend.value);
2103                 dtend.value = &itt;
2104                 e_cal_component_set_dtend (ci->comp, &dtend);
2105
2106                 g_free ((gchar *) dtend.tzid);
2107         }
2108
2109         ci->start = start;
2110         ci->end = end;
2111
2112         *list = g_slist_prepend (*list, ci);
2113
2114         return TRUE;
2115 }
2116
2117 /* Used from g_slist_sort(); compares two struct comp_instance structures */
2118 static gint
2119 compare_comp_instance (gconstpointer a,
2120                        gconstpointer b)
2121 {
2122         const struct comp_instance *cia, *cib;
2123         time_t diff;
2124
2125         cia = a;
2126         cib = b;
2127
2128         diff = cia->start - cib->start;
2129         return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
2130 }
2131
2132 static GSList *
2133 process_detached_instances (GSList *instances,
2134                             GSList *detached_instances)
2135 {
2136         struct comp_instance *ci, *cid;
2137         GSList *dl, *unprocessed_instances = NULL;
2138
2139         for (dl = detached_instances; dl != NULL; dl = dl->next) {
2140                 GSList *il;
2141                 const gchar *uid;
2142                 gboolean processed;
2143                 ECalComponentRange recur_id, instance_recur_id;
2144
2145                 processed = FALSE;
2146                 recur_id.type = E_CAL_COMPONENT_RANGE_SINGLE;
2147                 instance_recur_id.type = E_CAL_COMPONENT_RANGE_SINGLE;
2148
2149                 cid = dl->data;
2150                 e_cal_component_get_uid (cid->comp, &uid);
2151                 e_cal_component_get_recurid (cid->comp, &recur_id);
2152
2153                 /* search for coincident instances already expanded */
2154                 for (il = instances; il != NULL; il = il->next) {
2155                         const gchar *instance_uid;
2156                         gint cmp;
2157
2158                         ci = il->data;
2159                         e_cal_component_get_uid (ci->comp, &instance_uid);
2160                         e_cal_component_get_recurid (ci->comp, &instance_recur_id);
2161                         if (strcmp (uid, instance_uid) == 0) {
2162                                 gchar *i_rid = NULL, *d_rid = NULL;
2163
2164                                 i_rid = e_cal_component_get_recurid_as_string (ci->comp);
2165                                 d_rid = e_cal_component_get_recurid_as_string (cid->comp);
2166
2167                                 if (i_rid && d_rid && strcmp (i_rid, d_rid) == 0) {
2168                                         g_object_unref (ci->comp);
2169                                         ci->comp = g_object_ref (cid->comp);
2170                                         ci->start = cid->start;
2171                                         ci->end = cid->end;
2172
2173                                         processed = TRUE;
2174                                 } else {
2175                                         if (!instance_recur_id.datetime.value ||
2176                                             !recur_id.datetime.value) {
2177                                                 /*
2178                                                  * Prevent obvious segfault by ignoring missing
2179                                                  * recurrency ids. Real problem might be elsewhere,
2180                                                  * but anything is better than crashing...
2181                                                  */
2182                                                 g_log (
2183                                                         G_LOG_DOMAIN,
2184                                                         G_LOG_LEVEL_CRITICAL,
2185                                                         "UID %s: instance RECURRENCE-ID %s + detached instance RECURRENCE-ID %s: cannot compare",
2186                                                         uid,
2187                                                         i_rid,
2188                                                         d_rid);
2189
2190                                                 e_cal_component_free_datetime (&instance_recur_id.datetime);
2191                                                 g_free (i_rid);
2192                                                 g_free (d_rid);
2193                                                 continue;
2194                                         }
2195                                         cmp = icaltime_compare (
2196                                                 *instance_recur_id.datetime.value,
2197                                                 *recur_id.datetime.value);
2198                                         if ((recur_id.type == E_CAL_COMPONENT_RANGE_THISPRIOR && cmp <= 0) ||
2199                                                 (recur_id.type == E_CAL_COMPONENT_RANGE_THISFUTURE && cmp >= 0)) {
2200                                                 ECalComponent *comp;
2201
2202                                                 comp = e_cal_component_new ();
2203                                                 e_cal_component_set_icalcomponent (
2204                                                         comp,
2205                                                         icalcomponent_new_clone (e_cal_component_get_icalcomponent (cid->comp)));
2206                                                 e_cal_component_set_recurid (comp, &instance_recur_id);
2207
2208                                                 /* replace the generated instances */
2209                                                 g_object_unref (ci->comp);
2210                                                 ci->comp = comp;
2211                                         }
2212                                 }
2213                                 g_free (i_rid);
2214                                 g_free (d_rid);
2215                         }
2216                         e_cal_component_free_datetime (&instance_recur_id.datetime);
2217                 }
2218
2219                 e_cal_component_free_datetime (&recur_id.datetime);
2220
2221                 if (!processed)
2222                         unprocessed_instances = g_slist_prepend (unprocessed_instances, cid);
2223         }
2224
2225         /* add the unprocessed instances
2226          * (ie, detached instances with no master object) */
2227         while (unprocessed_instances != NULL) {
2228                 cid = unprocessed_instances->data;
2229                 ci = g_new0 (struct comp_instance, 1);
2230                 ci->comp = g_object_ref (cid->comp);
2231                 ci->start = cid->start;
2232                 ci->end = cid->end;
2233                 instances = g_slist_append (instances, ci);
2234
2235                 unprocessed_instances = g_slist_remove (unprocessed_instances, cid);
2236         }
2237
2238         return instances;
2239 }
2240
2241 static void
2242 generate_instances (ECalClient *client,
2243                     time_t start,
2244                     time_t end,
2245                     GSList *objects,
2246                     GCancellable *cancellable,
2247                     ECalRecurInstanceFn cb,
2248                     gpointer cb_data)
2249 {
2250         GSList *instances, *detached_instances = NULL;
2251         GSList *l;
2252         ECalClientPrivate *priv;
2253
2254         priv = client->priv;
2255
2256         instances = NULL;
2257
2258         for (l = objects; l && !g_cancellable_is_cancelled (cancellable); l = l->next) {
2259                 ECalComponent *comp;
2260                 icaltimezone *default_zone;
2261
2262                 if (priv->default_zone)
2263                         default_zone = priv->default_zone;
2264                 else
2265                         default_zone = icaltimezone_get_utc_timezone ();
2266
2267                 comp = l->data;
2268                 if (e_cal_component_is_instance (comp)) {
2269                         struct comp_instance *ci;
2270                         ECalComponentDateTime dtstart, dtend;
2271                         icaltimezone *start_zone = NULL, *end_zone = NULL;
2272
2273                         /* keep the detached instances apart */
2274                         ci = g_new0 (struct comp_instance, 1);
2275                         ci->comp = g_object_ref (comp);
2276
2277                         e_cal_component_get_dtstart (comp, &dtstart);
2278                         e_cal_component_get_dtend (comp, &dtend);
2279
2280                         /* For DATE-TIME values with a TZID, we use
2281                          * e_cal_resolve_tzid_cb to resolve the TZID.
2282                          * For DATE values and DATE-TIME values without a
2283                          * TZID (i.e. floating times) we use the default
2284                          * timezone. */
2285                         if (dtstart.tzid && dtstart.value && !dtstart.value->is_date) {
2286                                 start_zone = e_cal_client_resolve_tzid_cb (
2287                                         dtstart.tzid, client);
2288                                 if (!start_zone)
2289                                         start_zone = default_zone;
2290                         } else {
2291                                 start_zone = default_zone;
2292                         }
2293
2294                         if (dtend.tzid && dtend.value && !dtend.value->is_date) {
2295                                 end_zone = e_cal_client_resolve_tzid_cb (
2296                                         dtend.tzid, client);
2297                                 if (!end_zone)
2298                                         end_zone = default_zone;
2299                         } else {
2300                                 end_zone = default_zone;
2301                         }
2302
2303                         ci->start = icaltime_as_timet_with_zone (
2304                                 *dtstart.value, start_zone);
2305
2306                         if (dtend.value)
2307                                 ci->end = icaltime_as_timet_with_zone (
2308                                         *dtend.value, end_zone);
2309                         else if (icaltime_is_date (*dtstart.value))
2310                                 ci->end = time_day_end (ci->start);
2311                         else
2312                                 ci->end = ci->start;
2313
2314                         e_cal_component_free_datetime (&dtstart);
2315                         e_cal_component_free_datetime (&dtend);
2316
2317                         if (ci->start <= end && ci->end >= start) {
2318                                 detached_instances = g_slist_prepend (
2319                                         detached_instances, ci);
2320                         } else {
2321                                 /* it doesn't fit to our time range, thus skip it */
2322                                 g_object_unref (G_OBJECT (ci->comp));
2323                                 g_free (ci);
2324                         }
2325                 } else {
2326                         ECalComponentDateTime datetime;
2327                         icaltimezone *start_zone = NULL, *end_zone = NULL;
2328                         struct instances_info *instances_hold;
2329
2330                         /* Get the start timezone */
2331                         e_cal_component_get_dtstart (comp, &datetime);
2332                         if (datetime.tzid)
2333                                 e_cal_client_get_timezone_sync (
2334                                         client, datetime.tzid,
2335                                         &start_zone, cancellable, NULL);
2336                         else
2337                                 start_zone = NULL;
2338                         e_cal_component_free_datetime (&datetime);
2339
2340                         /* Get the end timezone */
2341                         e_cal_component_get_dtend (comp, &datetime);
2342                         if (datetime.tzid)
2343                                 e_cal_client_get_timezone_sync (
2344                                         client, datetime.tzid,
2345                                         &end_zone, cancellable, NULL);
2346                         else
2347                                 end_zone = NULL;
2348                         e_cal_component_free_datetime (&datetime);
2349
2350                         instances_hold = g_new0 (struct instances_info, 1);
2351                         instances_hold->instances = &instances;
2352                         instances_hold->start_zone = start_zone;
2353                         instances_hold->end_zone = end_zone;
2354
2355                         e_cal_recur_generate_instances (
2356                                 comp, start, end, add_instance, instances_hold,
2357                                 e_cal_client_resolve_tzid_cb, client,
2358                                 default_zone);
2359
2360                         g_free (instances_hold);
2361                 }
2362         }
2363
2364         g_slist_foreach (objects, (GFunc) g_object_unref, NULL);
2365         g_slist_free (objects);
2366
2367         /* Generate instances and spew them out */
2368
2369         if (!g_cancellable_is_cancelled (cancellable)) {
2370                 instances = g_slist_sort (instances, compare_comp_instance);
2371                 instances = process_detached_instances (instances, detached_instances);
2372         }
2373
2374         for (l = instances; l && !g_cancellable_is_cancelled (cancellable); l = l->next) {
2375                 struct comp_instance *ci;
2376                 gboolean result;
2377
2378                 ci = l->data;
2379
2380                 result = (* cb) (ci->comp, ci->start, ci->end, cb_data);
2381
2382                 if (!result)
2383                         break;
2384         }
2385
2386         /* Clean up */
2387
2388         for (l = instances; l; l = l->next) {
2389                 struct comp_instance *ci;
2390
2391                 ci = l->data;
2392                 g_object_unref (G_OBJECT (ci->comp));
2393                 g_free (ci);
2394         }
2395
2396         g_slist_free (instances);
2397
2398         for (l = detached_instances; l; l = l->next) {
2399                 struct comp_instance *ci;
2400
2401                 ci = l->data;
2402                 g_object_unref (G_OBJECT (ci->comp));
2403                 g_free (ci);
2404         }
2405
2406         g_slist_free (detached_instances);
2407 }
2408
2409 static GSList *
2410 get_objects_sync (ECalClient *client,
2411                   time_t start,
2412                   time_t end,
2413                   const gchar *uid)
2414 {
2415         GSList *objects = NULL;
2416
2417         /* Generate objects */
2418         if (uid && *uid) {
2419                 GError *error = NULL;
2420
2421                 e_cal_client_get_objects_for_uid_sync (
2422                         client, uid, &objects, NULL, &error);
2423
2424                 if (error != NULL) {
2425                         g_warning (
2426                                 "Failed to get recurrence objects "
2427                                 "for uid: %s\n", error->message);
2428                         g_clear_error (&error);
2429                         return NULL;
2430                 }
2431         } else {
2432                 gchar *iso_start, *iso_end;
2433                 gchar *query;
2434
2435                 iso_start = isodate_from_time_t (start);
2436                 if (!iso_start)
2437                         return NULL;
2438
2439                 iso_end = isodate_from_time_t (end);
2440                 if (!iso_end) {
2441                         g_free (iso_start);
2442                         return NULL;
2443                 }
2444
2445                 query = g_strdup_printf (
2446                         "(occur-in-time-range? "
2447                         "(make-time \"%s\") (make-time \"%s\"))",
2448                         iso_start, iso_end);
2449                 g_free (iso_start);
2450                 g_free (iso_end);
2451                 if (!e_cal_client_get_object_list_as_comps_sync (
2452                         client, query, &objects, NULL, NULL)) {
2453                         g_free (query);
2454                         return NULL;
2455                 }
2456                 g_free (query);
2457         }
2458
2459         return objects;
2460 }
2461
2462 struct get_objects_async_data {
2463         GCancellable *cancellable;
2464         ECalClient *client;
2465         time_t start;
2466         time_t end;
2467         ECalRecurInstanceFn cb;
2468         gpointer cb_data;
2469         GDestroyNotify destroy_cb_data;
2470         gchar *uid;
2471         gchar *query;
2472         guint tries;
2473         void (* ready_cb) (struct get_objects_async_data *goad, GSList *objects);
2474         icaltimezone *start_zone;
2475         icaltimezone *end_zone;
2476         ECalComponent *comp;
2477 };
2478
2479 static void
2480 free_get_objects_async_data (struct get_objects_async_data *goad)
2481 {
2482         if (!goad)
2483                 return;
2484
2485         if (goad->cancellable)
2486                 g_object_unref (goad->cancellable);
2487         if (goad->destroy_cb_data)
2488                 goad->destroy_cb_data (goad->cb_data);
2489         if (goad->client)
2490                 g_object_unref (goad->client);
2491         if (goad->comp)
2492                 g_object_unref (goad->comp);
2493         g_free (goad->query);
2494         g_free (goad->uid);
2495         g_free (goad);
2496 }
2497
2498 static void
2499 got_objects_for_uid_cb (GObject *source_object,
2500                         GAsyncResult *result,
2501                         gpointer user_data)
2502 {
2503         struct get_objects_async_data *goad = user_data;
2504         GSList *objects = NULL;
2505         GError *error = NULL;
2506
2507         g_return_if_fail (source_object != NULL);
2508         g_return_if_fail (result != NULL);
2509         g_return_if_fail (goad != NULL);
2510         g_return_if_fail (goad->client == E_CAL_CLIENT (source_object));
2511
2512         e_cal_client_get_objects_for_uid_finish (
2513                 goad->client, result, &objects, &error);
2514
2515         if (error != NULL) {
2516                 if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) ||
2517                     g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
2518                         free_get_objects_async_data (goad);
2519                         g_clear_error (&error);
2520                         return;
2521                 }
2522
2523                 g_clear_error (&error);
2524                 objects = NULL;
2525         }
2526
2527         g_return_if_fail (goad->ready_cb != NULL);
2528
2529         /* takes care of the objects and goad */
2530         goad->ready_cb (goad, objects);
2531 }
2532
2533 static void
2534 got_object_list_as_comps_cb (GObject *source_object,
2535                              GAsyncResult *result,
2536                              gpointer user_data)
2537 {
2538         struct get_objects_async_data *goad = user_data;
2539         GSList *objects = NULL;
2540         GError *error = NULL;
2541
2542         g_return_if_fail (source_object != NULL);
2543         g_return_if_fail (result != NULL);
2544         g_return_if_fail (goad != NULL);
2545         g_return_if_fail (goad->client == E_CAL_CLIENT (source_object));
2546
2547         e_cal_client_get_object_list_as_comps_finish (
2548                 goad->client, result, &objects, &error);
2549
2550         if (error != NULL) {
2551                 if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) ||
2552                     g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
2553                         free_get_objects_async_data (goad);
2554                         g_clear_error (&error);
2555                         return;
2556                 }
2557
2558                 g_clear_error (&error);
2559                 objects = NULL;
2560         }
2561
2562         g_return_if_fail (goad->ready_cb != NULL);
2563
2564         /* takes care of the objects and goad */
2565         goad->ready_cb (goad, objects);
2566 }
2567
2568 /* ready_cb may take care of both arguments, goad and objects;
2569  * objects can be also NULL */
2570 static void
2571 get_objects_async (void (*ready_cb) (struct get_objects_async_data *goad,
2572                                      GSList *objects),
2573                    struct get_objects_async_data *goad)
2574 {
2575         g_return_if_fail (ready_cb != NULL);
2576         g_return_if_fail (goad != NULL);
2577
2578         goad->ready_cb = ready_cb;
2579
2580         if (goad->uid && *goad->uid) {
2581                 e_cal_client_get_objects_for_uid (
2582                         goad->client, goad->uid, goad->cancellable,
2583                         got_objects_for_uid_cb, goad);
2584         } else {
2585                 gchar *iso_start, *iso_end;
2586
2587                 iso_start = isodate_from_time_t (goad->start);
2588                 if (!iso_start) {
2589                         free_get_objects_async_data (goad);
2590                         return;
2591                 }
2592
2593                 iso_end = isodate_from_time_t (goad->end);
2594                 if (!iso_end) {
2595                         g_free (iso_start);
2596                         free_get_objects_async_data (goad);
2597                         return;
2598                 }
2599
2600                 goad->query = g_strdup_printf (
2601                         "(occur-in-time-range? "
2602                         "(make-time \"%s\") (make-time \"%s\"))",
2603                         iso_start, iso_end);
2604
2605                 g_free (iso_start);
2606                 g_free (iso_end);
2607
2608                 e_cal_client_get_object_list_as_comps (
2609                         goad->client, goad->query, goad->cancellable,
2610                         got_object_list_as_comps_cb, goad);
2611         }
2612 }
2613
2614 static void
2615 generate_instances_got_objects_cb (struct get_objects_async_data *goad,
2616                                    GSList *objects)
2617 {
2618         g_return_if_fail (goad != NULL);
2619
2620         /* generate_instaces () frees 'objects' slist */
2621         if (objects)
2622                 generate_instances (
2623                         goad->client, goad->start, goad->end, objects,
2624                         goad->cancellable, goad->cb, goad->cb_data);
2625
2626         free_get_objects_async_data (goad);
2627 }
2628
2629 /**
2630  * e_cal_client_generate_instances:
2631  * @client: A calendar client.
2632  * @start: Start time for query.
2633  * @end: End time for query.
2634  * @cancellable: a #GCancellable; can be %NULL
2635  * @cb: Callback for each generated instance.
2636  * @cb_data: Closure data for the callback.
2637  * @destroy_cb_data: Function to call when the processing is done, to free
2638  *                   @cb_data; can be %NULL.
2639  *
2640  * Does a combination of e_cal_client_get_object_list() and
2641  * e_cal_client_recur_generate_instances(). Unlike
2642  * e_cal_client_generate_instances_sync(), this returns immediately and the
2643  * @cb callback is called asynchronously.
2644  *
2645  * The callback function should do a g_object_ref() of the calendar component
2646  * it gets passed if it intends to keep it around, since it will be unref'ed
2647  * as soon as the callback returns.
2648  *
2649  * Since: 3.2
2650  **/
2651 void
2652 e_cal_client_generate_instances (ECalClient *client,
2653                                  time_t start,
2654                                  time_t end,
2655                                  GCancellable *cancellable,
2656                                  ECalRecurInstanceFn cb,
2657                                  gpointer cb_data,
2658                                  GDestroyNotify destroy_cb_data)
2659 {
2660         struct get_objects_async_data *goad;
2661         GCancellable *use_cancellable;
2662
2663         g_return_if_fail (E_IS_CAL_CLIENT (client));
2664
2665         g_return_if_fail (start >= 0);
2666         g_return_if_fail (end >= 0);
2667         g_return_if_fail (cb != NULL);
2668
2669         use_cancellable = cancellable;
2670         if (!use_cancellable)
2671                 use_cancellable = g_cancellable_new ();
2672
2673         goad = g_new0 (struct get_objects_async_data, 1);
2674         goad->cancellable = g_object_ref (use_cancellable);
2675         goad->client = g_object_ref (client);
2676         goad->start = start;
2677         goad->end = end;
2678         goad->cb = cb;
2679         goad->cb_data = cb_data;
2680         goad->destroy_cb_data = destroy_cb_data;
2681
2682         get_objects_async (generate_instances_got_objects_cb, goad);
2683
2684         if (use_cancellable != cancellable)
2685                 g_object_unref (use_cancellable);
2686 }
2687
2688 /**
2689  * e_cal_client_generate_instances_sync:
2690  * @client: A calendar client
2691  * @start: Start time for query
2692  * @end: End time for query
2693  * @cb: (closure cb_data) (scope call): Callback for each generated instance
2694  * @cb_data: (closure): Closure data for the callback
2695  *
2696  * Does a combination of e_cal_client_get_object_list() and
2697  * e_cal_client_recur_generate_instances().
2698  *
2699  * The callback function should do a g_object_ref() of the calendar component
2700  * it gets passed if it intends to keep it around, since it will be unreffed
2701  * as soon as the callback returns.
2702  *
2703  * Since: 3.2
2704  **/
2705 void
2706 e_cal_client_generate_instances_sync (ECalClient *client,
2707                                       time_t start,
2708                                       time_t end,
2709                                       ECalRecurInstanceFn cb,
2710                                       gpointer cb_data)
2711 {
2712         GSList *objects = NULL;
2713
2714         g_return_if_fail (E_IS_CAL_CLIENT (client));
2715
2716         g_return_if_fail (start >= 0);
2717         g_return_if_fail (end >= 0);
2718         g_return_if_fail (cb != NULL);
2719
2720         objects = get_objects_sync (client, start, end, NULL);
2721         if (!objects)
2722                 return;
2723
2724         /* generate_instaces frees 'objects' slist */
2725         generate_instances (client, start, end, objects, NULL, cb, cb_data);
2726 }
2727
2728 /* also frees 'instances' GSList */
2729 static void
2730 process_instances (ECalComponent *comp,
2731                    GSList *instances,
2732                    ECalRecurInstanceFn cb,
2733                    gpointer cb_data)
2734 {
2735         gchar *rid;
2736         gboolean result;
2737
2738         g_return_if_fail (comp != NULL);
2739         g_return_if_fail (cb != NULL);
2740
2741         rid = e_cal_component_get_recurid_as_string (comp);
2742
2743         /* Reverse the instances list because the add_instance() function
2744          * is prepending. */
2745         instances = g_slist_reverse (instances);
2746
2747         /* now only return back the instances for the given object */
2748         result = TRUE;
2749         while (instances != NULL) {
2750                 struct comp_instance *ci;
2751                 gchar *instance_rid = NULL;
2752
2753                 ci = instances->data;
2754
2755                 if (result) {
2756                         instance_rid = e_cal_component_get_recurid_as_string (ci->comp);
2757
2758                         if (rid && *rid) {
2759                                 if (instance_rid && *instance_rid && strcmp (rid, instance_rid) == 0)
2760                                         result = (* cb) (ci->comp, ci->start, ci->end, cb_data);
2761                         } else
2762                                 result = (* cb) (ci->comp, ci->start, ci->end, cb_data);
2763                 }
2764
2765                 /* remove instance from list */
2766                 instances = g_slist_remove (instances, ci);
2767                 g_object_unref (ci->comp);
2768                 g_free (ci);
2769                 g_free (instance_rid);
2770         }
2771
2772         /* clean up */
2773         g_free (rid);
2774 }
2775
2776 static void
2777 generate_instances_for_object_got_objects_cb (struct get_objects_async_data *goad,
2778                                               GSList *objects)
2779 {
2780         struct instances_info *instances_hold;
2781         GSList *instances = NULL;
2782
2783         g_return_if_fail (goad != NULL);
2784
2785         instances_hold = g_new0 (struct instances_info, 1);
2786         instances_hold->instances = &instances;
2787         instances_hold->start_zone = goad->start_zone;
2788         instances_hold->end_zone = goad->end_zone;
2789
2790         /* generate all instances in the given time range */
2791         generate_instances (
2792                 goad->client, goad->start, goad->end, objects,
2793                 goad->cancellable, add_instance, instances_hold);
2794
2795         /* it also frees 'instances' GSList */
2796         process_instances (
2797                 goad->comp, *(instances_hold->instances),
2798                 goad->cb, goad->cb_data);
2799
2800         /* clean up */
2801         free_get_objects_async_data (goad);
2802         g_free (instances_hold);
2803 }
2804
2805 /**
2806  * e_cal_client_generate_instances_for_object:
2807  * @client: A calendar client.
2808  * @icalcomp: Object to generate instances from.
2809  * @start: Start time for query.
2810  * @end: End time for query.
2811  * @cancellable: a #GCancellable; can be %NULL
2812  * @cb: Callback for each generated instance.
2813  * @cb_data: Closure data for the callback.
2814  * @destroy_cb_data: Function to call when the processing is done, to
2815  *                   free @cb_data; can be %NULL.
2816  *
2817  * Does a combination of e_cal_client_get_object_list() and
2818  * e_cal_client_recur_generate_instances(), like
2819  * e_cal_client_generate_instances(), but for a single object. Unlike
2820  * e_cal_client_generate_instances_for_object_sync(), this returns immediately
2821  * and the @cb callback is called asynchronously.
2822  *
2823  * The callback function should do a g_object_ref() of the calendar component
2824  * it gets passed if it intends to keep it around, since it will be unref'ed
2825  * as soon as the callback returns.
2826  *
2827  * Since: 3.2
2828  **/
2829 void
2830 e_cal_client_generate_instances_for_object (ECalClient *client,
2831                                             icalcomponent *icalcomp,
2832                                             time_t start,
2833                                             time_t end,
2834                                             GCancellable *cancellable,
2835                                             ECalRecurInstanceFn cb,
2836                                             gpointer cb_data,
2837                                             GDestroyNotify destroy_cb_data)
2838 {
2839         ECalComponent *comp;
2840         const gchar *uid;
2841         ECalComponentDateTime datetime;
2842         icaltimezone *start_zone = NULL, *end_zone = NULL;
2843         gboolean is_single_instance = FALSE;
2844         struct get_objects_async_data *goad;
2845         GCancellable *use_cancellable;
2846
2847         g_return_if_fail (E_IS_CAL_CLIENT (client));
2848
2849         g_return_if_fail (start >= 0);
2850         g_return_if_fail (end >= 0);
2851         g_return_if_fail (cb != NULL);
2852
2853         comp = e_cal_component_new ();
2854         e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (icalcomp));
2855
2856         if (!e_cal_component_has_recurrences (comp))
2857                 is_single_instance = TRUE;
2858
2859         /* If the backend stores it as individual instances and does not
2860          * have a master object - do not expand */
2861         if (is_single_instance || e_client_check_capability (E_CLIENT (client), CAL_STATIC_CAPABILITY_RECURRENCES_NO_MASTER)) {
2862                 /* return the same instance */
2863                 (* cb)  (comp,
2864                         icaltime_as_timet_with_zone (
2865                                 icalcomponent_get_dtstart (icalcomp),
2866                                 client->priv->default_zone),
2867                         icaltime_as_timet_with_zone (
2868                                 icalcomponent_get_dtend (icalcomp),
2869                                 client->priv->default_zone),
2870                         cb_data);
2871                 g_object_unref (comp);
2872
2873                 if (destroy_cb_data)
2874                         destroy_cb_data (cb_data);
2875                 return;
2876         }
2877
2878         e_cal_component_get_uid (comp, &uid);
2879
2880         /* Get the start timezone */
2881         e_cal_component_get_dtstart (comp, &datetime);
2882         if (datetime.tzid)
2883                 e_cal_client_get_timezone_sync (
2884                         client, datetime.tzid, &start_zone, NULL, NULL);
2885         else
2886                 start_zone = NULL;
2887         e_cal_component_free_datetime (&datetime);
2888
2889         /* Get the end timezone */
2890         e_cal_component_get_dtend (comp, &datetime);
2891         if (datetime.tzid)
2892                 e_cal_client_get_timezone_sync (
2893                         client, datetime.tzid, &end_zone, NULL, NULL);
2894         else
2895                 end_zone = NULL;
2896         e_cal_component_free_datetime (&datetime);
2897
2898         use_cancellable = cancellable;
2899         if (!use_cancellable)
2900                 use_cancellable = g_cancellable_new ();
2901
2902         goad = g_new0 (struct get_objects_async_data, 1);
2903         goad->cancellable = g_object_ref (use_cancellable);
2904         goad->client = g_object_ref (client);
2905         goad->start = start;
2906         goad->end = end;
2907         goad->cb = cb;
2908         goad->cb_data = cb_data;
2909         goad->destroy_cb_data = destroy_cb_data;
2910         goad->start_zone = start_zone;
2911         goad->end_zone = end_zone;
2912         goad->comp = comp;
2913         goad->uid = g_strdup (uid);
2914
2915         get_objects_async (generate_instances_for_object_got_objects_cb, goad);
2916
2917         if (use_cancellable != cancellable)
2918                 g_object_unref (use_cancellable);
2919 }
2920
2921 /**
2922  * e_cal_client_generate_instances_for_object_sync:
2923  * @client: A calendar client
2924  * @icalcomp: Object to generate instances from
2925  * @start: Start time for query
2926  * @end: End time for query
2927  * @cb: (closure cb_data) (scope call): Callback for each generated instance
2928  * @cb_data: (closure): Closure data for the callback
2929  *
2930  * Does a combination of e_cal_client_get_object_list() and
2931  * e_cal_client_recur_generate_instances(), like
2932  * e_cal_client_generate_instances_sync(), but for a single object.
2933  *
2934  * The callback function should do a g_object_ref() of the calendar component
2935  * it gets passed if it intends to keep it around, since it will be unref'ed
2936  * as soon as the callback returns.
2937  *
2938  * Since: 3.2
2939  **/
2940 void
2941 e_cal_client_generate_instances_for_object_sync (ECalClient *client,
2942                                                  icalcomponent *icalcomp,
2943                                                  time_t start,
2944                                                  time_t end,
2945                                                  ECalRecurInstanceFn cb,
2946                                                  gpointer cb_data)
2947 {
2948         ECalComponent *comp;
2949         const gchar *uid;
2950         GSList *instances = NULL;
2951         ECalComponentDateTime datetime;
2952         icaltimezone *start_zone = NULL, *end_zone = NULL;
2953         struct instances_info *instances_hold;
2954         gboolean is_single_instance = FALSE;
2955
2956         g_return_if_fail (E_IS_CAL_CLIENT (client));
2957
2958         g_return_if_fail (start >= 0);
2959         g_return_if_fail (end >= 0);
2960         g_return_if_fail (cb != NULL);
2961
2962         comp = e_cal_component_new ();
2963         e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (icalcomp));
2964
2965         if (!e_cal_component_has_recurrences (comp))
2966                 is_single_instance = TRUE;
2967
2968         /* If the backend stores it as individual instances and does not
2969          * have a master object - do not expand */
2970         if (is_single_instance || e_client_check_capability (E_CLIENT (client), CAL_STATIC_CAPABILITY_RECURRENCES_NO_MASTER)) {
2971                 /* return the same instance */
2972                 (* cb)  (comp,
2973                         icaltime_as_timet_with_zone (
2974                                 icalcomponent_get_dtstart (icalcomp),
2975                                 client->priv->default_zone),
2976                         icaltime_as_timet_with_zone (
2977                                 icalcomponent_get_dtend (icalcomp),
2978                                 client->priv->default_zone),
2979                         cb_data);
2980                 g_object_unref (comp);
2981                 return;
2982         }
2983
2984         e_cal_component_get_uid (comp, &uid);
2985
2986         /* Get the start timezone */
2987         e_cal_component_get_dtstart (comp, &datetime);
2988         if (datetime.tzid)
2989                 e_cal_client_get_timezone_sync (
2990                         client, datetime.tzid, &start_zone, NULL, NULL);
2991         else
2992                 start_zone = NULL;
2993         e_cal_component_free_datetime (&datetime);
2994
2995         /* Get the end timezone */
2996         e_cal_component_get_dtend (comp, &datetime);
2997         if (datetime.tzid)
2998                 e_cal_client_get_timezone_sync (
2999                         client, datetime.tzid, &end_zone, NULL, NULL);
3000         else
3001                 end_zone = NULL;
3002         e_cal_component_free_datetime (&datetime);
3003
3004         instances_hold = g_new0 (struct instances_info, 1);
3005         instances_hold->instances = &instances;
3006         instances_hold->start_zone = start_zone;
3007         instances_hold->end_zone = end_zone;
3008
3009         /* generate all instances in the given time range */
3010         generate_instances (
3011                 client, start, end,
3012                 get_objects_sync (client, start, end, uid),
3013                 NULL, add_instance, instances_hold);
3014
3015         /* it also frees 'instances' GSList */
3016         process_instances (comp, *(instances_hold->instances), cb, cb_data);
3017
3018         /* clean up */
3019         g_object_unref (comp);
3020         g_free (instances_hold);
3021 }
3022
3023 typedef struct _ForeachTZIDCallbackData ForeachTZIDCallbackData;
3024 struct _ForeachTZIDCallbackData {
3025         ECalClient *client;
3026         GHashTable *timezone_hash;
3027         gboolean success;
3028 };
3029
3030 /* This adds the VTIMEZONE given by the TZID parameter to the GHashTable in
3031  * data. */
3032 static void
3033 foreach_tzid_callback (icalparameter *param,
3034                        gpointer cbdata)
3035 {
3036         ForeachTZIDCallbackData *data = cbdata;
3037         const gchar *tzid;
3038         icaltimezone *zone = NULL;
3039         icalcomponent *vtimezone_comp;
3040         gchar *vtimezone_as_string;
3041
3042         /* Get the TZID string from the parameter. */
3043         tzid = icalparameter_get_tzid (param);
3044         if (!tzid)
3045                 return;
3046
3047         /* Check if we've already added it to the GHashTable. */
3048         if (g_hash_table_lookup (data->timezone_hash, tzid))
3049                 return;
3050
3051         if (!e_cal_client_get_timezone_sync (data->client, tzid, &zone, NULL, NULL) || !zone) {
3052                 data->success = FALSE;
3053                 return;
3054         }
3055
3056         /* Convert it to a string and add it to the hash. */
3057         vtimezone_comp = icaltimezone_get_component (zone);
3058         if (!vtimezone_comp)
3059                 return;
3060
3061         vtimezone_as_string = icalcomponent_as_ical_string_r (vtimezone_comp);
3062
3063         g_hash_table_insert (data->timezone_hash, (gchar *) tzid, vtimezone_as_string);
3064 }
3065
3066 /* This appends the value string to the GString given in data. */
3067 static void
3068 append_timezone_string (gpointer key,
3069                         gpointer value,
3070                         gpointer data)
3071 {
3072         GString *vcal_string = data;
3073
3074         g_string_append (vcal_string, value);
3075         g_free (value);
3076 }
3077
3078 /* This simply frees the hash values. */
3079 static void
3080 free_timezone_string (gpointer key,
3081                       gpointer value,
3082                       gpointer data)
3083 {
3084         g_free (value);
3085 }
3086
3087 /**
3088  * e_cal_client_get_component_as_string:
3089  * @client: A calendar client.
3090  * @icalcomp: A calendar component object.
3091  *
3092  * Gets a calendar component as an iCalendar string, with a toplevel
3093  * VCALENDAR component and all VTIMEZONEs needed for the component.
3094  *
3095  * Returns: the component as a complete iCalendar string, or NULL on
3096  * failure. The string should be freed with g_free().
3097  *
3098  * Since: 3.2
3099  **/
3100 gchar *
3101 e_cal_client_get_component_as_string (ECalClient *client,
3102                                       icalcomponent *icalcomp)
3103 {
3104         GHashTable *timezone_hash;
3105         GString *vcal_string;
3106         ForeachTZIDCallbackData cbdata;
3107         gchar *obj_string;
3108
3109         g_return_val_if_fail (E_IS_CAL_CLIENT (client), NULL);
3110         g_return_val_if_fail (icalcomp != NULL, NULL);
3111
3112         timezone_hash = g_hash_table_new (g_str_hash, g_str_equal);
3113
3114         /* Add any timezones needed to the hash. We use a hash since we only
3115          * want to add each timezone once at most. */
3116         cbdata.client = client;
3117         cbdata.timezone_hash = timezone_hash;
3118         cbdata.success = TRUE;
3119         icalcomponent_foreach_tzid (icalcomp, foreach_tzid_callback, &cbdata);
3120         if (!cbdata.success) {
3121                 g_hash_table_foreach (timezone_hash, free_timezone_string, NULL);
3122                 return NULL;
3123         }
3124
3125         /* Create the start of a VCALENDAR, to add the VTIMEZONES to,
3126          * and remember its length so we know if any VTIMEZONEs get added. */
3127         vcal_string = g_string_new (NULL);
3128         g_string_append (
3129                 vcal_string,
3130                 "BEGIN:VCALENDAR\n"
3131                 "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
3132                 "VERSION:2.0\n"
3133                 "METHOD:PUBLISH\n");
3134
3135         /* Now concatenate all the timezone strings. This also frees the
3136          * timezone strings as it goes. */
3137         g_hash_table_foreach (timezone_hash, append_timezone_string, vcal_string);
3138
3139         /* Get the string for the VEVENT/VTODO. */
3140         obj_string = icalcomponent_as_ical_string_r (icalcomp);
3141
3142         /* If there were any timezones to send, create a complete VCALENDAR,
3143          * else just send the VEVENT/VTODO string. */
3144         g_string_append (vcal_string, obj_string);
3145         g_string_append (vcal_string, "END:VCALENDAR\n");
3146         g_free (obj_string);
3147
3148         obj_string = g_string_free (vcal_string, FALSE);
3149
3150         g_hash_table_destroy (timezone_hash);
3151
3152         return obj_string;
3153 }
3154
3155 /* Helper for e_cal_client_get_default_object() */
3156 static void
3157 cal_client_get_default_object_thread (GSimpleAsyncResult *simple,
3158                                       GObject *source_object,
3159                                       GCancellable *cancellable)
3160 {
3161         AsyncContext *async_context;
3162         GError *error = NULL;
3163
3164         async_context = g_simple_async_result_get_op_res_gpointer (simple);
3165
3166         e_cal_client_get_default_object_sync (
3167                 E_CAL_CLIENT (source_object),
3168                 &async_context->out_comp,
3169                 cancellable, &error);
3170
3171         if (error != NULL)
3172                 g_simple_async_result_take_error (simple, error);
3173 }
3174
3175 /**
3176  * e_cal_client_get_default_object:
3177  * @client: an #ECalClient
3178  * @cancellable: a #GCancellable; can be %NULL
3179  * @callback: callback to call when a result is ready
3180  * @user_data: user data for the @callback
3181  *
3182  * Retrives an #icalcomponent from the backend that contains the default
3183  * values for properties needed. The call is finished
3184  * by e_cal_client_get_default_object_finish() from the @callback.
3185  *
3186  * Since: 3.2
3187  **/
3188 void
3189 e_cal_client_get_default_object (ECalClient *client,
3190                                  GCancellable *cancellable,
3191                                  GAsyncReadyCallback callback,
3192                                  gpointer user_data)
3193 {
3194         GSimpleAsyncResult *simple;
3195         AsyncContext *async_context;
3196
3197         g_return_if_fail (E_IS_CAL_CLIENT (client));
3198
3199         async_context = g_slice_new0 (AsyncContext);
3200
3201         simple = g_simple_async_result_new (
3202                 G_OBJECT (client), callback, user_data,
3203                 e_cal_client_get_default_object);
3204
3205         g_simple_async_result_set_check_cancellable (simple, cancellable);
3206
3207         g_simple_async_result_set_op_res_gpointer (
3208                 simple, async_context, (GDestroyNotify) async_context_free);
3209
3210         g_simple_async_result_run_in_thread (
3211                 simple, cal_client_get_default_object_thread,
3212                 G_PRIORITY_DEFAULT, cancellable);
3213
3214         g_object_unref (simple);
3215 }
3216
3217 /**
3218  * e_cal_client_get_default_object_finish:
3219  * @client: an #ECalClient
3220  * @result: a #GAsyncResult
3221  * @out_icalcomp: (out): Return value for the default calendar object.
3222  * @error: (out): a #GError to set an error, if any
3223  *
3224  * Finishes previous call of e_cal_client_get_default_object() and
3225  * sets @out_icalcomp to an #icalcomponent from the backend that contains
3226  * the default values for properties needed. This @out_icalcomp should be
3227  * freed with icalcomponent_free().
3228  *
3229  * Returns: %TRUE if successful, %FALSE otherwise.
3230  *
3231  * Since: 3.2
3232  **/
3233 gboolean
3234 e_cal_client_get_default_object_finish (ECalClient *client,
3235                                         GAsyncResult *result,
3236                                         icalcomponent **out_icalcomp,
3237                                         GError **error)
3238 {
3239         GSimpleAsyncResult *simple;
3240         AsyncContext *async_context;
3241
3242         g_return_val_if_fail (
3243                 g_simple_async_result_is_valid (
3244                 result, G_OBJECT (client),
3245                 e_cal_client_get_default_object), FALSE);
3246
3247         simple = G_SIMPLE_ASYNC_RESULT (result);
3248         async_context = g_simple_async_result_get_op_res_gpointer (simple);
3249
3250         if (g_simple_async_result_propagate_error (simple, error))
3251                 return FALSE;
3252
3253         g_return_val_if_fail (async_context->out_comp != NULL, FALSE);
3254
3255         if (out_icalcomp != NULL) {
3256                 *out_icalcomp = async_context->out_comp;
3257                 async_context->out_comp = NULL;
3258         }
3259
3260         return TRUE;
3261 }
3262
3263 /**
3264  * e_cal_client_get_default_object_sync:
3265  * @client: an #ECalClient
3266  * @out_icalcomp: (out): Return value for the default calendar object.
3267  * @cancellable: a #GCancellable; can be %NULL
3268  * @error: (out): a #GError to set an error, if any
3269  *
3270  * Retrives an #icalcomponent from the backend that contains the default
3271  * values for properties needed. This @out_icalcomp should be freed with
3272  * icalcomponent_free().
3273  *
3274  * Returns: %TRUE if successful, %FALSE otherwise.
3275  *
3276  * Since: 3.2
3277  **/
3278 gboolean
3279 e_cal_client_get_default_object_sync (ECalClient *client,
3280                                       icalcomponent **out_icalcomp,
3281                                       GCancellable *cancellable,
3282                                       GError **error)
3283 {
3284         icalcomponent *icalcomp = NULL;
3285         gchar *string;
3286
3287         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
3288         g_return_val_if_fail (out_icalcomp != NULL, FALSE);
3289
3290         string = e_dbus_calendar_dup_default_object (client->priv->dbus_proxy);
3291         if (string != NULL) {
3292                 icalcomp = icalparser_parse_string (string);
3293                 g_free (string);
3294         }
3295
3296         if (icalcomp == NULL) {
3297                 g_set_error_literal (
3298                         error, E_CAL_CLIENT_ERROR,
3299                         E_CAL_CLIENT_ERROR_INVALID_OBJECT,
3300                         e_cal_client_error_to_string (
3301                         E_CAL_CLIENT_ERROR_INVALID_OBJECT));
3302                 return FALSE;
3303         }
3304
3305         if (icalcomponent_get_uid (icalcomp) != NULL) {
3306                 gchar *new_uid;
3307
3308                 /* Make sure the UID is always unique. */
3309                 new_uid = e_cal_component_gen_uid ();
3310                 icalcomponent_set_uid (icalcomp, new_uid);
3311                 g_free (new_uid);
3312         }
3313
3314         *out_icalcomp = icalcomp;
3315
3316         return TRUE;
3317 }
3318
3319 /* Helper for e_cal_client_get_object() */
3320 static void
3321 cal_client_get_object_thread (GSimpleAsyncResult *simple,
3322                               GObject *source_object,
3323                               GCancellable *cancellable)
3324 {
3325         AsyncContext *async_context;
3326         GError *error = NULL;
3327
3328         async_context = g_simple_async_result_get_op_res_gpointer (simple);
3329
3330         e_cal_client_get_object_sync (
3331                 E_CAL_CLIENT (source_object),
3332                 async_context->uid,
3333                 async_context->rid,
3334                 &async_context->out_comp,
3335                 cancellable, &error);
3336
3337         if (error != NULL)
3338                 g_simple_async_result_take_error (simple, error);
3339 }
3340
3341 /**
3342  * e_cal_client_get_object:
3343  * @client: an #ECalClient
3344  * @uid: Unique identifier for a calendar component.
3345  * @rid: Recurrence identifier.
3346  * @cancellable: a #GCancellable; can be %NULL
3347  * @callback: callback to call when a result is ready
3348  * @user_data: user data for the @callback
3349  *
3350  * Queries a calendar for a calendar component object based on its unique
3351  * identifier. The call is finished by e_cal_client_get_object_finish()
3352  * from the @callback.
3353  *
3354  * Use e_cal_client_get_objects_for_uid() to get list of all
3355  * objects for the given uid, which includes master object and
3356  * all detached instances.
3357  *
3358  * Since: 3.2
3359  **/
3360 void
3361 e_cal_client_get_object (ECalClient *client,
3362                          const gchar *uid,
3363                          const gchar *rid,
3364                          GCancellable *cancellable,
3365                          GAsyncReadyCallback callback,
3366                          gpointer user_data)
3367 {
3368         GSimpleAsyncResult *simple;
3369         AsyncContext *async_context;
3370
3371         g_return_if_fail (E_IS_CAL_CLIENT (client));
3372         g_return_if_fail (uid != NULL);
3373         /* rid is optional */
3374
3375         async_context = g_slice_new0 (AsyncContext);
3376         async_context->uid = g_strdup (uid);
3377         async_context->rid = g_strdup (rid);
3378
3379         simple = g_simple_async_result_new (
3380                 G_OBJECT (client), callback, user_data,
3381                 e_cal_client_get_object);
3382
3383         g_simple_async_result_set_check_cancellable (simple, cancellable);
3384
3385         g_simple_async_result_set_op_res_gpointer (
3386                 simple, async_context, (GDestroyNotify) async_context_free);
3387
3388         g_simple_async_result_run_in_thread (
3389                 simple, cal_client_get_object_thread,
3390                 G_PRIORITY_DEFAULT, cancellable);
3391
3392         g_object_unref (simple);
3393 }
3394
3395 /**
3396  * e_cal_client_get_object_finish:
3397  * @client: an #ECalClient
3398  * @result: a #GAsyncResult
3399  * @out_icalcomp: (out): Return value for the calendar component object.
3400  * @error: (out): a #GError to set an error, if any
3401  *
3402  * Finishes previous call of e_cal_client_get_object() and
3403  * sets @out_icalcomp to queried component. This function always returns
3404  * master object for a case of @rid being NULL or an empty string.
3405  * This component should be freed with icalcomponent_free().
3406  *
3407  * Use e_cal_client_get_objects_for_uid() to get list of all
3408  * objects for the given uid, which includes master object and
3409  * all detached instances.
3410  *
3411  * Returns: %TRUE if successful, %FALSE otherwise.
3412  *
3413  * Since: 3.2
3414  **/
3415 gboolean
3416 e_cal_client_get_object_finish (ECalClient *client,
3417                                 GAsyncResult *result,
3418                                 icalcomponent **out_icalcomp,
3419                                 GError **error)
3420 {
3421         GSimpleAsyncResult *simple;
3422         AsyncContext *async_context;
3423
3424         g_return_val_if_fail (
3425                 g_simple_async_result_is_valid (
3426                 result, G_OBJECT (client),
3427                 e_cal_client_get_object), FALSE);
3428
3429         simple = G_SIMPLE_ASYNC_RESULT (result);
3430         async_context = g_simple_async_result_get_op_res_gpointer (simple);
3431
3432         if (g_simple_async_result_propagate_error (simple, error))
3433                 return FALSE;
3434
3435         g_return_val_if_fail (async_context->out_comp != NULL, FALSE);
3436
3437         if (out_icalcomp != NULL) {
3438                 *out_icalcomp = async_context->out_comp;
3439                 async_context->out_comp = NULL;
3440         }
3441
3442         return TRUE;
3443 }
3444
3445 /**
3446  * e_cal_client_get_object_sync:
3447  * @client: an #ECalClient
3448  * @uid: Unique identifier for a calendar component.
3449  * @rid: Recurrence identifier.
3450  * @out_icalcomp: (out): Return value for the calendar component object.
3451  * @cancellable: a #GCancellable; can be %NULL
3452  * @error: (out): a #GError to set an error, if any
3453  *
3454  * Queries a calendar for a calendar component object based
3455  * on its unique identifier. This function always returns
3456  * master object for a case of @rid being NULL or an empty string.
3457  * This component should be freed with icalcomponent_free().
3458  *
3459  * Use e_cal_client_get_objects_for_uid_sync() to get list of all
3460  * objects for the given uid, which includes master object and
3461  * all detached instances.
3462  *
3463  * Returns: %TRUE if successful, %FALSE otherwise.
3464  *
3465  * Since: 3.2
3466  **/
3467 gboolean
3468 e_cal_client_get_object_sync (ECalClient *client,
3469                               const gchar *uid,
3470                               const gchar *rid,
3471                               icalcomponent **out_icalcomp,
3472                               GCancellable *cancellable,
3473                               GError **error)
3474 {
3475         icalcomponent *icalcomp = NULL;
3476         icalcomponent_kind kind;
3477         gchar *utf8_uid;
3478         gchar *utf8_rid;
3479         gchar *string = NULL;
3480         gboolean success;
3481
3482         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
3483         g_return_val_if_fail (uid != NULL, FALSE);
3484         g_return_val_if_fail (out_icalcomp != NULL, FALSE);
3485
3486         if (rid == NULL)
3487                 rid = "";
3488
3489         utf8_uid = e_util_utf8_make_valid (uid);
3490         utf8_rid = e_util_utf8_make_valid (rid);
3491
3492         success = e_dbus_calendar_call_get_object_sync (
3493                 client->priv->dbus_proxy, utf8_uid, utf8_rid,
3494                 &string, cancellable, error);
3495
3496         g_free (utf8_uid);
3497         g_free (utf8_rid);
3498
3499         /* Sanity check. */
3500         g_return_val_if_fail (
3501                 (success && (string != NULL)) ||
3502                 (!success && (string == NULL)), FALSE);
3503
3504         if (!success)
3505                 return FALSE;
3506
3507         icalcomp = icalparser_parse_string (string);
3508
3509         g_free (string);
3510
3511         if (icalcomp == NULL) {
3512                 g_set_error_literal (
3513                         error, E_CAL_CLIENT_ERROR,
3514                         E_CAL_CLIENT_ERROR_INVALID_OBJECT,
3515                         e_cal_client_error_to_string (
3516                         E_CAL_CLIENT_ERROR_INVALID_OBJECT));
3517                 return FALSE;
3518         }
3519
3520         switch (e_cal_client_get_source_type (client)) {
3521                 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
3522                         kind = ICAL_VEVENT_COMPONENT;
3523                         break;
3524                 case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
3525                         kind = ICAL_VTODO_COMPONENT;
3526                         break;
3527                 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
3528                         kind = ICAL_VJOURNAL_COMPONENT;
3529                         break;
3530                 default:
3531                         g_warn_if_reached ();
3532                         kind = ICAL_VEVENT_COMPONENT;
3533                         break;
3534         }
3535
3536         if (icalcomponent_isa (icalcomp) == kind) {
3537                 *out_icalcomp = icalcomp;
3538
3539         } else if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
3540                 icalcomponent *subcomponent;
3541
3542                 for (subcomponent = icalcomponent_get_first_component (icalcomp, kind);
3543                         subcomponent != NULL;
3544                         subcomponent = icalcomponent_get_next_component (icalcomp, kind)) {
3545                         struct icaltimetype recurrenceid;
3546
3547                         if (icalcomponent_get_uid (subcomponent) == NULL)
3548                                 continue;
3549
3550                         recurrenceid =
3551                                 icalcomponent_get_recurrenceid (subcomponent);
3552
3553                         if (icaltime_is_null_time (recurrenceid))
3554                                 break;
3555
3556                         if (!icaltime_is_valid_time (recurrenceid))
3557                                 break;
3558                 }
3559
3560                 if (subcomponent == NULL)
3561                         subcomponent = icalcomponent_get_first_component (icalcomp, kind);
3562                 if (subcomponent != NULL)
3563                         subcomponent = icalcomponent_new_clone (subcomponent);
3564
3565                 /* XXX Shouldn't we set an error is this is still NULL? */
3566                 *out_icalcomp = subcomponent;
3567
3568                 icalcomponent_free (icalcomp);
3569         }
3570
3571         return TRUE;
3572 }
3573
3574 /* Helper for e_cal_client_get_objects_for_uid() */
3575 static void
3576 cal_client_get_objects_for_uid_thread (GSimpleAsyncResult *simple,
3577                                        GObject *source_object,
3578                                        GCancellable *cancellable)
3579 {
3580         AsyncContext *async_context;
3581         GError *error = NULL;
3582
3583         async_context = g_simple_async_result_get_op_res_gpointer (simple);
3584
3585         e_cal_client_get_objects_for_uid_sync (
3586                 E_CAL_CLIENT (source_object),
3587                 async_context->uid,
3588                 &async_context->object_list,
3589                 cancellable, &error);
3590
3591         if (error != NULL)
3592                 g_simple_async_result_take_error (simple, error);
3593 }
3594
3595 /**
3596  * e_cal_client_get_objects_for_uid:
3597  * @client: an #ECalClient
3598  * @uid: Unique identifier for a calendar component
3599  * @cancellable: a #GCancellable; can be %NULL
3600  * @callback: callback to call when a result is ready
3601  * @user_data: user data for the @callback
3602  *
3603  * Queries a calendar for all calendar components with the given unique
3604  * ID. This will return any recurring event and all its detached recurrences.
3605  * For non-recurring events, it will just return the object with that ID.
3606  * The call is finished by e_cal_client_get_objects_for_uid_finish() from
3607  * the @callback.
3608  *
3609  * Since: 3.2
3610  **/
3611 void
3612 e_cal_client_get_objects_for_uid (ECalClient *client,
3613                                   const gchar *uid,
3614                                   GCancellable *cancellable,
3615                                   GAsyncReadyCallback callback,
3616                                   gpointer user_data)
3617 {
3618         GSimpleAsyncResult *simple;
3619         AsyncContext *async_context;
3620
3621         g_return_if_fail (E_IS_CAL_CLIENT (client));
3622         g_return_if_fail (uid != NULL);
3623
3624         async_context = g_slice_new0 (AsyncContext);
3625         async_context->uid = g_strdup (uid);
3626
3627         simple = g_simple_async_result_new (
3628                 G_OBJECT (client), callback, user_data,
3629                 e_cal_client_get_objects_for_uid);
3630
3631         g_simple_async_result_set_check_cancellable (simple, cancellable);
3632
3633         g_simple_async_result_set_op_res_gpointer (
3634                 simple, async_context, (GDestroyNotify) async_context_free);
3635
3636         g_simple_async_result_run_in_thread (
3637                 simple, cal_client_get_objects_for_uid_thread,
3638                 G_PRIORITY_DEFAULT, cancellable);
3639
3640         g_object_unref (simple);
3641 }
3642
3643 /**
3644  * e_cal_client_get_objects_for_uid_finish:
3645  * @client: an #ECalClient
3646  * @result: a #GAsyncResult
3647  * @out_ecalcomps: (out) (transfer full) (element-type ECalComponent):
3648  *                 Return location for the list of objects obtained from the
3649  *                 backend
3650  * @error: (out): a #GError to set an error, if any
3651  *
3652  * Finishes previous call of e_cal_client_get_objects_for_uid() and
3653  * sets @out_ecalcomps to a list of #ECalComponent<!-- -->s corresponding to
3654  * found components for a given uid of the same type as this client.
3655  * This list should be freed with e_cal_client_free_ecalcomp_slist().
3656  *
3657  * Returns: %TRUE if successful, %FALSE otherwise.
3658  *
3659  * Since: 3.2
3660  **/
3661 gboolean
3662 e_cal_client_get_objects_for_uid_finish (ECalClient *client,
3663                                          GAsyncResult *result,
3664                                          GSList **out_ecalcomps,
3665                                          GError **error)
3666 {
3667         GSimpleAsyncResult *simple;
3668         AsyncContext *async_context;
3669
3670         g_return_val_if_fail (
3671                 g_simple_async_result_is_valid (
3672                 result, G_OBJECT (client),
3673                 e_cal_client_get_objects_for_uid), FALSE);
3674
3675         simple = G_SIMPLE_ASYNC_RESULT (result);
3676         async_context = g_simple_async_result_get_op_res_gpointer (simple);
3677
3678         if (g_simple_async_result_propagate_error (simple, error))
3679                 return FALSE;
3680
3681         if (out_ecalcomps != NULL) {
3682                 *out_ecalcomps = async_context->object_list;
3683                 async_context->object_list = NULL;
3684         }
3685
3686         return TRUE;
3687 }
3688
3689 /**
3690  * e_cal_client_get_objects_for_uid_sync:
3691  * @client: an #ECalClient
3692  * @uid: Unique identifier for a calendar component
3693  * @out_ecalcomps: (out) (transfer full) (element-type ECalComponent):
3694  *                 Return location for the list of objects obtained from the
3695  *                 backend
3696  * @cancellable: (allow-none): a #GCancellable; can be %NULL
3697  * @error: (out): a #GError to set an error, if any
3698  *
3699  * Queries a calendar for all calendar components with the given unique
3700  * ID. This will return any recurring event and all its detached recurrences.
3701  * For non-recurring events, it will just return the object with that ID.
3702  * This list should be freed with e_cal_client_free_ecalcomp_slist().
3703  *
3704  * Returns: %TRUE if successful, %FALSE otherwise.
3705  *
3706  * Since: 3.2
3707  **/
3708 gboolean
3709 e_cal_client_get_objects_for_uid_sync (ECalClient *client,
3710                                        const gchar *uid,
3711                                        GSList **out_ecalcomps,
3712                                        GCancellable *cancellable,
3713                                        GError **error)
3714 {
3715         icalcomponent *icalcomp;
3716         icalcomponent_kind kind;
3717         gchar *utf8_uid;
3718         gchar *string = NULL;
3719         gboolean success;
3720
3721         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
3722         g_return_val_if_fail (uid != NULL, FALSE);
3723         g_return_val_if_fail (out_ecalcomps != NULL, FALSE);
3724
3725         utf8_uid = e_util_utf8_make_valid (uid);
3726
3727         success = e_dbus_calendar_call_get_object_sync (
3728                 client->priv->dbus_proxy, utf8_uid, "",
3729                 &string, cancellable, error);
3730
3731         g_free (utf8_uid);
3732
3733         /* Sanity check. */
3734         g_return_val_if_fail (
3735                 (success && (string != NULL)) ||
3736                 (!success && (string == NULL)), FALSE);
3737
3738         if (!success)
3739                 return FALSE;
3740
3741         icalcomp = icalparser_parse_string (string);
3742
3743         g_free (string);
3744
3745         if (icalcomp == NULL) {
3746                 g_set_error_literal (
3747                         error, E_CAL_CLIENT_ERROR,
3748                         E_CAL_CLIENT_ERROR_INVALID_OBJECT,
3749                         e_cal_client_error_to_string (
3750                         E_CAL_CLIENT_ERROR_INVALID_OBJECT));
3751                 return FALSE;
3752         }
3753
3754         switch (e_cal_client_get_source_type (client)) {
3755                 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
3756                         kind = ICAL_VEVENT_COMPONENT;
3757                         break;
3758                 case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
3759                         kind = ICAL_VTODO_COMPONENT;
3760                         break;
3761                 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
3762                         kind = ICAL_VJOURNAL_COMPONENT;
3763                         break;
3764                 default:
3765                         g_warn_if_reached ();
3766                         kind = ICAL_VEVENT_COMPONENT;
3767                         break;
3768         }
3769
3770         if (icalcomponent_isa (icalcomp) == kind) {
3771                 ECalComponent *comp;
3772
3773                 comp = e_cal_component_new ();
3774                 e_cal_component_set_icalcomponent (comp, icalcomp);
3775                 *out_ecalcomps = g_slist_append (NULL, comp);
3776
3777         } else if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
3778                 GSList *tmp = NULL;
3779                 icalcomponent *subcomponent;
3780
3781                 subcomponent = icalcomponent_get_first_component (
3782                         icalcomp, kind);
3783
3784                 while (subcomponent != NULL) {
3785                         ECalComponent *comp;
3786                         icalcomponent *clone;
3787
3788                         comp = e_cal_component_new ();
3789                         clone = icalcomponent_new_clone (subcomponent);
3790                         e_cal_component_set_icalcomponent (comp, clone);
3791                         tmp = g_slist_prepend (tmp, comp);
3792
3793                         subcomponent = icalcomponent_get_next_component (
3794                                 icalcomp, kind);
3795                 }
3796
3797                 *out_ecalcomps = g_slist_reverse (tmp);
3798
3799                 icalcomponent_free (icalcomp);
3800         }
3801
3802         return TRUE;
3803 }
3804
3805 /* Helper for e_cal_client_get_object_list() */
3806 static void
3807 cal_client_get_object_list_thread (GSimpleAsyncResult *simple,
3808                                    GObject *source_object,
3809                                    GCancellable *cancellable)
3810 {
3811         AsyncContext *async_context;
3812         GError *error = NULL;
3813
3814         async_context = g_simple_async_result_get_op_res_gpointer (simple);
3815
3816         e_cal_client_get_object_list_sync (
3817                 E_CAL_CLIENT (source_object),
3818                 async_context->sexp,
3819                 &async_context->comp_list,
3820                 cancellable, &error);
3821
3822         if (error != NULL)
3823                 g_simple_async_result_take_error (simple, error);
3824 }
3825
3826 /**
3827  * e_cal_client_get_object_list:
3828  * @client: an #ECalClient
3829  * @sexp: an S-expression representing the query
3830  * @cancellable: a #GCancellable; can be %NULL
3831  * @callback: callback to call when a result is ready
3832  * @user_data: user data for the @callback
3833  *
3834  * Gets a list of objects from the calendar that match the query specified
3835  * by the @sexp argument, returning matching objects as a list of #icalcomponent-s.
3836  * The call is finished by e_cal_client_get_object_list_finish() from
3837  * the @callback.
3838  *
3839  * Since: 3.2
3840  **/
3841 void
3842 e_cal_client_get_object_list (ECalClient *client,
3843                               const gchar *sexp,
3844                               GCancellable *cancellable,
3845                               GAsyncReadyCallback callback,
3846                               gpointer user_data)
3847 {
3848         GSimpleAsyncResult *simple;
3849         AsyncContext *async_context;
3850
3851         g_return_if_fail (E_IS_CAL_CLIENT (client));
3852         g_return_if_fail (sexp != NULL);
3853
3854         async_context = g_slice_new0 (AsyncContext);
3855         async_context->sexp = g_strdup (sexp);
3856
3857         simple = g_simple_async_result_new (
3858                 G_OBJECT (client), callback, user_data,
3859                 e_cal_client_get_object_list);
3860
3861         g_simple_async_result_set_check_cancellable (simple, cancellable);
3862
3863         g_simple_async_result_set_op_res_gpointer (
3864                 simple, async_context, (GDestroyNotify) async_context_free);
3865
3866         g_simple_async_result_run_in_thread (
3867                 simple, cal_client_get_object_list_thread,
3868                 G_PRIORITY_DEFAULT, cancellable);
3869
3870         g_object_unref (simple);
3871 }
3872
3873 /**
3874  * e_cal_client_get_object_list_finish:
3875  * @client: an #ECalClient
3876  * @result: a #GAsyncResult
3877  * @out_icalcomps: (out) (element-type icalcomponent): list of matching
3878  *                 #icalcomponent<!-- -->s
3879  * @error: (out): a #GError to set an error, if any
3880  *
3881  * Finishes previous call of e_cal_client_get_object_list() and
3882  * sets @out_icalcomps to a matching list of #icalcomponent-s.
3883  * This list should be freed with e_cal_client_free_icalcomp_slist().
3884  *
3885  * Returns: %TRUE if successful, %FALSE otherwise.
3886  *
3887  * Since: 3.2
3888  **/
3889 gboolean
3890 e_cal_client_get_object_list_finish (ECalClient *client,
3891                                      GAsyncResult *result,
3892                                      GSList **out_icalcomps,
3893                                      GError **error)
3894 {
3895         GSimpleAsyncResult *simple;
3896         AsyncContext *async_context;
3897
3898         g_return_val_if_fail (
3899                 g_simple_async_result_is_valid (
3900                 result, G_OBJECT (client),
3901                 e_cal_client_get_object_list), FALSE);
3902
3903         simple = G_SIMPLE_ASYNC_RESULT (result);
3904         async_context = g_simple_async_result_get_op_res_gpointer (simple);
3905
3906         if (g_simple_async_result_propagate_error (simple, error))
3907                 return FALSE;
3908
3909         if (out_icalcomps != NULL) {
3910                 *out_icalcomps = async_context->comp_list;
3911                 async_context->comp_list = NULL;
3912         }
3913
3914         return TRUE;
3915 }
3916
3917 /**
3918  * e_cal_client_get_object_list_sync:
3919  * @client: an #ECalClient
3920  * @sexp: an S-expression representing the query
3921  * @out_icalcomps: (out) (element-type icalcomponent): list of matching
3922  *                 #icalcomponent<!-- -->s
3923  * @cancellable: (allow-none): a #GCancellable; can be %NULL
3924  * @error: (out): a #GError to set an error, if any
3925  *
3926  * Gets a list of objects from the calendar that match the query specified
3927  * by the @sexp argument. The objects will be returned in the @out_icalcomps
3928  * argument, which is a list of #icalcomponent.
3929  * This list should be freed with e_cal_client_free_icalcomp_slist().
3930  *
3931  * Returns: %TRUE if successful, %FALSE otherwise.
3932  *
3933  * Since: 3.2
3934  **/
3935 gboolean
3936 e_cal_client_get_object_list_sync (ECalClient *client,
3937                                    const gchar *sexp,
3938                                    GSList **out_icalcomps,
3939                                    GCancellable *cancellable,
3940                                    GError **error)
3941 {
3942         GSList *tmp = NULL;
3943         gchar *utf8_sexp;
3944         gchar **strv = NULL;
3945         gboolean success;
3946         gint ii;
3947
3948         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
3949         g_return_val_if_fail (sexp != NULL, FALSE);
3950         g_return_val_if_fail (out_icalcomps != NULL, FALSE);
3951
3952         utf8_sexp = e_util_utf8_make_valid (sexp);
3953
3954         success = e_dbus_calendar_call_get_object_list_sync (
3955                 client->priv->dbus_proxy, utf8_sexp,
3956                 &strv, cancellable, error);
3957
3958         g_free (utf8_sexp);
3959
3960         /* Sanity check. */
3961         g_return_val_if_fail (
3962                 (success && (strv != NULL)) ||
3963                 (!success && (strv == NULL)), FALSE);
3964
3965         if (!success)
3966                 return FALSE;
3967
3968         for (ii = 0; strv[ii] != NULL; ii++) {
3969                 icalcomponent *icalcomp;
3970
3971                 icalcomp = icalcomponent_new_from_string (strv[ii]);
3972                 if (icalcomp == NULL)
3973                         continue;
3974
3975                 tmp = g_slist_prepend (tmp, icalcomp);
3976         }
3977
3978         *out_icalcomps = g_slist_reverse (tmp);
3979
3980         return TRUE;
3981 }
3982
3983 /* Helper for e_cal_client_get_object_list_as_comps() */
3984 static void
3985 cal_client_get_object_list_as_comps_thread (GSimpleAsyncResult *simple,
3986                                             GObject *source_object,
3987                                             GCancellable *cancellable)
3988 {
3989         AsyncContext *async_context;
3990         GError *error = NULL;
3991
3992         async_context = g_simple_async_result_get_op_res_gpointer (simple);
3993
3994         e_cal_client_get_object_list_as_comps_sync (
3995                 E_CAL_CLIENT (source_object),
3996                 async_context->sexp,
3997                 &async_context->object_list,
3998                 cancellable, &error);
3999
4000         if (error != NULL)
4001                 g_simple_async_result_take_error (simple, error);
4002 }
4003
4004 /**
4005  * e_cal_client_get_object_list_as_comps:
4006  * @client: an #ECalClient
4007  * @sexp: an S-expression representing the query
4008  * @cancellable: a #GCancellable; can be %NULL
4009  * @callback: callback to call when a result is ready
4010  * @user_data: user data for the @callback
4011  *
4012  * Gets a list of objects from the calendar that match the query specified
4013  * by the @sexp argument, returning matching objects as a list of #ECalComponent-s.
4014  * The call is finished by e_cal_client_get_object_list_as_comps_finish() from
4015  * the @callback.
4016  *
4017  * Since: 3.2
4018  **/
4019 void
4020 e_cal_client_get_object_list_as_comps (ECalClient *client,
4021                                        const gchar *sexp,
4022                                        GCancellable *cancellable,
4023                                        GAsyncReadyCallback callback,
4024                                        gpointer user_data)
4025 {
4026         GSimpleAsyncResult *simple;
4027         AsyncContext *async_context;
4028
4029         g_return_if_fail (E_IS_CAL_CLIENT (client));
4030         g_return_if_fail (sexp != NULL);
4031
4032         async_context = g_slice_new0 (AsyncContext);
4033         async_context->sexp = g_strdup (sexp);
4034
4035         simple = g_simple_async_result_new (
4036                 G_OBJECT (client), callback, user_data,
4037                 e_cal_client_get_object_list_as_comps);
4038
4039         g_simple_async_result_set_check_cancellable (simple, cancellable);
4040
4041         g_simple_async_result_set_op_res_gpointer (
4042                 simple, async_context, (GDestroyNotify) async_context_free);
4043
4044         g_simple_async_result_run_in_thread (
4045                 simple, cal_client_get_object_list_as_comps_thread,
4046                 G_PRIORITY_DEFAULT, cancellable);
4047
4048         g_object_unref (simple);
4049 }
4050
4051 /**
4052  * e_cal_client_get_object_list_as_comps_finish:
4053  * @client: an #ECalClient
4054  * @result: a #GAsyncResult
4055  * @out_ecalcomps: (out) (element-type ECalComponent): list of matching
4056  *                 #ECalComponent<!-- -->s
4057  * @error: (out): a #GError to set an error, if any
4058  *
4059  * Finishes previous call of e_cal_client_get_object_list_as_comps() and
4060  * sets @out_ecalcomps to a matching list of #ECalComponent-s.
4061  * This list should be freed with e_cal_client_free_ecalcomp_slist().
4062  *
4063  * Returns: %TRUE if successful, %FALSE otherwise.
4064  *
4065  * Since: 3.2
4066  **/
4067 gboolean
4068 e_cal_client_get_object_list_as_comps_finish (ECalClient *client,
4069                                               GAsyncResult *result,
4070                                               GSList **out_ecalcomps,
4071                                               GError **error)
4072 {
4073         GSimpleAsyncResult *simple;
4074         AsyncContext *async_context;
4075
4076         g_return_val_if_fail (
4077                 g_simple_async_result_is_valid (
4078                 result, G_OBJECT (client),
4079                 e_cal_client_get_object_list_as_comps), FALSE);
4080
4081         simple = G_SIMPLE_ASYNC_RESULT (result);
4082         async_context = g_simple_async_result_get_op_res_gpointer (simple);
4083
4084         if (g_simple_async_result_propagate_error (simple, error))
4085                 return FALSE;
4086
4087         if (out_ecalcomps != NULL) {
4088                 *out_ecalcomps = async_context->object_list;
4089                 async_context->object_list = NULL;
4090         }
4091
4092         return TRUE;
4093 }
4094
4095 /**
4096  * e_cal_client_get_object_list_as_comps_sync:
4097  * @client: an #ECalClient
4098  * @sexp: an S-expression representing the query
4099  * @out_ecalcomps: (out) (element-type ECalComponent): list of matching
4100  *                 #ECalComponent<!-- -->s
4101  * @cancellable: (allow-none): a #GCancellable; can be %NULL
4102  * @error: (out): a #GError to set an error, if any
4103  *
4104  * Gets a list of objects from the calendar that match the query specified
4105  * by the @sexp argument. The objects will be returned in the @out_ecalcomps
4106  * argument, which is a list of #ECalComponent.
4107  * This list should be freed with e_cal_client_free_ecalcomp_slist().
4108  *
4109  * Returns: %TRUE if successful, %FALSE otherwise.
4110  *
4111  * Since: 3.2
4112  **/
4113 gboolean
4114 e_cal_client_get_object_list_as_comps_sync (ECalClient *client,
4115                                             const gchar *sexp,
4116                                             GSList **out_ecalcomps,
4117                                             GCancellable *cancellable,
4118                                             GError **error)
4119 {
4120         GSList *list = NULL;
4121         GSList *link;
4122         GQueue trash = G_QUEUE_INIT;
4123         gboolean success;
4124
4125         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
4126         g_return_val_if_fail (sexp != NULL, FALSE);
4127         g_return_val_if_fail (out_ecalcomps != NULL, FALSE);
4128
4129         success = e_cal_client_get_object_list_sync (
4130                 client, sexp, &list, cancellable, error);
4131
4132         if (!success) {
4133                 g_warn_if_fail (list == NULL);
4134                 return FALSE;
4135         }
4136
4137         /* Convert the icalcomponent list to an ECalComponent list. */
4138         for (link = list; link != NULL; link = g_slist_next (link)) {
4139                 ECalComponent *comp;
4140                 icalcomponent *icalcomp = link->data;
4141
4142                 comp = e_cal_component_new ();
4143
4144                 /* This takes ownership of the icalcomponent, if it works. */
4145                 if (e_cal_component_set_icalcomponent (comp, icalcomp)) {
4146                         link->data = g_object_ref (comp);
4147                 } else {
4148                         /* On failure, free resources and add
4149                          * the GSList link to the trash queue. */
4150                         icalcomponent_free (icalcomp);
4151                         g_queue_push_tail (&trash, link);
4152                         link->data = NULL;
4153                 }
4154
4155                 g_object_unref (comp);
4156         }
4157
4158         /* Delete GSList links we failed to convert. */
4159         while ((link = g_queue_pop_head (&trash)) != NULL)
4160                 list = g_slist_delete_link (list, link);
4161
4162         *out_ecalcomps = list;
4163
4164         return TRUE;
4165 }
4166
4167 /* Helper for e_cal_client_get_free_busy() */
4168 static void
4169 cal_client_get_free_busy_thread (GSimpleAsyncResult *simple,
4170                                  GObject *source_object,
4171                                  GCancellable *cancellable)
4172 {
4173         AsyncContext *async_context;
4174         GError *error = NULL;
4175
4176         async_context = g_simple_async_result_get_op_res_gpointer (simple);
4177
4178         e_cal_client_get_free_busy_sync (
4179                 E_CAL_CLIENT (source_object),
4180                 async_context->start,
4181                 async_context->end,
4182                 async_context->string_list,
4183                 cancellable, &error);
4184
4185         if (error != NULL)
4186                 g_simple_async_result_take_error (simple, error);
4187 }
4188
4189 /**
4190  * e_cal_client_get_free_busy:
4191  * @client: an #ECalClient
4192  * @start: Start time for query
4193  * @end: End time for query
4194  * @users: (element-type utf8): List of users to retrieve free/busy information for
4195  * @cancellable: (allow-none): a #GCancellable; can be %NULL
4196  * @callback: callback to call when a result is ready
4197  * @user_data: user data for the @callback
4198  *
4199  * Begins retrieval of free/busy information from the calendar server
4200  * as a list of #ECalComponent-s. Connect to "free-busy-data" signal
4201  * to receive chunks of free/busy components.
4202  * The call is finished by e_cal_client_get_free_busy_finish() from
4203  * the @callback.
4204  *
4205  * Since: 3.2
4206  **/
4207 void
4208 e_cal_client_get_free_busy (ECalClient *client,
4209                             time_t start,
4210                             time_t end,
4211                             const GSList *users,
4212                             GCancellable *cancellable,
4213                             GAsyncReadyCallback callback,
4214                             gpointer user_data)
4215 {
4216         GSimpleAsyncResult *simple;
4217         AsyncContext *async_context;
4218
4219         g_return_if_fail (E_IS_CAL_CLIENT (client));
4220         g_return_if_fail (start > 0);
4221         g_return_if_fail (end > 0);
4222
4223         async_context = g_slice_new0 (AsyncContext);
4224         async_context->start = start;
4225         async_context->end = end;
4226         async_context->string_list = g_slist_copy_deep (
4227                 (GSList *) users, (GCopyFunc) g_strdup, NULL);
4228
4229         simple = g_simple_async_result_new (
4230                 G_OBJECT (client), callback, user_data,
4231                 e_cal_client_get_free_busy);
4232
4233         g_simple_async_result_set_check_cancellable (simple, cancellable);
4234
4235         g_simple_async_result_set_op_res_gpointer (
4236                 simple, async_context, (GDestroyNotify) async_context_free);
4237
4238         g_simple_async_result_run_in_thread (
4239                 simple, cal_client_get_free_busy_thread,
4240                 G_PRIORITY_DEFAULT, cancellable);
4241
4242         g_object_unref (simple);
4243 }
4244
4245 /**
4246  * e_cal_client_get_free_busy_finish:
4247  * @client: an #ECalClient
4248  * @result: a #GAsyncResult
4249  * @error: (out): a #GError to set an error, if any
4250  *
4251  * Finishes previous call of e_cal_client_get_free_busy().
4252  * All VFREEBUSY #ECalComponent-s were received by "free-busy-data" signal.
4253  *
4254  * Returns: %TRUE if successful, %FALSE otherwise.
4255  *
4256  * Since: 3.2
4257  **/
4258 gboolean
4259 e_cal_client_get_free_busy_finish (ECalClient *client,
4260                                    GAsyncResult *result,
4261                                    GError **error)
4262 {
4263         GSimpleAsyncResult *simple;
4264
4265         g_return_val_if_fail (
4266                 g_simple_async_result_is_valid (
4267                 result, G_OBJECT (client),
4268                 e_cal_client_get_free_busy), FALSE);
4269
4270         simple = G_SIMPLE_ASYNC_RESULT (result);
4271
4272         /* Assume success unless a GError is set. */
4273         return !g_simple_async_result_propagate_error (simple, error);
4274 }
4275
4276 /**
4277  * e_cal_client_get_free_busy_sync:
4278  * @client: an #ECalClient
4279  * @start: Start time for query
4280  * @end: End time for query
4281  * @users: (element-type utf8): List of users to retrieve free/busy information for
4282  * @cancellable: (allow-none): a #GCancellable; can be %NULL
4283  * @error: (out): a #GError to set an error, if any
4284  *
4285  * Gets free/busy information from the calendar server.
4286  * All VFREEBUSY #ECalComponent-s were received by "free-busy-data" signal.
4287  *
4288  * Returns: %TRUE if successful, %FALSE otherwise.
4289  *
4290  * Since: 3.2
4291  **/
4292 gboolean
4293 e_cal_client_get_free_busy_sync (ECalClient *client,
4294                                  time_t start,
4295                                  time_t end,
4296                                  const GSList *users,
4297                                  GCancellable *cancellable,
4298                                  GError **error)
4299 {
4300         gchar **strv;
4301         gboolean success;
4302         gint ii = 0;
4303
4304         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
4305         g_return_val_if_fail (start > 0, FALSE);
4306         g_return_val_if_fail (end > 0, FALSE);
4307
4308         strv = g_new0 (gchar *, g_slist_length ((GSList *) users) + 1);
4309         while (users != NULL) {
4310                 strv[ii++] = e_util_utf8_make_valid (users->data);
4311                 users = g_slist_next (users);
4312         }
4313
4314         success = e_dbus_calendar_call_get_free_busy_sync (
4315                 client->priv->dbus_proxy,
4316                 (gint64) start, (gint64) end,
4317                 (const gchar * const *) strv,
4318                 cancellable, error);
4319
4320         g_strfreev (strv);
4321
4322         return success;
4323 }
4324
4325 /* Helper for e_cal_client_create_object() */
4326 static void
4327 cal_client_create_object_thread (GSimpleAsyncResult *simple,
4328                                  GObject *source_object,
4329                                  GCancellable *cancellable)
4330 {
4331         AsyncContext *async_context;
4332         GError *error = NULL;
4333
4334         async_context = g_simple_async_result_get_op_res_gpointer (simple);
4335
4336         e_cal_client_create_object_sync (
4337                 E_CAL_CLIENT (source_object),
4338                 async_context->in_comp,
4339                 &async_context->uid,
4340                 cancellable, &error);
4341
4342         if (error != NULL)
4343                 g_simple_async_result_take_error (simple, error);
4344 }
4345
4346 /**
4347  * e_cal_client_create_object:
4348  * @client: an #ECalClient
4349  * @icalcomp: The component to create
4350  * @cancellable: a #GCancellable; can be %NULL
4351  * @callback: callback to call when a result is ready
4352  * @user_data: user data for the @callback
4353  *
4354  * Requests the calendar backend to create the object specified by the @icalcomp
4355  * argument. Some backends would assign a specific UID to the newly created object,
4356  * but this function does not modify the original @icalcomp if its UID changes.
4357  * The call is finished by e_cal_client_create_object_finish() from
4358  * the @callback.
4359  *
4360  * Since: 3.2
4361  **/
4362 void
4363 e_cal_client_create_object (ECalClient *client,
4364                             icalcomponent *icalcomp,
4365                             GCancellable *cancellable,
4366                             GAsyncReadyCallback callback,
4367                             gpointer user_data)
4368 {
4369         GSimpleAsyncResult *simple;
4370         AsyncContext *async_context;
4371
4372         g_return_if_fail (E_IS_CAL_CLIENT (client));
4373         g_return_if_fail (icalcomp != NULL);
4374
4375         async_context = g_slice_new0 (AsyncContext);
4376         async_context->in_comp = icalcomponent_new_clone (icalcomp);
4377
4378         simple = g_simple_async_result_new (
4379                 G_OBJECT (client), callback, user_data,
4380                 e_cal_client_create_object);
4381
4382         g_simple_async_result_set_op_res_gpointer (
4383                 simple, async_context, (GDestroyNotify) async_context_free);
4384
4385         g_simple_async_result_run_in_thread (
4386                 simple, cal_client_create_object_thread,
4387                 G_PRIORITY_DEFAULT, cancellable);
4388
4389         g_object_unref (simple);
4390 }
4391
4392 /**
4393  * e_cal_client_create_object_finish:
4394  * @client: an #ECalClient
4395  * @result: a #GAsyncResult
4396  * @out_uid: (out): Return value for the UID assigned to the new component
4397  *           by the calendar backend
4398  * @error: (out): a #GError to set an error, if any
4399  *
4400  * Finishes previous call of e_cal_client_create_object() and
4401  * sets @out_uid to newly assigned UID for the created object.
4402  * This @out_uid should be freed with g_free().
4403  *
4404  * Returns: %TRUE if successful, %FALSE otherwise.
4405  *
4406  * Since: 3.2
4407  **/
4408 gboolean
4409 e_cal_client_create_object_finish (ECalClient *client,
4410                                    GAsyncResult *result,
4411                                    gchar **out_uid,
4412                                    GError **error)
4413 {
4414         GSimpleAsyncResult *simple;
4415         AsyncContext *async_context;
4416
4417         g_return_val_if_fail (
4418                 g_simple_async_result_is_valid (
4419                 result, G_OBJECT (client),
4420                 e_cal_client_create_object), FALSE);
4421
4422         simple = G_SIMPLE_ASYNC_RESULT (result);
4423         async_context = g_simple_async_result_get_op_res_gpointer (simple);
4424
4425         if (g_simple_async_result_propagate_error (simple, error))
4426                 return FALSE;
4427
4428         g_return_val_if_fail (async_context->uid != NULL, FALSE);
4429
4430         if (out_uid != NULL) {
4431                 *out_uid = async_context->uid;
4432                 async_context->uid = NULL;
4433         }
4434
4435         return TRUE;
4436 }
4437
4438 /**
4439  * e_cal_client_create_object_sync:
4440  * @client: an #ECalClient
4441  * @icalcomp: The component to create
4442  * @out_uid: (out): Return value for the UID assigned to the new component
4443  *           by the calendar backend
4444  * @cancellable: a #GCancellable; can be %NULL
4445  * @error: (out): a #GError to set an error, if any
4446  *
4447  * Requests the calendar backend to create the object specified by the
4448  * @icalcomp argument. Some backends would assign a specific UID to the newly
4449  * created object, in those cases that UID would be returned in the @out_uid
4450  * argument. This function does not modify the original @icalcomp if its UID
4451  * changes.  Returned @out_uid should be freed with g_free().
4452  *
4453  * Returns: %TRUE if successful, %FALSE otherwise.
4454  *
4455  * Since: 3.2
4456  **/
4457 gboolean
4458 e_cal_client_create_object_sync (ECalClient *client,
4459                                  icalcomponent *icalcomp,
4460                                  gchar **out_uid,
4461                                  GCancellable *cancellable,
4462                                  GError **error)
4463 {
4464         GSList link = { icalcomp, NULL };
4465         GSList *string_list = NULL;
4466         gboolean success;
4467
4468         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
4469         g_return_val_if_fail (icalcomp != NULL, FALSE);
4470
4471         success = e_cal_client_create_objects_sync (
4472                 client, &link, &string_list, cancellable, error);
4473
4474         /* Sanity check. */
4475         g_return_val_if_fail (
4476                 (success && (string_list != NULL)) ||
4477                 (!success && (string_list == NULL)), FALSE);
4478
4479         if (out_uid != NULL && string_list != NULL)
4480                 *out_uid = g_strdup (string_list->data);
4481
4482         g_slist_free_full (string_list, (GDestroyNotify) g_free);
4483
4484         return success;
4485 }
4486
4487 /* Helper for e_cal_client_create_objects() */
4488 static void
4489 cal_client_create_objects_thread (GSimpleAsyncResult *simple,
4490                                   GObject *source_object,
4491                                   GCancellable *cancellable)
4492 {
4493         AsyncContext *async_context;
4494         GError *error = NULL;
4495
4496         async_context = g_simple_async_result_get_op_res_gpointer (simple);
4497
4498         e_cal_client_create_objects_sync (
4499                 E_CAL_CLIENT (source_object),
4500                 async_context->comp_list,
4501                 &async_context->string_list,
4502                 cancellable, &error);
4503
4504         if (error != NULL)
4505                 g_simple_async_result_take_error (simple, error);
4506 }
4507
4508 /**
4509  * e_cal_client_create_objects:
4510  * @client: an #ECalClient
4511  * @icalcomps: (element-type icalcomponent): The components to create
4512  * @cancellable: (allow-none): a #GCancellable; can be %NULL
4513  * @callback: callback to call when a result is ready
4514  * @user_data: user data for the @callback
4515  *
4516  * Requests the calendar backend to create the objects specified by the @icalcomps
4517  * argument. Some backends would assign a specific UID to the newly created object,
4518  * but this function does not modify the original @icalcomps if their UID changes.
4519  * The call is finished by e_cal_client_create_objects_finish() from
4520  * the @callback.
4521  *
4522  * Since: 3.6
4523  **/
4524 void
4525 e_cal_client_create_objects (ECalClient *client,
4526                              GSList *icalcomps,
4527                              GCancellable *cancellable,
4528                              GAsyncReadyCallback callback,
4529                              gpointer user_data)
4530 {
4531         GSimpleAsyncResult *simple;
4532         AsyncContext *async_context;
4533
4534         g_return_if_fail (E_IS_CAL_CLIENT (client));
4535         g_return_if_fail (icalcomps != NULL);
4536
4537         async_context = g_slice_new0 (AsyncContext);
4538         async_context->comp_list = g_slist_copy_deep (
4539                 icalcomps, (GCopyFunc) icalcomponent_new_clone, NULL);
4540
4541         simple = g_simple_async_result_new (
4542                 G_OBJECT (client), callback, user_data,
4543                 e_cal_client_create_objects);
4544
4545         g_simple_async_result_set_check_cancellable (simple, cancellable);
4546
4547         g_simple_async_result_set_op_res_gpointer (
4548                 simple, async_context, (GDestroyNotify) async_context_free);
4549
4550         g_simple_async_result_run_in_thread (
4551                 simple, cal_client_create_objects_thread,
4552                 G_PRIORITY_DEFAULT, cancellable);
4553
4554         g_object_unref (simple);
4555 }
4556
4557 /**
4558  * e_cal_client_create_objects_finish:
4559  * @client: an #ECalClient
4560  * @result: a #GAsyncResult
4561  * @out_uids: (out) (element-type utf8): Return value for the UIDs assigned
4562  *            to the new components by the calendar backend
4563  * @error: (out): a #GError to set an error, if any
4564  *
4565  * Finishes previous call of e_cal_client_create_objects() and
4566  * sets @out_uids to newly assigned UIDs for the created objects.
4567  * This @out_uids should be freed with e_client_util_free_string_slist().
4568  *
4569  * Returns: %TRUE if successful, %FALSE otherwise.
4570  *
4571  * Since: 3.6
4572  **/
4573 gboolean
4574 e_cal_client_create_objects_finish (ECalClient *client,
4575                                     GAsyncResult *result,
4576                                     GSList **out_uids,
4577                                     GError **error)
4578 {
4579         GSimpleAsyncResult *simple;
4580         AsyncContext *async_context;
4581
4582         g_return_val_if_fail (
4583                 g_simple_async_result_is_valid (
4584                 result, G_OBJECT (client),
4585                 e_cal_client_create_objects), FALSE);
4586
4587         simple = G_SIMPLE_ASYNC_RESULT (result);
4588         async_context = g_simple_async_result_get_op_res_gpointer (simple);
4589
4590         if (g_simple_async_result_propagate_error (simple, error))
4591                 return FALSE;
4592
4593         if (out_uids != NULL) {
4594                 *out_uids = async_context->string_list;
4595                 async_context->string_list = NULL;
4596         }
4597
4598         return TRUE;
4599 }
4600
4601 /**
4602  * e_cal_client_create_objects_sync:
4603  * @client: an #ECalClient
4604  * @icalcomps: (element-type icalcomponent): The components to create
4605  * @out_uids: (out) (element-type utf8): Return value for the UIDs assigned
4606  *            to the new components by the calendar backend
4607  * @cancellable: (allow-none): a #GCancellable; can be %NULL
4608  * @error: (out): a #GError to set an error, if any
4609  *
4610  * Requests the calendar backend to create the objects specified by the
4611  * @icalcomps argument. Some backends would assign a specific UID to the
4612  * newly created objects, in those cases these UIDs would be returned in
4613  * the @out_uids argument. This function does not modify the original
4614  * @icalcomps if their UID changes.  Returned @out_uids should be freed
4615  * with e_client_util_free_string_slist().
4616  *
4617  * Returns: %TRUE if successful, %FALSE otherwise.
4618  *
4619  * Since: 3.6
4620  **/
4621 gboolean
4622 e_cal_client_create_objects_sync (ECalClient *client,
4623                                   GSList *icalcomps,
4624                                   GSList **out_uids,
4625                                   GCancellable *cancellable,
4626                                   GError **error)
4627 {
4628         gchar **strv;
4629         gchar **uids = NULL;
4630         gboolean success;
4631         gint ii = 0;
4632
4633         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
4634         g_return_val_if_fail (icalcomps != NULL, FALSE);
4635         g_return_val_if_fail (out_uids != NULL, FALSE);
4636
4637         strv = g_new0 (gchar *, g_slist_length (icalcomps) + 1);
4638         while (icalcomps != NULL) {
4639                 gchar *ical_string;
4640
4641                 ical_string = icalcomponent_as_ical_string_r (icalcomps->data);
4642                 strv[ii++] = e_util_utf8_make_valid (ical_string);
4643                 g_free (ical_string);
4644
4645                 icalcomps = g_slist_next (icalcomps);
4646         }
4647
4648         success = e_dbus_calendar_call_create_objects_sync (
4649                 client->priv->dbus_proxy,
4650                 (const gchar * const *) strv,
4651                 &uids, cancellable, error);
4652
4653         g_strfreev (strv);
4654
4655         /* Sanity check. */
4656         g_return_val_if_fail (
4657                 (success && (uids != NULL)) ||
4658                 (!success && (uids == NULL)), FALSE);
4659
4660         if (uids != NULL) {
4661                 GSList *tmp = NULL;
4662
4663                 /* Steal the string array elements. */
4664                 for (ii = 0; uids[ii] != NULL; ii++) {
4665                         tmp = g_slist_prepend (tmp, uids[ii]);
4666                         uids[ii] = NULL;
4667                 }
4668
4669                 *out_uids = g_slist_reverse (tmp);
4670         }
4671
4672         g_strfreev (uids);
4673
4674         return success;
4675 }
4676
4677 /* Helper for e_cal_client_modify_object() */
4678 static void
4679 cal_client_modify_object_thread (GSimpleAsyncResult *simple,
4680                                  GObject *source_object,
4681                                  GCancellable *cancellable)
4682 {
4683         AsyncContext *async_context;
4684         GError *error = NULL;
4685
4686         async_context = g_simple_async_result_get_op_res_gpointer (simple);
4687
4688         e_cal_client_modify_object_sync (
4689                 E_CAL_CLIENT (source_object),
4690                 async_context->in_comp,
4691                 async_context->mod,
4692                 cancellable, &error);
4693
4694         if (error != NULL)
4695                 g_simple_async_result_take_error (simple, error);
4696 }
4697
4698 /**
4699  * e_cal_client_modify_object:
4700  * @client: an #ECalClient
4701  * @icalcomp: Component to modify
4702  * @mod: Type of modification
4703  * @cancellable: a #GCancellable; can be %NULL
4704  * @callback: callback to call when a result is ready
4705  * @user_data: user data for the @callback
4706  *
4707  * Requests the calendar backend to modify an existing object. If the object
4708  * does not exist on the calendar, an error will be returned.
4709  *
4710  * For recurrent appointments, the @mod argument specifies what to modify,
4711  * if all instances (E_CAL_OBJ_MOD_ALL), a single instance (E_CAL_OBJ_MOD_THIS),
4712  * or a specific set of instances (E_CAL_OBJ_MOD_THIS_AND_PRIOR and
4713  * E_CAL_OBJ_MOD_THIS_AND_FUTURE).
4714  *
4715  * The call is finished by e_cal_client_modify_object_finish() from
4716  * the @callback.
4717  *
4718  * Since: 3.2
4719  **/
4720 void
4721 e_cal_client_modify_object (ECalClient *client,
4722                             icalcomponent *icalcomp,
4723                             ECalObjModType mod,
4724                             GCancellable *cancellable,
4725                             GAsyncReadyCallback callback,
4726                             gpointer user_data)
4727 {
4728         GSimpleAsyncResult *simple;
4729         AsyncContext *async_context;
4730
4731         g_return_if_fail (E_IS_CAL_CLIENT (client));
4732         g_return_if_fail (icalcomp != NULL);
4733
4734         async_context = g_slice_new0 (AsyncContext);
4735         async_context->in_comp = icalcomponent_new_clone (icalcomp);
4736         async_context->mod = mod;
4737
4738         simple = g_simple_async_result_new (
4739                 G_OBJECT (client), callback, user_data,
4740                 e_cal_client_modify_object);
4741
4742         g_simple_async_result_set_check_cancellable (simple, cancellable);
4743
4744         g_simple_async_result_set_op_res_gpointer (
4745                 simple, async_context, (GDestroyNotify) async_context_free);
4746
4747         g_simple_async_result_run_in_thread (
4748                 simple, cal_client_modify_object_thread,
4749                 G_PRIORITY_DEFAULT, cancellable);
4750
4751         g_object_unref (simple);
4752 }
4753
4754 /**
4755  * e_cal_client_modify_object_finish:
4756  * @client: an #ECalClient
4757  * @result: a #GAsyncResult
4758  * @error: (out): a #GError to set an error, if any
4759  *
4760  * Finishes previous call of e_cal_client_modify_object().
4761  *
4762  * Returns: %TRUE if successful, %FALSE otherwise.
4763  *
4764  * Since: 3.2
4765  **/
4766 gboolean
4767 e_cal_client_modify_object_finish (ECalClient *client,
4768                                    GAsyncResult *result,
4769                                    GError **error)
4770 {
4771         GSimpleAsyncResult *simple;
4772
4773         g_return_val_if_fail (
4774                 g_simple_async_result_is_valid (
4775                 result, G_OBJECT (client),
4776                 e_cal_client_modify_object), FALSE);
4777
4778         simple = G_SIMPLE_ASYNC_RESULT (result);
4779
4780         /* Assume success unless a GError is set. */
4781         return !g_simple_async_result_propagate_error (simple, error);
4782 }
4783
4784 /**
4785  * e_cal_client_modify_object_sync:
4786  * @client: an #ECalClient
4787  * @icalcomp: Component to modify
4788  * @mod: Type of modification
4789  * @cancellable: a #GCancellable; can be %NULL
4790  * @error: (out): a #GError to set an error, if any
4791  *
4792  * Requests the calendar backend to modify an existing object. If the object
4793  * does not exist on the calendar, an error will be returned.
4794  *
4795  * For recurrent appointments, the @mod argument specifies what to modify,
4796  * if all instances (E_CAL_OBJ_MOD_ALL), a single instance (E_CAL_OBJ_MOD_THIS),
4797  * or a specific set of instances (E_CAL_OBJ_MOD_THISNADPRIOR and
4798  * E_CAL_OBJ_MOD_THIS_AND_FUTURE).
4799  *
4800  * Returns: %TRUE if successful, %FALSE otherwise.
4801  *
4802  * Since: 3.2
4803  **/
4804 gboolean
4805 e_cal_client_modify_object_sync (ECalClient *client,
4806                                  icalcomponent *icalcomp,
4807                                  ECalObjModType mod,
4808                                  GCancellable *cancellable,
4809                                  GError **error)
4810 {
4811         GSList link = { icalcomp, NULL };
4812
4813         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
4814         g_return_val_if_fail (icalcomp != NULL, FALSE);
4815
4816         return e_cal_client_modify_objects_sync (
4817                 client, &link, mod, cancellable, error);
4818 }
4819
4820 /* Helper for e_cal_client_modify_objects() */
4821 static void
4822 cal_client_modify_objects_thread (GSimpleAsyncResult *simple,
4823                                   GObject *source_object,
4824                                   GCancellable *cancellable)
4825 {
4826         AsyncContext *async_context;
4827         GError *error = NULL;
4828
4829         async_context = g_simple_async_result_get_op_res_gpointer (simple);
4830
4831         e_cal_client_modify_objects_sync (
4832                 E_CAL_CLIENT (source_object),
4833                 async_context->comp_list,
4834                 async_context->mod,
4835                 cancellable, &error);
4836
4837         if (error != NULL)
4838                 g_simple_async_result_take_error (simple, error);
4839 }
4840
4841 /**
4842  * e_cal_client_modify_objects:
4843  * @client: an #ECalClient
4844  * @comps: (element-type icalcomponent): Components to modify
4845  * @mod: Type of modification
4846  * @cancellable: (allow-none): a #GCancellable; can be %NULL
4847  * @callback: callback to call when a result is ready
4848  * @user_data: user data for the @callback
4849  *
4850  * Requests the calendar backend to modify existing objects. If an object
4851  * does not exist on the calendar, an error will be returned.
4852  *
4853  * For recurrent appointments, the @mod argument specifies what to modify,
4854  * if all instances (E_CAL_OBJ_MOD_ALL), a single instance (E_CAL_OBJ_MOD_THIS),
4855  * or a specific set of instances (E_CAL_OBJ_MOD_THISNADPRIOR and
4856  * E_CAL_OBJ_MOD_THIS_AND_FUTURE).
4857  *
4858  * The call is finished by e_cal_client_modify_objects_finish() from
4859  * the @callback.
4860  *
4861  * Since: 3.6
4862  **/
4863 void
4864 e_cal_client_modify_objects (ECalClient *client,
4865                              GSList *comps,
4866                              ECalObjModType mod,
4867                              GCancellable *cancellable,
4868                              GAsyncReadyCallback callback,
4869                              gpointer user_data)
4870 {
4871         GSimpleAsyncResult *simple;
4872         AsyncContext *async_context;
4873
4874         g_return_if_fail (E_IS_CAL_CLIENT (client));
4875         g_return_if_fail (comps != NULL);
4876
4877         async_context = g_slice_new0 (AsyncContext);
4878         async_context->comp_list = g_slist_copy_deep (
4879                 comps, (GCopyFunc) icalcomponent_new_clone, NULL);
4880         async_context->mod = mod;
4881
4882         simple = g_simple_async_result_new (
4883                 G_OBJECT (client), callback, user_data,
4884                 e_cal_client_modify_objects);
4885
4886         g_simple_async_result_set_check_cancellable (simple, cancellable);
4887
4888         g_simple_async_result_set_op_res_gpointer (
4889                 simple, async_context, (GDestroyNotify) async_context_free);
4890
4891         g_simple_async_result_run_in_thread (
4892                 simple, cal_client_modify_objects_thread,
4893                 G_PRIORITY_DEFAULT, cancellable);
4894
4895         g_object_unref (simple);
4896 }
4897
4898 /**
4899  * e_cal_client_modify_objects_finish:
4900  * @client: an #ECalClient
4901  * @result: a #GAsyncResult
4902  * @error: (out): a #GError to set an error, if any
4903  *
4904  * Finishes previous call of e_cal_client_modify_objects().
4905  *
4906  * Returns: %TRUE if successful, %FALSE otherwise.
4907  *
4908  * Since: 3.6
4909  **/
4910 gboolean
4911 e_cal_client_modify_objects_finish (ECalClient *client,
4912                                     GAsyncResult *result,
4913                                     GError **error)
4914 {
4915         GSimpleAsyncResult *simple;
4916
4917         g_return_val_if_fail (
4918                 g_simple_async_result_is_valid (
4919                 result, G_OBJECT (client),
4920                 e_cal_client_modify_objects), FALSE);
4921
4922         simple = G_SIMPLE_ASYNC_RESULT (result);
4923
4924         /* Assume success unless a GError is set. */
4925         return !g_simple_async_result_propagate_error (simple, error);
4926 }
4927
4928 /**
4929  * e_cal_client_modify_objects_sync:
4930  * @client: an #ECalClient
4931  * @comps: (element-type icalcomponent): Components to modify
4932  * @mod: Type of modification
4933  * @cancellable: (allow-none): a #GCancellable; can be %NULL
4934  * @error: (out): a #GError to set an error, if any
4935  *
4936  * Requests the calendar backend to modify existing objects. If an object
4937  * does not exist on the calendar, an error will be returned.
4938  *
4939  * For recurrent appointments, the @mod argument specifies what to modify,
4940  * if all instances (E_CAL_OBJ_MOD_ALL), a single instance (E_CAL_OBJ_MOD_THIS),
4941  * or a specific set of instances (E_CAL_OBJ_MOD_THISNADPRIOR and
4942  * E_CAL_OBJ_MOD_THIS_AND_FUTURE).
4943  *
4944  * Returns: %TRUE if successful, %FALSE otherwise.
4945  *
4946  * Since: 3.6
4947  **/
4948 gboolean
4949 e_cal_client_modify_objects_sync (ECalClient *client,
4950                                   GSList *comps,
4951                                   ECalObjModType mod,
4952                                   GCancellable *cancellable,
4953                                   GError **error)
4954 {
4955         GFlagsClass *flags_class;
4956         GFlagsValue *flags_value;
4957         GString *flags;
4958         gboolean success;
4959         gchar **strv;
4960         gint ii = 0;
4961
4962         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
4963         g_return_val_if_fail (comps != NULL, FALSE);
4964
4965         flags = g_string_new (NULL);
4966         flags_class = g_type_class_ref (E_TYPE_CAL_OBJ_MOD_TYPE);
4967         flags_value = g_flags_get_first_value (flags_class, mod);
4968         while (flags_value != NULL) {
4969                 if (flags->len > 0)
4970                         g_string_append_c (flags, ':');
4971                 g_string_append (flags, flags_value->value_nick);
4972                 mod &= ~flags_value->value;
4973                 flags_value = g_flags_get_first_value (flags_class, mod);
4974         }
4975
4976         strv = g_new0 (gchar *, g_slist_length (comps) + 1);
4977         while (comps != NULL) {
4978                 gchar *ical_string;
4979
4980                 ical_string = icalcomponent_as_ical_string_r (comps->data);
4981                 strv[ii++] = e_util_utf8_make_valid (ical_string);
4982                 g_free (ical_string);
4983
4984                 comps = g_slist_next (comps);
4985         }
4986
4987         success = e_dbus_calendar_call_modify_objects_sync (
4988                 client->priv->dbus_proxy,
4989                 (const gchar * const *) strv,
4990                 flags->str, cancellable, error);
4991
4992         g_strfreev (strv);
4993
4994         g_type_class_unref (flags_class);
4995         g_string_free (flags, TRUE);
4996
4997         return success;
4998 }
4999
5000 /* Helper for e_cal_client_remove_object() */
5001 static void
5002 cal_client_remove_object_thread (GSimpleAsyncResult *simple,
5003                                  GObject *source_object,
5004                                  GCancellable *cancellable)
5005 {
5006         AsyncContext *async_context;
5007         GError *error = NULL;
5008
5009         async_context = g_simple_async_result_get_op_res_gpointer (simple);
5010
5011         e_cal_client_remove_object_sync (
5012                 E_CAL_CLIENT (source_object),
5013                 async_context->uid,
5014                 async_context->rid,
5015                 async_context->mod,
5016                 cancellable, &error);
5017
5018         if (error != NULL)
5019                 g_simple_async_result_take_error (simple, error);
5020 }
5021
5022 /**
5023  * e_cal_client_remove_object:
5024  * @client: an #ECalClient
5025  * @uid: UID of the object to remove
5026  * @rid: Recurrence ID of the specific recurrence to remove
5027  * @mod: Type of the removal
5028  * @cancellable: a #GCancellable; can be %NULL
5029  * @callback: callback to call when a result is ready
5030  * @user_data: user data for the @callback
5031  *
5032  * This function allows the removal of instances of a recurrent
5033  * appointment. By using a combination of the @uid, @rid and @mod
5034  * arguments, you can remove specific instances. If what you want
5035  * is to remove all instances, use %NULL @rid and E_CAL_OBJ_MOD_ALL
5036  * for the @mod.
5037  *
5038  * The call is finished by e_cal_client_remove_object_finish() from
5039  * the @callback.
5040  *
5041  * Since: 3.2
5042  **/
5043 void
5044 e_cal_client_remove_object (ECalClient *client,
5045                             const gchar *uid,
5046                             const gchar *rid,
5047                             ECalObjModType mod,
5048                             GCancellable *cancellable,
5049                             GAsyncReadyCallback callback,
5050                             gpointer user_data)
5051 {
5052         GSimpleAsyncResult *simple;
5053         AsyncContext *async_context;
5054
5055         g_return_if_fail (E_IS_CAL_CLIENT (client));
5056         g_return_if_fail (uid != NULL);
5057         /* rid is optional */
5058
5059         async_context = g_slice_new0 (AsyncContext);
5060         async_context->uid = g_strdup (uid);
5061         async_context->rid = g_strdup (rid);
5062         async_context->mod = mod;
5063
5064         simple = g_simple_async_result_new (
5065                 G_OBJECT (client), callback, user_data,
5066                 e_cal_client_remove_object);
5067
5068         g_simple_async_result_set_check_cancellable (simple, cancellable);
5069
5070         g_simple_async_result_set_op_res_gpointer (
5071                 simple, async_context, (GDestroyNotify) async_context_free);
5072
5073         g_simple_async_result_run_in_thread (
5074                 simple, cal_client_remove_object_thread,
5075                 G_PRIORITY_DEFAULT, cancellable);
5076
5077         g_object_unref (simple);
5078 }
5079
5080 /**
5081  * e_cal_client_remove_object_finish:
5082  * @client: an #ECalClient
5083  * @result: a #GAsyncResult
5084  * @error: (out): a #GError to set an error, if any
5085  *
5086  * Finishes previous call of e_cal_client_remove_object().
5087  *
5088  * Returns: %TRUE if successful, %FALSE otherwise.
5089  *
5090  * Since: 3.2
5091  **/
5092 gboolean
5093 e_cal_client_remove_object_finish (ECalClient *client,
5094                                    GAsyncResult *result,
5095                                    GError **error)
5096 {
5097         GSimpleAsyncResult *simple;
5098
5099         g_return_val_if_fail (
5100                 g_simple_async_result_is_valid (
5101                 result, G_OBJECT (client),
5102                 e_cal_client_remove_object), FALSE);
5103
5104         simple = G_SIMPLE_ASYNC_RESULT (result);
5105
5106         /* Assume success unless a GError is set. */
5107         return !g_simple_async_result_propagate_error (simple, error);
5108 }
5109
5110 /**
5111  * e_cal_client_remove_object_sync:
5112  * @client: an #ECalClient
5113  * @uid: UID of the object to remove
5114  * @rid: Recurrence ID of the specific recurrence to remove
5115  * @mod: Type of the removal
5116  * @cancellable: a #GCancellable; can be %NULL
5117  * @error: (out): a #GError to set an error, if any
5118  *
5119  * This function allows the removal of instances of a recurrent
5120  * appointment. By using a combination of the @uid, @rid and @mod
5121  * arguments, you can remove specific instances. If what you want
5122  * is to remove all instances, use %NULL @rid and E_CAL_OBJ_MODE_THIS
5123  * for the @mod.
5124  *
5125  * Returns: %TRUE if successful, %FALSE otherwise.
5126  *
5127  * Since: 3.2
5128  **/
5129 gboolean
5130 e_cal_client_remove_object_sync (ECalClient *client,
5131                                  const gchar *uid,
5132                                  const gchar *rid,
5133                                  ECalObjModType mod,
5134                                  GCancellable *cancellable,
5135                                  GError **error)
5136 {
5137         ECalComponentId id = { (gchar *) uid, (gchar *) rid };
5138         GSList link = { &id, NULL };
5139
5140         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
5141         g_return_val_if_fail (uid != NULL, FALSE);
5142
5143         return e_cal_client_remove_objects_sync (
5144                 client, &link, mod, cancellable, error);
5145 }
5146
5147 /* Helper for e_cal_client_remove_objects() */
5148 static void
5149 cal_client_remove_objects_thread (GSimpleAsyncResult *simple,
5150                                   GObject *source_object,
5151                                   GCancellable *cancellable)
5152 {
5153         AsyncContext *async_context;
5154         GError *error = NULL;
5155
5156         async_context = g_simple_async_result_get_op_res_gpointer (simple);
5157
5158         e_cal_client_remove_objects_sync (
5159                 E_CAL_CLIENT (source_object),
5160                 async_context->string_list,
5161                 async_context->mod,
5162                 cancellable, &error);
5163
5164         if (error != NULL)
5165                 g_simple_async_result_take_error (simple, error);
5166 }
5167
5168 /**
5169  * e_cal_client_remove_objects:
5170  * @client: an #ECalClient
5171  * @ids: (element-type ECalComponentId): A list of #ECalComponentId objects
5172  * identifying the objects to remove
5173  * @mod: Type of the removal
5174  * @cancellable: a #GCancellable; can be %NULL
5175  * @callback: callback to call when a result is ready
5176  * @user_data: user data for the @callback
5177  *
5178  * This function allows the removal of instances of recurrent appointments.
5179  * #ECalComponentId objects can identify specific instances (if rid is not
5180  * %NULL).  If what you want is to remove all instances, use a %NULL rid in
5181  * the #ECalComponentId and E_CAL_OBJ_MOD_ALL for the @mod.
5182  *
5183  * The call is finished by e_cal_client_remove_objects_finish() from
5184  * the @callback.
5185  *
5186  * Since: 3.6
5187  **/
5188 void
5189 e_cal_client_remove_objects (ECalClient *client,
5190                              const GSList *ids,
5191                              ECalObjModType mod,
5192                              GCancellable *cancellable,
5193                              GAsyncReadyCallback callback,
5194                              gpointer user_data)
5195 {
5196         GSimpleAsyncResult *simple;
5197         AsyncContext *async_context;
5198
5199         g_return_if_fail (E_IS_CAL_CLIENT (client));
5200         g_return_if_fail (ids != NULL);
5201
5202         async_context = g_slice_new0 (AsyncContext);
5203         async_context->string_list = g_slist_copy_deep (
5204                 (GSList *) ids, (GCopyFunc) g_strdup, NULL);
5205         async_context->mod = mod;
5206
5207         simple = g_simple_async_result_new (
5208                 G_OBJECT (client), callback, user_data,
5209                 e_cal_client_remove_objects);
5210
5211         g_simple_async_result_set_check_cancellable (simple, cancellable);
5212
5213         g_simple_async_result_set_op_res_gpointer (
5214                 simple, async_context, (GDestroyNotify) async_context_free);
5215
5216         g_simple_async_result_run_in_thread (
5217                 simple, cal_client_remove_objects_thread,
5218                 G_PRIORITY_DEFAULT, cancellable);
5219
5220         g_object_unref (simple);
5221 }
5222
5223 /**
5224  * e_cal_client_remove_objects_finish:
5225  * @client: an #ECalClient
5226  * @result: a #GAsyncResult
5227  * @error: (out): a #GError to set an error, if any
5228  *
5229  * Finishes previous call of e_cal_client_remove_objects().
5230  *
5231  * Returns: %TRUE if successful, %FALSE otherwise.
5232  *
5233  * Since: 3.6
5234  **/
5235 gboolean
5236 e_cal_client_remove_objects_finish (ECalClient *client,
5237                                     GAsyncResult *result,
5238                                     GError **error)
5239 {
5240         GSimpleAsyncResult *simple;
5241
5242         g_return_val_if_fail (
5243                 g_simple_async_result_is_valid (
5244                 result, G_OBJECT (client),
5245                 e_cal_client_remove_objects), FALSE);
5246
5247         simple = G_SIMPLE_ASYNC_RESULT (result);
5248
5249         /* Assume success unless a GError is set. */
5250         return !g_simple_async_result_propagate_error (simple, error);
5251 }
5252
5253 /**
5254  * e_cal_client_remove_objects_sync:
5255  * @client: an #ECalClient
5256  * @ids: (element-type ECalComponentId): a list of #ECalComponentId objects
5257  *       identifying the objects to remove
5258  * @mod: Type of the removal
5259  * @cancellable: (allow-none): a #GCancellable; can be %NULL
5260  * @error: (out): a #GError to set an error, if any
5261  *
5262  * This function allows the removal of instances of recurrent
5263  * appointments. #ECalComponentId objects can identify specific instances
5264  * (if rid is not %NULL).  If what you want is to remove all instances, use
5265  * a %NULL rid in the #ECalComponentId and E_CAL_OBJ_MOD_ALL for the @mod.
5266  *
5267  * Returns: %TRUE if successful, %FALSE otherwise.
5268  *
5269  * Since: 3.6
5270  **/
5271 gboolean
5272 e_cal_client_remove_objects_sync (ECalClient *client,
5273                                   const GSList *ids,
5274                                   ECalObjModType mod,
5275                                   GCancellable *cancellable,
5276                                   GError **error)
5277 {
5278         GVariantBuilder builder;
5279         GFlagsClass *flags_class;
5280         GFlagsValue *flags_value;
5281         GString *flags;
5282         gboolean success;
5283
5284         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
5285         g_return_val_if_fail (ids != NULL, FALSE);
5286
5287         flags = g_string_new (NULL);
5288         flags_class = g_type_class_ref (E_TYPE_CAL_OBJ_MOD_TYPE);
5289         flags_value = g_flags_get_first_value (flags_class, mod);
5290         while (flags_value != NULL) {
5291                 if (flags->len > 0)
5292                         g_string_append_c (flags, ':');
5293                 g_string_append (flags, flags_value->value_nick);
5294                 mod &= ~flags_value->value;
5295                 flags_value = g_flags_get_first_value (flags_class, mod);
5296         }
5297
5298         g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
5299         while (ids != NULL) {
5300                 ECalComponentId *id = ids->data;
5301                 gchar *utf8_uid;
5302                 gchar *utf8_rid;
5303
5304                 ids = g_slist_next (ids);
5305
5306                 if (id->uid == NULL || *id->uid == '\0')
5307                         continue;
5308
5309                 utf8_uid = e_util_utf8_make_valid (id->uid);
5310                 if (id->rid != NULL)
5311                         utf8_rid = e_util_utf8_make_valid (id->rid);
5312                 else
5313                         utf8_rid = g_strdup ("");
5314
5315                 g_variant_builder_add (&builder, "(ss)", utf8_uid, utf8_rid);
5316
5317                 g_free (utf8_uid);
5318                 g_free (utf8_rid);
5319         }
5320
5321         success = e_dbus_calendar_call_remove_objects_sync (
5322                 client->priv->dbus_proxy,
5323                 g_variant_builder_end (&builder),
5324                 flags->str, cancellable, error);
5325
5326         g_type_class_unref (flags_class);
5327         g_string_free (flags, TRUE);
5328
5329         return success;
5330 }
5331
5332 /* Helper for e_cal_client_receive_objects() */
5333 static void
5334 cal_client_receive_objects_thread (GSimpleAsyncResult *simple,
5335                                    GObject *source_object,
5336                                    GCancellable *cancellable)
5337 {
5338         AsyncContext *async_context;
5339         GError *error = NULL;
5340
5341         async_context = g_simple_async_result_get_op_res_gpointer (simple);
5342
5343         e_cal_client_receive_objects_sync (
5344                 E_CAL_CLIENT (source_object),
5345                 async_context->in_comp,
5346                 cancellable, &error);
5347
5348         if (error != NULL)
5349                 g_simple_async_result_take_error (simple, error);
5350 }
5351
5352 /**
5353  * e_cal_client_receive_objects:
5354  * @client: an #ECalClient
5355  * @icalcomp: An #icalcomponent
5356  * @cancellable: a #GCancellable; can be %NULL
5357  * @callback: callback to call when a result is ready
5358  * @user_data: user data for the @callback
5359  *
5360  * Makes the backend receive the set of iCalendar objects specified in the
5361  * @icalcomp argument. This is used for iTIP confirmation/cancellation
5362  * messages for scheduled meetings.
5363  *
5364  * The call is finished by e_cal_client_receive_objects_finish() from
5365  * the @callback.
5366  *
5367  * Since: 3.2
5368  **/
5369 void
5370 e_cal_client_receive_objects (ECalClient *client,
5371                               icalcomponent *icalcomp,
5372                               GCancellable *cancellable,
5373                               GAsyncReadyCallback callback,
5374                               gpointer user_data)
5375 {
5376         GSimpleAsyncResult *simple;
5377         AsyncContext *async_context;
5378
5379         g_return_if_fail (E_IS_CAL_CLIENT (client));
5380         g_return_if_fail (icalcomp != NULL);
5381
5382         async_context = g_slice_new0 (AsyncContext);
5383         async_context->in_comp = icalcomponent_new_clone (icalcomp);
5384
5385         simple = g_simple_async_result_new (
5386                 G_OBJECT (client), callback, user_data,
5387                 e_cal_client_receive_objects);
5388
5389         g_simple_async_result_set_check_cancellable (simple, cancellable);
5390
5391         g_simple_async_result_set_op_res_gpointer (
5392                 simple, async_context, (GDestroyNotify) async_context_free);
5393
5394         g_simple_async_result_run_in_thread (
5395                 simple, cal_client_receive_objects_thread,
5396                 G_PRIORITY_DEFAULT, cancellable);
5397
5398         g_object_unref (simple);
5399 }
5400
5401 /**
5402  * e_cal_client_receive_objects_finish:
5403  * @client: an #ECalClient
5404  * @result: a #GAsyncResult
5405  * @error: (out): a #GError to set an error, if any
5406  *
5407  * Finishes previous call of e_cal_client_receive_objects().
5408  *
5409  * Returns: %TRUE if successful, %FALSE otherwise.
5410  *
5411  * Since: 3.2
5412  **/
5413 gboolean
5414 e_cal_client_receive_objects_finish (ECalClient *client,
5415                                      GAsyncResult *result,
5416                                      GError **error)
5417 {
5418         GSimpleAsyncResult *simple;
5419
5420         g_return_val_if_fail (
5421                 g_simple_async_result_is_valid (
5422                 result, G_OBJECT (client),
5423                 e_cal_client_receive_objects), FALSE);
5424
5425         simple = G_SIMPLE_ASYNC_RESULT (result);
5426
5427         /* Assume success unless a GError is set. */
5428         return !g_simple_async_result_propagate_error (simple, error);
5429 }
5430
5431 /**
5432  * e_cal_client_receive_objects_sync:
5433  * @client: an #ECalClient
5434  * @icalcomp: An #icalcomponent
5435  * @cancellable: a #GCancellable; can be %NULL
5436  * @error: (out): a #GError to set an error, if any
5437  *
5438  * Makes the backend receive the set of iCalendar objects specified in the
5439  * @icalcomp argument. This is used for iTIP confirmation/cancellation
5440  * messages for scheduled meetings.
5441  *
5442  * Returns: %TRUE if successful, %FALSE otherwise.
5443  *
5444  * Since: 3.2
5445  **/
5446 gboolean
5447 e_cal_client_receive_objects_sync (ECalClient *client,
5448                                    icalcomponent *icalcomp,
5449                                    GCancellable *cancellable,
5450                                    GError **error)
5451 {
5452         gchar *ical_string;
5453         gchar *utf8_ical_string;
5454         gboolean success;
5455
5456         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
5457
5458         ical_string = icalcomponent_as_ical_string_r (icalcomp);
5459         utf8_ical_string = e_util_utf8_make_valid (ical_string);
5460
5461         success = e_dbus_calendar_call_receive_objects_sync (
5462                 client->priv->dbus_proxy,
5463                 utf8_ical_string, cancellable, error);
5464
5465         g_free (utf8_ical_string);
5466         g_free (ical_string);
5467
5468         return success;
5469 }
5470
5471 /* Helper for e_cal_client_send_objects() */
5472 static void
5473 cal_client_send_objects_thread (GSimpleAsyncResult *simple,
5474                                 GObject *source_object,
5475                                 GCancellable *cancellable)
5476 {
5477         AsyncContext *async_context;
5478         GError *error = NULL;
5479
5480         async_context = g_simple_async_result_get_op_res_gpointer (simple);
5481
5482         e_cal_client_send_objects_sync (
5483                 E_CAL_CLIENT (source_object),
5484                 async_context->in_comp,
5485                 &async_context->string_list,
5486                 &async_context->out_comp,
5487                 cancellable, &error);
5488
5489         if (error != NULL)
5490                 g_simple_async_result_take_error (simple, error);
5491 }
5492
5493 /**
5494  * e_cal_client_send_objects:
5495  * @client: an #ECalClient
5496  * @icalcomp: An icalcomponent to be sent
5497  * @cancellable: a #GCancellable; can be %NULL
5498  * @callback: callback to call when a result is ready
5499  * @user_data: user data for the @callback
5500  *
5501  * Requests a calendar backend to send meeting information stored in @icalcomp.
5502  * The backend can modify this component and request a send to particular users.
5503  * The call is finished by e_cal_client_send_objects_finish() from
5504  * the @callback.
5505  *
5506  * Since: 3.2
5507  **/
5508 void
5509 e_cal_client_send_objects (ECalClient *client,
5510                            icalcomponent *icalcomp,
5511                            GCancellable *cancellable,
5512                            GAsyncReadyCallback callback,
5513                            gpointer user_data)
5514 {
5515         GSimpleAsyncResult *simple;
5516         AsyncContext *async_context;
5517
5518         g_return_if_fail (E_IS_CAL_CLIENT (client));
5519         g_return_if_fail (icalcomp != NULL);
5520
5521         async_context = g_slice_new0 (AsyncContext);
5522         async_context->in_comp = icalcomponent_new_clone (icalcomp);
5523
5524         simple = g_simple_async_result_new (
5525                 G_OBJECT (client), callback, user_data,
5526                 e_cal_client_send_objects);
5527
5528         g_simple_async_result_set_check_cancellable (simple, cancellable);
5529
5530         g_simple_async_result_set_op_res_gpointer (
5531                 simple, async_context, (GDestroyNotify) async_context_free);
5532
5533         g_simple_async_result_run_in_thread (
5534                 simple, cal_client_send_objects_thread,
5535                 G_PRIORITY_DEFAULT, cancellable);
5536
5537         g_object_unref (simple);
5538 }
5539
5540 /**
5541  * e_cal_client_send_objects_finish:
5542  * @client: an #ECalClient
5543  * @result: a #GAsyncResult
5544  * @out_users: (out) (element-type utf8): List of users to send
5545  *             the @out_modified_icalcomp to
5546  * @out_modified_icalcomp: (out): Return value for the icalcomponent to be sent
5547  * @error: (out): a #GError to set an error, if any
5548  *
5549  * Finishes previous call of e_cal_client_send_objects() and
5550  * populates @out_users with a list of users to send @out_modified_icalcomp to.
5551  *
5552  * The @out_users list should be freed with e_client_util_free_string_slist()
5553  * and the @out_modified_icalcomp should be freed with icalcomponent_free().
5554  *
5555  * Returns: %TRUE if successful, %FALSE otherwise.
5556  *
5557  * Since: 3.2
5558  **/
5559 gboolean
5560 e_cal_client_send_objects_finish (ECalClient *client,
5561                                   GAsyncResult *result,
5562                                   GSList **out_users,
5563                                   icalcomponent **out_modified_icalcomp,
5564                                   GError **error)
5565 {
5566         GSimpleAsyncResult *simple;
5567         AsyncContext *async_context;
5568
5569         g_return_val_if_fail (
5570                 g_simple_async_result_is_valid (
5571                 result, G_OBJECT (client),
5572                 e_cal_client_send_objects), FALSE);
5573
5574         simple = G_SIMPLE_ASYNC_RESULT (result);
5575         async_context = g_simple_async_result_get_op_res_gpointer (simple);
5576
5577         if (g_simple_async_result_propagate_error (simple, error))
5578                 return FALSE;
5579
5580         g_return_val_if_fail (async_context->out_comp != NULL, FALSE);
5581
5582         if (out_users != NULL) {
5583                 *out_users = async_context->string_list;
5584                 async_context->string_list = NULL;
5585         }
5586
5587         if (out_modified_icalcomp != NULL) {
5588                 *out_modified_icalcomp = async_context->out_comp;
5589                 async_context->out_comp = NULL;
5590         }
5591
5592         return TRUE;
5593 }
5594
5595 /**
5596  * e_cal_client_send_objects_sync:
5597  * @client: an #ECalClient
5598  * @icalcomp: An icalcomponent to be sent
5599  * @out_users: (out) (element-type utf8): List of users to send the
5600  *             @out_modified_icalcomp to
5601  * @out_modified_icalcomp: (out): Return value for the icalcomponent to be sent
5602  * @cancellable: (allow-none): a #GCancellable; can be %NULL
5603  * @error: (out): a #GError to set an error, if any
5604  *
5605  * Requests a calendar backend to send meeting information stored in @icalcomp.
5606  * The backend can modify this component and request a send to users in the
5607  * @out_users list.
5608  *
5609  * The @out_users list should be freed with e_client_util_free_string_slist()
5610  * and the @out_modified_icalcomp should be freed with icalcomponent_free().
5611  *
5612  * Returns: %TRUE if successful, %FALSE otherwise.
5613  *
5614  * Since: 3.2
5615  **/
5616 gboolean
5617 e_cal_client_send_objects_sync (ECalClient *client,
5618                                 icalcomponent *icalcomp,
5619                                 GSList **out_users,
5620                                 icalcomponent **out_modified_icalcomp,
5621                                 GCancellable *cancellable,
5622                                 GError **error)
5623 {
5624         gchar *ical_string;
5625         gchar *utf8_ical_string;
5626         gchar **users = NULL;
5627         gchar *out_ical_string = NULL;
5628         gboolean success;
5629
5630         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
5631         g_return_val_if_fail (icalcomp != NULL, FALSE);
5632         g_return_val_if_fail (out_users != NULL, FALSE);
5633         g_return_val_if_fail (out_modified_icalcomp != NULL, FALSE);
5634
5635         ical_string = icalcomponent_as_ical_string_r (icalcomp);
5636         utf8_ical_string = e_util_utf8_make_valid (ical_string);
5637
5638         success = e_dbus_calendar_call_send_objects_sync (
5639                 client->priv->dbus_proxy, utf8_ical_string,
5640                 &users, &out_ical_string, cancellable, error);
5641
5642         g_free (utf8_ical_string);
5643         g_free (ical_string);
5644
5645         /* Sanity check. */
5646         g_return_val_if_fail (
5647                 (success && (out_ical_string != NULL)) ||
5648                 (!success && (out_ical_string == NULL)), FALSE);
5649
5650         if (!success) {
5651                 g_warn_if_fail (users == NULL);
5652                 return FALSE;
5653         }
5654
5655         icalcomp = icalparser_parse_string (out_ical_string);
5656
5657         g_free (out_ical_string);
5658
5659         if (icalcomp != NULL) {
5660                 *out_modified_icalcomp = icalcomp;
5661         } else {
5662                 g_set_error_literal (
5663                         error, E_CAL_CLIENT_ERROR,
5664                         E_CAL_CLIENT_ERROR_INVALID_OBJECT,
5665                         e_cal_client_error_to_string (
5666                         E_CAL_CLIENT_ERROR_INVALID_OBJECT));
5667                 g_strfreev (users);
5668                 return FALSE;
5669         }
5670
5671         if (users != NULL) {
5672                 GSList *tmp = NULL;
5673                 gint ii;
5674
5675                 for (ii = 0; users[ii] != NULL; ii++) {
5676                         tmp = g_slist_prepend (tmp, users[ii]);
5677                         users[ii] = NULL;
5678                 }
5679
5680                 *out_users = g_slist_reverse (tmp);
5681         }
5682
5683         g_strfreev (users);
5684
5685         return TRUE;
5686 }
5687
5688 /* Helper for e_cal_client_get_attachment_uris() */
5689 static void
5690 cal_client_get_attachment_uris_thread (GSimpleAsyncResult *simple,
5691                                        GObject *source_object,
5692                                        GCancellable *cancellable)
5693 {
5694         AsyncContext *async_context;
5695         GError *error = NULL;
5696
5697         async_context = g_simple_async_result_get_op_res_gpointer (simple);
5698
5699         e_cal_client_get_attachment_uris_sync (
5700                 E_CAL_CLIENT (source_object),
5701                 async_context->uid,
5702                 async_context->rid,
5703                 &async_context->string_list,
5704                 cancellable, &error);
5705
5706         if (error != NULL)
5707                 g_simple_async_result_take_error (simple, error);
5708 }
5709
5710 /**
5711  * e_cal_client_get_attachment_uris:
5712  * @client: an #ECalClient
5713  * @uid: Unique identifier for a calendar component
5714  * @rid: Recurrence identifier
5715  * @cancellable: a #GCancellable; can be %NULL
5716  * @callback: callback to call when a result is ready
5717  * @user_data: user data for the @callback
5718  *
5719  * Queries a calendar for a specified component's object attachment uris.
5720  * The call is finished by e_cal_client_get_attachment_uris_finish() from
5721  * the @callback.
5722  *
5723  * Since: 3.2
5724  **/
5725 void
5726 e_cal_client_get_attachment_uris (ECalClient *client,
5727                                   const gchar *uid,
5728                                   const gchar *rid,
5729                                   GCancellable *cancellable,
5730                                   GAsyncReadyCallback callback,
5731                                   gpointer user_data)
5732 {
5733         GSimpleAsyncResult *simple;
5734         AsyncContext *async_context;
5735
5736         g_return_if_fail (E_CAL_CLIENT (client));
5737         g_return_if_fail (uid != NULL);
5738         /* rid is optional */
5739
5740         async_context = g_slice_new0 (AsyncContext);
5741         async_context->uid = g_strdup (uid);
5742         async_context->rid = g_strdup (rid);
5743
5744         simple = g_simple_async_result_new (
5745                 G_OBJECT (client), callback, user_data,
5746                 e_cal_client_get_attachment_uris);
5747
5748         g_simple_async_result_set_check_cancellable (simple, cancellable);
5749
5750         g_simple_async_result_set_op_res_gpointer (
5751                 simple, async_context, (GDestroyNotify) async_context_free);
5752
5753         g_simple_async_result_run_in_thread (
5754                 simple, cal_client_get_attachment_uris_thread,
5755                 G_PRIORITY_DEFAULT, cancellable);
5756
5757         g_object_unref (simple);
5758 }
5759
5760 /**
5761  * e_cal_client_get_attachment_uris_finish:
5762  * @client: an #ECalClient
5763  * @result: a #GAsyncResult
5764  * @out_attachment_uris: (out) (element-type utf8): Return location for the
5765  *                       list of attachment URIs
5766  * @error: (out): a #GError to set an error, if any
5767  *
5768  * Finishes previous call of e_cal_client_get_attachment_uris() and
5769  * sets @out_attachment_uris to uris for component's attachments.
5770  * The list should be freed with e_client_util_free_string_slist().
5771  *
5772  * Returns: %TRUE if successful, %FALSE otherwise.
5773  *
5774  * Since: 3.2
5775  **/
5776 gboolean
5777 e_cal_client_get_attachment_uris_finish (ECalClient *client,
5778                                          GAsyncResult *result,
5779                                          GSList **out_attachment_uris,
5780                                          GError **error)
5781 {
5782         GSimpleAsyncResult *simple;
5783         AsyncContext *async_context;
5784
5785         g_return_val_if_fail (
5786                 g_simple_async_result_is_valid (
5787                 result, G_OBJECT (client),
5788                 e_cal_client_get_attachment_uris), FALSE);
5789
5790         simple = G_SIMPLE_ASYNC_RESULT (result);
5791         async_context = g_simple_async_result_get_op_res_gpointer (simple);
5792
5793         if (g_simple_async_result_propagate_error (simple, error))
5794                 return FALSE;
5795
5796         if (out_attachment_uris != NULL) {
5797                 *out_attachment_uris = async_context->string_list;
5798                 async_context->string_list = NULL;
5799         }
5800
5801         return TRUE;
5802 }
5803
5804 /**
5805  * e_cal_client_get_attachment_uris_sync:
5806  * @client: an #ECalClient
5807  * @uid: Unique identifier for a calendar component
5808  * @rid: Recurrence identifier
5809  * @out_attachment_uris: (out) (element-type utf8): Return location for the
5810  *                       list of attachment URIs
5811  * @cancellable: (allow-none): a #GCancellable; can be %NULL
5812  * @error: (out): a #GError to set an error, if any
5813  *
5814  * Queries a calendar for a specified component's object attachment URIs.
5815  * The list should be freed with e_client_util_free_string_slist().
5816  *
5817  * Returns: %TRUE if successful, %FALSE otherwise.
5818  *
5819  * Since: 3.2
5820  **/
5821 gboolean
5822 e_cal_client_get_attachment_uris_sync (ECalClient *client,
5823                                        const gchar *uid,
5824                                        const gchar *rid,
5825                                        GSList **out_attachment_uris,
5826                                        GCancellable *cancellable,
5827                                        GError **error)
5828 {
5829         gchar *utf8_uid;
5830         gchar *utf8_rid;
5831         gchar **uris = NULL;
5832         gboolean success;
5833
5834         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
5835         g_return_val_if_fail (uid != NULL, FALSE);
5836         g_return_val_if_fail (out_attachment_uris != NULL, FALSE);
5837
5838         if (rid == NULL)
5839                 rid = "";
5840
5841         utf8_uid = e_util_utf8_make_valid (uid);
5842         utf8_rid = e_util_utf8_make_valid (rid);
5843
5844         success = e_dbus_calendar_call_get_attachment_uris_sync (
5845                 client->priv->dbus_proxy, utf8_uid,
5846                 utf8_rid, &uris, cancellable, error);
5847
5848         g_free (utf8_uid);
5849         g_free (utf8_rid);
5850
5851         /* Sanity check. */
5852         g_return_val_if_fail (
5853                 (success && (uris != NULL)) ||
5854                 (!success && (uris == NULL)), FALSE);
5855
5856         if (uris != NULL) {
5857                 GSList *tmp = NULL;
5858                 gint ii;
5859
5860                 for (ii = 0; uris[ii] != NULL; ii++) {
5861                         tmp = g_slist_prepend (tmp, uris[ii]);
5862                         uris[ii] = NULL;
5863                 }
5864
5865                 *out_attachment_uris = g_slist_reverse (tmp);
5866         }
5867
5868         return success;
5869 }
5870
5871 /* Helper for e_cal_client_discard_alarm() */
5872 static void
5873 cal_client_discard_alarm_thread (GSimpleAsyncResult *simple,
5874                                  GObject *source_object,
5875                                  GCancellable *cancellable)
5876 {
5877         AsyncContext *async_context;
5878         GError *error = NULL;
5879
5880         async_context = g_simple_async_result_get_op_res_gpointer (simple);
5881
5882         e_cal_client_discard_alarm_sync (
5883                 E_CAL_CLIENT (source_object),
5884                 async_context->uid,
5885                 async_context->rid,
5886                 async_context->auid,
5887                 cancellable, &error);
5888
5889         if (error != NULL)
5890                 g_simple_async_result_take_error (simple, error);
5891 }
5892
5893 /**
5894  * e_cal_client_discard_alarm:
5895  * @client: an #ECalClient
5896  * @uid: Unique identifier for a calendar component
5897  * @rid: Recurrence identifier
5898  * @auid: Alarm identifier to remove
5899  * @cancellable: a #GCancellable; can be %NULL
5900  * @callback: callback to call when a result is ready
5901  * @user_data: user data for the @callback
5902  *
5903  * Removes alarm @auid from a given component identified by @uid and @rid.
5904  * The call is finished by e_cal_client_discard_alarm_finish() from
5905  * the @callback.
5906  *
5907  * Since: 3.2
5908  **/
5909 void
5910 e_cal_client_discard_alarm (ECalClient *client,
5911                             const gchar *uid,
5912                             const gchar *rid,
5913                             const gchar *auid,
5914                             GCancellable *cancellable,
5915                             GAsyncReadyCallback callback,
5916                             gpointer user_data)
5917 {
5918         GSimpleAsyncResult *simple;
5919         AsyncContext *async_context;
5920
5921         g_return_if_fail (E_IS_CAL_CLIENT (client));
5922         g_return_if_fail (uid != NULL);
5923         /* rid is optional */
5924         g_return_if_fail (auid != NULL);
5925
5926         async_context = g_slice_new0 (AsyncContext);
5927         async_context->uid = g_strdup (uid);
5928         async_context->rid = NULL;
5929         async_context->auid = g_strdup (auid);
5930
5931         simple = g_simple_async_result_new (
5932                 G_OBJECT (client), callback, user_data,
5933                 e_cal_client_discard_alarm);
5934
5935         g_simple_async_result_set_check_cancellable (simple, cancellable);
5936
5937         g_simple_async_result_set_op_res_gpointer (
5938                 simple, async_context, (GDestroyNotify) async_context_free);
5939
5940         g_simple_async_result_run_in_thread (
5941                 simple, cal_client_discard_alarm_thread,
5942                 G_PRIORITY_DEFAULT, cancellable);
5943
5944         g_object_unref (simple);
5945 }
5946
5947 /**
5948  * e_cal_client_discard_alarm_finish:
5949  * @client: an #ECalClient
5950  * @result: a #GAsyncResult
5951  * @error: (out): a #GError to set an error, if any
5952  *
5953  * Finishes previous call of e_cal_client_discard_alarm().
5954  *
5955  * Returns: %TRUE if successful, %FALSE otherwise.
5956  *
5957  * Since: 3.2
5958  **/
5959 gboolean
5960 e_cal_client_discard_alarm_finish (ECalClient *client,
5961                                    GAsyncResult *result,
5962                                    GError **error)
5963 {
5964         GSimpleAsyncResult *simple;
5965
5966         g_return_val_if_fail (
5967                 g_simple_async_result_is_valid (
5968                 result, G_OBJECT (client),
5969                 e_cal_client_discard_alarm), FALSE);
5970
5971         simple = G_SIMPLE_ASYNC_RESULT (result);
5972
5973         /* Assume success unless a GError is set. */
5974         return !g_simple_async_result_propagate_error (simple, error);
5975 }
5976
5977 /**
5978  * e_cal_client_discard_alarm_sync:
5979  * @client: an #ECalClient
5980  * @uid: Unique identifier for a calendar component
5981  * @rid: Recurrence identifier
5982  * @auid: Alarm identifier to remove
5983  * @cancellable: a #GCancellable; can be %NULL
5984  * @error: (out): a #GError to set an error, if any
5985  *
5986  * Removes alarm @auid from a given component identified by @uid and @rid.
5987  *
5988  * Returns: %TRUE if successful, %FALSE otherwise.
5989  *
5990  * Since: 3.2
5991  **/
5992 gboolean
5993 e_cal_client_discard_alarm_sync (ECalClient *client,
5994                                  const gchar *uid,
5995                                  const gchar *rid,
5996                                  const gchar *auid,
5997                                  GCancellable *cancellable,
5998                                  GError **error)
5999 {
6000         gchar *utf8_uid;
6001         gchar *utf8_rid;
6002         gchar *utf8_auid;
6003         gboolean success;
6004
6005         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
6006         g_return_val_if_fail (uid != NULL, FALSE);
6007         g_return_val_if_fail (auid != NULL, FALSE);
6008
6009         if (rid == NULL)
6010                 rid = "";
6011
6012         utf8_uid = e_util_utf8_make_valid (uid);
6013         utf8_rid = e_util_utf8_make_valid (rid);
6014         utf8_auid = e_util_utf8_make_valid (auid);
6015
6016         success = e_dbus_calendar_call_discard_alarm_sync (
6017                 client->priv->dbus_proxy, utf8_uid,
6018                 utf8_rid, utf8_auid, cancellable, error);
6019
6020         g_free (utf8_uid);
6021         g_free (utf8_rid);
6022         g_free (utf8_auid);
6023
6024         return success;
6025 }
6026
6027 /* Helper for e_cal_client_get_view() */
6028 static void
6029 cal_client_get_view_in_dbus_thread (GSimpleAsyncResult *simple,
6030                                     GObject *source_object,
6031                                     GCancellable *cancellable)
6032 {
6033         ECalClient *client = E_CAL_CLIENT (source_object);
6034         AsyncContext *async_context;
6035         gchar *utf8_sexp;
6036         gchar *object_path = NULL;
6037         GError *error = NULL;
6038
6039         async_context = g_simple_async_result_get_op_res_gpointer (simple);
6040
6041         utf8_sexp = e_util_utf8_make_valid (async_context->sexp);
6042
6043         e_dbus_calendar_call_get_view_sync (
6044                 client->priv->dbus_proxy, utf8_sexp,
6045                 &object_path, cancellable, &error);
6046
6047         g_free (utf8_sexp);
6048
6049         /* Sanity check. */
6050         g_return_if_fail (
6051                 ((object_path != NULL) && (error == NULL)) ||
6052                 ((object_path == NULL) && (error != NULL)));
6053
6054         if (object_path != NULL) {
6055                 GDBusConnection *connection;
6056                 ECalClientView *client_view;
6057
6058                 connection = g_dbus_proxy_get_connection (
6059                         G_DBUS_PROXY (client->priv->dbus_proxy));
6060
6061                 client_view = g_initable_new (
6062                         E_TYPE_CAL_CLIENT_VIEW,
6063                         cancellable, &error,
6064                         "client", client,
6065                         "connection", connection,
6066                         "object-path", object_path,
6067                         NULL);
6068
6069                 /* Sanity check. */
6070                 g_return_if_fail (
6071                         ((client_view != NULL) && (error == NULL)) ||
6072                         ((client_view == NULL) && (error != NULL)));
6073
6074                 async_context->client_view = client_view;
6075
6076                 g_free (object_path);
6077         }
6078
6079         if (error != NULL)
6080                 g_simple_async_result_take_error (simple, error);
6081 }
6082
6083 /**
6084  * e_cal_client_get_view:
6085  * @client: an #ECalClient
6086  * @sexp: an S-expression representing the query.
6087  * @cancellable: a #GCancellable; can be %NULL
6088  * @callback: callback to call when a result is ready
6089  * @user_data: user data for the @callback
6090  *
6091  * Query @client with @sexp, creating an #ECalClientView.
6092  * The call is finished by e_cal_client_get_view_finish()
6093  * from the @callback.
6094  *
6095  * Since: 3.2
6096  **/
6097 void
6098 e_cal_client_get_view (ECalClient *client,
6099                        const gchar *sexp,
6100                        GCancellable *cancellable,
6101                        GAsyncReadyCallback callback,
6102                        gpointer user_data)
6103 {
6104         GSimpleAsyncResult *simple;
6105         AsyncContext *async_context;
6106
6107         g_return_if_fail (E_IS_CAL_CLIENT (client));
6108         g_return_if_fail (sexp != NULL);
6109
6110         async_context = g_slice_new0 (AsyncContext);
6111         async_context->sexp = g_strdup (sexp);
6112
6113         simple = g_simple_async_result_new (
6114                 G_OBJECT (client), callback, user_data,
6115                 e_cal_client_get_view);
6116
6117         g_simple_async_result_set_check_cancellable (simple, cancellable);
6118
6119         g_simple_async_result_set_op_res_gpointer (
6120                 simple, async_context, (GDestroyNotify) async_context_free);
6121
6122         cal_client_run_in_dbus_thread (
6123                 simple, cal_client_get_view_in_dbus_thread,
6124                 G_PRIORITY_DEFAULT, cancellable);
6125
6126         g_object_unref (simple);
6127 }
6128
6129 /**
6130  * e_cal_client_get_view_finish:
6131  * @client: an #ECalClient
6132  * @result: a #GAsyncResult
6133  * @out_view: (out) an #ECalClientView
6134  * @error: (out): a #GError to set an error, if any
6135  *
6136  * Finishes previous call of e_cal_client_get_view().
6137  * If successful, then the @out_view is set to newly allocated #ECalClientView,
6138  * which should be freed with g_object_unref().
6139  *
6140  * Returns: %TRUE if successful, %FALSE otherwise.
6141  *
6142  * Since: 3.2
6143  **/
6144 gboolean
6145 e_cal_client_get_view_finish (ECalClient *client,
6146                               GAsyncResult *result,
6147                               ECalClientView **out_view,
6148                               GError **error)
6149 {
6150         GSimpleAsyncResult *simple;
6151         AsyncContext *async_context;
6152
6153         g_return_val_if_fail (
6154                 g_simple_async_result_is_valid (
6155                 result, G_OBJECT (client),
6156                 e_cal_client_get_view), FALSE);
6157
6158         simple = G_SIMPLE_ASYNC_RESULT (result);
6159         async_context = g_simple_async_result_get_op_res_gpointer (simple);
6160
6161         if (g_simple_async_result_propagate_error (simple, error))
6162                 return FALSE;
6163
6164         g_return_val_if_fail (async_context->client_view != NULL, FALSE);
6165
6166         if (out_view != NULL)
6167                 *out_view = g_object_ref (async_context->client_view);
6168
6169         return TRUE;
6170 }
6171
6172 /**
6173  * e_cal_client_get_view_sync:
6174  * @client: an #ECalClient
6175  * @sexp: an S-expression representing the query.
6176  * @out_view: (out) an #ECalClientView
6177  * @cancellable: a #GCancellable; can be %NULL
6178  * @error: (out): a #GError to set an error, if any
6179  *
6180  * Query @client with @sexp, creating an #ECalClientView.
6181  * If successful, then the @out_view is set to newly allocated #ECalClientView,
6182  * which should be freed with g_object_unref().
6183  *
6184  * Returns: %TRUE if successful, %FALSE otherwise.
6185  *
6186  * Since: 3.2
6187  **/
6188 gboolean
6189 e_cal_client_get_view_sync (ECalClient *client,
6190                             const gchar *sexp,
6191                             ECalClientView **out_view,
6192                             GCancellable *cancellable,
6193                             GError **error)
6194 {
6195         EAsyncClosure *closure;
6196         GAsyncResult *result;
6197         gboolean success;
6198
6199         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
6200         g_return_val_if_fail (sexp != NULL, FALSE);
6201         g_return_val_if_fail (out_view != NULL, FALSE);
6202
6203         closure = e_async_closure_new ();
6204
6205         e_cal_client_get_view (
6206                 client, sexp, cancellable,
6207                 e_async_closure_callback, closure);
6208
6209         result = e_async_closure_wait (closure);
6210
6211         success = e_cal_client_get_view_finish (
6212                 client, result, out_view, error);
6213
6214         e_async_closure_free (closure);
6215
6216         return success;
6217 }
6218
6219 /* Helper for e_cal_client_get_timezone() */
6220 static void
6221 cal_client_get_timezone_thread (GSimpleAsyncResult *simple,
6222                                 GObject *source_object,
6223                                 GCancellable *cancellable)
6224 {
6225         AsyncContext *async_context;
6226         GError *error = NULL;
6227
6228         async_context = g_simple_async_result_get_op_res_gpointer (simple);
6229
6230         e_cal_client_get_timezone_sync (
6231                 E_CAL_CLIENT (source_object),
6232                 async_context->tzid,
6233                 &async_context->zone,
6234                 cancellable, &error);
6235
6236         if (error != NULL)
6237                 g_simple_async_result_take_error (simple, error);
6238 }
6239
6240 /**
6241  * e_cal_client_get_timezone:
6242  * @client: an #ECalClient
6243  * @tzid: ID of the timezone to retrieve
6244  * @cancellable: a #GCancellable; can be %NULL
6245  * @callback: callback to call when a result is ready
6246  * @user_data: user data for the @callback
6247  *
6248  * Retrieves a timezone object from the calendar backend.
6249  * The call is finished by e_cal_client_get_timezone_finish() from
6250  * the @callback.
6251  *
6252  * Since: 3.2
6253  **/
6254 void
6255 e_cal_client_get_timezone (ECalClient *client,
6256                            const gchar *tzid,
6257                            GCancellable *cancellable,
6258                            GAsyncReadyCallback callback,
6259                            gpointer user_data)
6260 {
6261         GSimpleAsyncResult *simple;
6262         AsyncContext *async_context;
6263
6264         g_return_if_fail (E_IS_CAL_CLIENT (client));
6265         g_return_if_fail (tzid != NULL);
6266
6267         async_context = g_slice_new0 (AsyncContext);
6268         async_context->tzid = g_strdup (tzid);
6269
6270         simple = g_simple_async_result_new (
6271                 G_OBJECT (client), callback, user_data,
6272                 e_cal_client_get_timezone);
6273
6274         g_simple_async_result_set_check_cancellable (simple, cancellable);
6275
6276         g_simple_async_result_set_op_res_gpointer (
6277                 simple, async_context, (GDestroyNotify) async_context_free);
6278
6279         g_simple_async_result_run_in_thread (
6280                 simple, cal_client_get_timezone_thread,
6281                 G_PRIORITY_DEFAULT, cancellable);
6282
6283         g_object_unref (simple);
6284 }
6285
6286 /**
6287  * e_cal_client_get_timezone_finish:
6288  * @client: an #ECalClient
6289  * @result: a #GAsyncResult
6290  * @out_zone: (out): Return value for the timezone
6291  * @error: (out): a #GError to set an error, if any
6292  *
6293  * Finishes previous call of e_cal_client_get_timezone() and
6294  * sets @out_zone to a retrieved timezone object from the calendar backend.
6295  * This object is owned by the @client, thus do not free it.
6296  *
6297  * Returns: %TRUE if successful, %FALSE otherwise.
6298  *
6299  * Since: 3.2
6300  **/
6301 gboolean
6302 e_cal_client_get_timezone_finish (ECalClient *client,
6303                                   GAsyncResult *result,
6304                                   icaltimezone **out_zone,
6305                                   GError **error)
6306 {
6307         GSimpleAsyncResult *simple;
6308         AsyncContext *async_context;
6309
6310         g_return_val_if_fail (
6311                 g_simple_async_result_is_valid (
6312                 result, G_OBJECT (client),
6313                 e_cal_client_get_timezone), FALSE);
6314
6315         simple = G_SIMPLE_ASYNC_RESULT (result);
6316         async_context = g_simple_async_result_get_op_res_gpointer (simple);
6317
6318         if (g_simple_async_result_propagate_error (simple, error))
6319                 return FALSE;
6320
6321         g_return_val_if_fail (async_context->zone != NULL, FALSE);
6322
6323         if (out_zone != NULL) {
6324                 *out_zone = async_context->zone;
6325                 async_context->zone = NULL;
6326         }
6327
6328         return TRUE;
6329 }
6330
6331 /**
6332  * e_cal_client_get_timezone_sync:
6333  * @client: an #ECalClient
6334  * @tzid: ID of the timezone to retrieve
6335  * @out_zone: (out): Return value for the timezone
6336  * @cancellable: a #GCancellable; can be %NULL
6337  * @error: (out): a #GError to set an error, if any
6338  *
6339  * Retrieves a timezone object from the calendar backend.
6340  * This object is owned by the @client, thus do not free it.
6341  *
6342  * Returns: %TRUE if successful, %FALSE otherwise.
6343  *
6344  * Since: 3.2
6345  **/
6346 gboolean
6347 e_cal_client_get_timezone_sync (ECalClient *client,
6348                                 const gchar *tzid,
6349                                 icaltimezone **out_zone,
6350                                 GCancellable *cancellable,
6351                                 GError **error)
6352 {
6353         icalcomponent *icalcomp;
6354         icaltimezone *zone;
6355         gchar *utf8_tzid;
6356         gchar *string = NULL;
6357         gboolean success;
6358
6359         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
6360         g_return_val_if_fail (tzid != NULL, FALSE);
6361         g_return_val_if_fail (out_zone != NULL, FALSE);
6362
6363         zone = e_timezone_cache_get_timezone (
6364                 E_TIMEZONE_CACHE (client), tzid);
6365         if (zone != NULL) {
6366                 *out_zone = zone;
6367                 return TRUE;
6368         }
6369
6370         utf8_tzid = e_util_utf8_make_valid (tzid);
6371
6372         success = e_dbus_calendar_call_get_timezone_sync (
6373                 client->priv->dbus_proxy, utf8_tzid,
6374                 &string, cancellable, error);
6375
6376         g_free (utf8_tzid);
6377
6378         /* Sanity check. */
6379         g_return_val_if_fail (
6380                 (success && (string != NULL)) ||
6381                 (!success && (string == NULL)), FALSE);
6382
6383         if (!success)
6384                 return FALSE;
6385
6386         icalcomp = icalparser_parse_string (string);
6387
6388         g_free (string);
6389
6390         if (icalcomp == NULL) {
6391                 g_set_error_literal (
6392                         error, E_CAL_CLIENT_ERROR,
6393                         E_CAL_CLIENT_ERROR_INVALID_OBJECT,
6394                         e_cal_client_error_to_string (
6395                         E_CAL_CLIENT_ERROR_INVALID_OBJECT));
6396                 return FALSE;
6397         }
6398
6399         zone = icaltimezone_new ();
6400         if (!icaltimezone_set_component (zone, icalcomp)) {
6401                 g_set_error_literal (
6402                         error, E_CAL_CLIENT_ERROR,
6403                         E_CAL_CLIENT_ERROR_INVALID_OBJECT,
6404                         e_cal_client_error_to_string (
6405                         E_CAL_CLIENT_ERROR_INVALID_OBJECT));
6406                 icalcomponent_free (icalcomp);
6407                 icaltimezone_free (zone, 1);
6408                 return FALSE;
6409         }
6410
6411         /* Add the timezone to the cache directly,
6412          * otherwise we'd have to free this struct
6413          * and fetch the cached copy. */
6414         g_mutex_lock (&client->priv->zone_cache_lock);
6415         g_hash_table_insert (
6416                 client->priv->zone_cache, g_strdup (tzid), zone);
6417         g_mutex_unlock (&client->priv->zone_cache_lock);
6418
6419         *out_zone = zone;
6420
6421         return TRUE;
6422 }
6423
6424 /* Helper for e_cal_client_add_timezone() */
6425 static void
6426 cal_client_add_timezone_thread (GSimpleAsyncResult *simple,
6427                                 GObject *source_object,
6428                                 GCancellable *cancellable)
6429 {
6430         AsyncContext *async_context;
6431         GError *error = NULL;
6432
6433         async_context = g_simple_async_result_get_op_res_gpointer (simple);
6434
6435         e_cal_client_add_timezone_sync (
6436                 E_CAL_CLIENT (source_object),
6437                 async_context->zone,
6438                 cancellable, &error);
6439
6440         if (error != NULL)
6441                 g_simple_async_result_take_error (simple, error);
6442 }
6443
6444 /**
6445  * e_cal_client_add_timezone:
6446  * @client: an #ECalClient
6447  * @zone: The timezone to add
6448  * @cancellable: a #GCancellable; can be %NULL
6449  * @callback: callback to call when a result is ready
6450  * @user_data: user data for the @callback
6451  *
6452  * Add a VTIMEZONE object to the given calendar client.
6453  * The call is finished by e_cal_client_add_timezone_finish() from
6454  * the @callback.
6455  *
6456  * Since: 3.2
6457  **/
6458 void
6459 e_cal_client_add_timezone (ECalClient *client,
6460                            icaltimezone *zone,
6461                            GCancellable *cancellable,
6462                            GAsyncReadyCallback callback,
6463                            gpointer user_data)
6464 {
6465         GSimpleAsyncResult *simple;
6466         AsyncContext *async_context;
6467         icalcomponent *icalcomp;
6468
6469         g_return_if_fail (E_IS_CAL_CLIENT (client));
6470         g_return_if_fail (zone != NULL);
6471
6472         icalcomp = icaltimezone_get_component (zone);
6473         g_return_if_fail (icalcomp != NULL);
6474
6475         async_context = g_slice_new0 (AsyncContext);
6476         async_context->zone = icaltimezone_new ();
6477
6478         icalcomp = icalcomponent_new_clone (icalcomp);
6479         icaltimezone_set_component (async_context->zone, icalcomp);
6480
6481         simple = g_simple_async_result_new (
6482                 G_OBJECT (client), callback, user_data,
6483                 e_cal_client_add_timezone);
6484
6485         g_simple_async_result_set_check_cancellable (simple, cancellable);
6486
6487         g_simple_async_result_set_op_res_gpointer (
6488                 simple, async_context, (GDestroyNotify) async_context_free);
6489
6490         if (zone == icaltimezone_get_utc_timezone ())
6491                 g_simple_async_result_complete_in_idle (simple);
6492         else
6493                 g_simple_async_result_run_in_thread (
6494                         simple, cal_client_add_timezone_thread,
6495                         G_PRIORITY_DEFAULT, cancellable);
6496
6497         g_object_unref (simple);
6498 }
6499
6500 /**
6501  * e_cal_client_add_timezone_finish:
6502  * @client: an #ECalClient
6503  * @result: a #GAsyncResult
6504  * @error: (out): a #GError to set an error, if any
6505  *
6506  * Finishes previous call of e_cal_client_add_timezone().
6507  *
6508  * Returns: %TRUE if successful, %FALSE otherwise.
6509  *
6510  * Since: 3.2
6511  **/
6512 gboolean
6513 e_cal_client_add_timezone_finish (ECalClient *client,
6514                                   GAsyncResult *result,
6515                                   GError **error)
6516 {
6517         GSimpleAsyncResult *simple;
6518
6519         g_return_val_if_fail (
6520                 g_simple_async_result_is_valid (
6521                 result, G_OBJECT (client),
6522                 e_cal_client_add_timezone), FALSE);
6523
6524         simple = G_SIMPLE_ASYNC_RESULT (result);
6525
6526         /* Assume success unless a GError is set. */
6527         return !g_simple_async_result_propagate_error (simple, error);
6528 }
6529
6530 /**
6531  * e_cal_client_add_timezone_sync:
6532  * @client: an #ECalClient
6533  * @zone: The timezone to add
6534  * @cancellable: a #GCancellable; can be %NULL
6535  * @error: (out): a #GError to set an error, if any
6536  *
6537  * Add a VTIMEZONE object to the given calendar client.
6538  *
6539  * Returns: %TRUE if successful, %FALSE otherwise.
6540  *
6541  * Since: 3.2
6542  **/
6543 gboolean
6544 e_cal_client_add_timezone_sync (ECalClient *client,
6545                                 icaltimezone *zone,
6546                                 GCancellable *cancellable,
6547                                 GError **error)
6548 {
6549         icalcomponent *icalcomp;
6550         gchar *zone_str;
6551         gchar *utf8_zone_str;
6552         gboolean success;
6553
6554         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
6555         g_return_val_if_fail (zone != NULL, FALSE);
6556
6557         if (zone == icaltimezone_get_utc_timezone ())
6558                 return TRUE;
6559
6560         icalcomp = icaltimezone_get_component (zone);
6561         if (icalcomp == NULL) {
6562                 g_propagate_error (
6563                         error, e_client_error_create (
6564                         E_CLIENT_ERROR_INVALID_ARG, NULL));
6565                 return FALSE;
6566         }
6567
6568         zone_str = icalcomponent_as_ical_string_r (icalcomp);
6569         utf8_zone_str = e_util_utf8_make_valid (zone_str);
6570
6571         success = e_dbus_calendar_call_add_timezone_sync (
6572                 client->priv->dbus_proxy, utf8_zone_str, cancellable, error);
6573
6574         g_free (zone_str);
6575         g_free (utf8_zone_str);
6576
6577         return success;
6578 }
6579