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