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