Make use of G_DEFINE_QUARK()
[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 #include <libedataserver/e-client-private.h>
30
31 #include "e-cal-client.h"
32 #include "e-cal-component.h"
33 #include "e-cal-check-timezones.h"
34 #include "e-cal-time-util.h"
35 #include "e-cal-types.h"
36 #include "e-timezone-cache.h"
37
38 #include "e-gdbus-cal.h"
39 #include "e-gdbus-cal-factory.h"
40
41 #define E_CAL_CLIENT_GET_PRIVATE(obj) \
42         (G_TYPE_INSTANCE_GET_PRIVATE \
43         ((obj), E_TYPE_CAL_CLIENT, ECalClientPrivate))
44
45 struct _ECalClientPrivate {
46         GDBusProxy *dbus_proxy;
47         guint gone_signal_id;
48
49         ECalClientSourceType source_type;
50         icaltimezone *default_zone;
51         gchar *cache_dir;
52
53         GMutex zone_cache_lock;
54         GHashTable *zone_cache;
55 };
56
57 enum {
58         FREE_BUSY_DATA,
59         LAST_SIGNAL
60 };
61
62 /* Forward Declarations */
63 static void     e_cal_client_timezone_cache_init
64                                         (ETimezoneCacheInterface *interface);
65
66 static guint signals[LAST_SIGNAL];
67
68 G_DEFINE_TYPE_WITH_CODE (
69         ECalClient,
70         e_cal_client,
71         E_TYPE_CLIENT,
72         G_IMPLEMENT_INTERFACE (
73                 E_TYPE_TIMEZONE_CACHE,
74                 e_cal_client_timezone_cache_init))
75
76 static void
77 free_zone_cb (gpointer zone)
78 {
79         icaltimezone_free (zone, 1);
80 }
81
82 /*
83  * Well-known calendar backend properties:
84  * @CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS: Contains default calendar's email
85  *   address suggested by the backend.
86  * @CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS: Contains default alarm email
87  *   address suggested by the backend.
88  * @CAL_BACKEND_PROPERTY_DEFAULT_OBJECT: Contains iCal component string
89  *   of an #icalcomponent with the default values for properties needed.
90  *   Preferred way of retrieving this property is by
91  *   calling e_cal_client_get_default_object().
92  *
93  * See also: @CLIENT_BACKEND_PROPERTY_OPENED, @CLIENT_BACKEND_PROPERTY_OPENING,
94  *   @CLIENT_BACKEND_PROPERTY_ONLINE, @CLIENT_BACKEND_PROPERTY_READONLY
95  *   @CLIENT_BACKEND_PROPERTY_CACHE_DIR, @CLIENT_BACKEND_PROPERTY_CAPABILITIES
96  */
97
98 G_DEFINE_QUARK (e-cal-client-error-quark, e_cal_client_error)
99
100 /**
101  * e_cal_client_error_to_string:
102  *
103  * FIXME: Document me.
104  *
105  * Since: 3.2
106  **/
107 const gchar *
108 e_cal_client_error_to_string (ECalClientError code)
109 {
110         switch (code) {
111         case E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR:
112                 return _("No such calendar");
113         case E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND:
114                 return _("Object not found");
115         case E_CAL_CLIENT_ERROR_INVALID_OBJECT:
116                 return _("Invalid object");
117         case E_CAL_CLIENT_ERROR_UNKNOWN_USER:
118                 return _("Unknown user");
119         case E_CAL_CLIENT_ERROR_OBJECT_ID_ALREADY_EXISTS:
120                 return _("Object ID already exists");
121         case E_CAL_CLIENT_ERROR_INVALID_RANGE:
122                 return _("Invalid range");
123         }
124
125         return _("Unknown error");
126 }
127
128 /**
129  * e_cal_client_error_create:
130  * @code: an #ECalClientError code to create
131  * @custom_msg: custom message to use for the error; can be %NULL
132  *
133  * Returns: a new #GError containing an E_CAL_CLIENT_ERROR of the given
134  * @code. If the @custom_msg is NULL, then the error message is
135  * the one returned from e_cal_client_error_to_string() for the @code,
136  * otherwise the given message is used.
137  *
138  * Returned pointer should be freed with g_error_free().
139  *
140  * Since: 3.2
141  **/
142 GError *
143 e_cal_client_error_create (ECalClientError code,
144                            const gchar *custom_msg)
145 {
146         return g_error_new_literal (E_CAL_CLIENT_ERROR, code, custom_msg ? custom_msg : e_cal_client_error_to_string (code));
147 }
148
149 /*
150  * If the specified GError is a remote error, then create a new error
151  * representing the remote error.  If the error is anything else, then
152  * leave it alone.
153  */
154 static gboolean
155 unwrap_dbus_error (GError *error,
156                    GError **client_error)
157 {
158         #define err(a,b) "org.gnome.evolution.dataserver.Calendar." a, b
159         static EClientErrorsList cal_errors[] = {
160                 { err ("Success",                               -1) },
161                 { err ("ObjectNotFound",                        E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND) },
162                 { err ("InvalidObject",                         E_CAL_CLIENT_ERROR_INVALID_OBJECT) },
163                 { err ("ObjectIdAlreadyExists",                 E_CAL_CLIENT_ERROR_OBJECT_ID_ALREADY_EXISTS) },
164                 { err ("NoSuchCal",                             E_CAL_CLIENT_ERROR_NO_SUCH_CALENDAR) },
165                 { err ("UnknownUser",                           E_CAL_CLIENT_ERROR_UNKNOWN_USER) },
166                 { err ("InvalidRange",                          E_CAL_CLIENT_ERROR_INVALID_RANGE) },
167         }, cl_errors[] = {
168                 { err ("Busy",                                  E_CLIENT_ERROR_BUSY) },
169                 { err ("InvalidArg",                            E_CLIENT_ERROR_INVALID_ARG) },
170                 { err ("RepositoryOffline",                     E_CLIENT_ERROR_REPOSITORY_OFFLINE) },
171                 { err ("OfflineUnavailable",                    E_CLIENT_ERROR_OFFLINE_UNAVAILABLE) },
172                 { err ("PermissionDenied",                      E_CLIENT_ERROR_PERMISSION_DENIED) },
173                 { err ("AuthenticationFailed",                  E_CLIENT_ERROR_AUTHENTICATION_FAILED) },
174                 { err ("AuthenticationRequired",                E_CLIENT_ERROR_AUTHENTICATION_REQUIRED) },
175                 { err ("CouldNotCancel",                        E_CLIENT_ERROR_COULD_NOT_CANCEL) },
176                 { err ("NotSupported",                          E_CLIENT_ERROR_NOT_SUPPORTED) },
177                 { err ("UnsupportedAuthenticationMethod",       E_CLIENT_ERROR_UNSUPPORTED_AUTHENTICATION_METHOD) },
178                 { err ("TLSNotAvailable",                       E_CLIENT_ERROR_TLS_NOT_AVAILABLE) },
179                 { err ("SearchSizeLimitExceeded",               E_CLIENT_ERROR_SEARCH_SIZE_LIMIT_EXCEEDED) },
180                 { err ("SearchTimeLimitExceeded",               E_CLIENT_ERROR_SEARCH_TIME_LIMIT_EXCEEDED) },
181                 { err ("InvalidQuery",                          E_CLIENT_ERROR_INVALID_QUERY) },
182                 { err ("QueryRefused",                          E_CLIENT_ERROR_QUERY_REFUSED) },
183                 { err ("NotOpened",                             E_CLIENT_ERROR_NOT_OPENED) },
184                 { err ("UnsupportedField",                      E_CLIENT_ERROR_OTHER_ERROR) },
185                 { err ("UnsupportedMethod",                     E_CLIENT_ERROR_OTHER_ERROR) },
186                 { err ("InvalidServerVersion",                  E_CLIENT_ERROR_OTHER_ERROR) },
187                 { err ("OtherError",                            E_CLIENT_ERROR_OTHER_ERROR) }
188         };
189         #undef err
190
191         if (error == NULL)
192                 return TRUE;
193
194         if (!e_client_util_unwrap_dbus_error (error, client_error, cal_errors, G_N_ELEMENTS (cal_errors), E_CAL_CLIENT_ERROR, TRUE))
195                 e_client_util_unwrap_dbus_error (error, client_error, cl_errors, G_N_ELEMENTS (cl_errors), E_CLIENT_ERROR, FALSE);
196
197         return FALSE;
198 }
199
200 static void
201 set_proxy_gone_error (GError **error)
202 {
203         /* do not translate this string, it should ideally never happen */
204         g_set_error_literal (error, E_CLIENT_ERROR, E_CLIENT_ERROR_DBUS_ERROR, "D-Bus calendar proxy gone");
205 }
206
207 static guint active_cal_clients = 0, cal_connection_closed_id = 0;
208 static EGdbusCalFactory *cal_factory = NULL;
209 static GRecMutex cal_factory_lock;
210 #define LOCK_FACTORY()   g_rec_mutex_lock (&cal_factory_lock)
211 #define UNLOCK_FACTORY() g_rec_mutex_unlock (&cal_factory_lock)
212
213 static void gdbus_cal_factory_closed_cb (GDBusConnection *connection, gboolean remote_peer_vanished, GError *error, gpointer user_data);
214
215 static void
216 gdbus_cal_factory_disconnect (GDBusConnection *connection)
217 {
218         LOCK_FACTORY ();
219
220         if (!connection && cal_factory)
221                 connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (cal_factory));
222
223         if (connection && cal_connection_closed_id) {
224                 g_dbus_connection_signal_unsubscribe (connection, cal_connection_closed_id);
225                 g_signal_handlers_disconnect_by_func (connection, gdbus_cal_factory_closed_cb, NULL);
226         }
227
228         if (cal_factory != NULL)
229                 g_object_unref (cal_factory);
230
231         cal_connection_closed_id = 0;
232         cal_factory = NULL;
233
234         UNLOCK_FACTORY ();
235 }
236
237 static void
238 gdbus_cal_factory_closed_cb (GDBusConnection *connection,
239                              gboolean remote_peer_vanished,
240                              GError *error,
241                              gpointer user_data)
242 {
243         GError *err = NULL;
244
245         LOCK_FACTORY ();
246
247         gdbus_cal_factory_disconnect (connection);
248
249         if (error)
250                 unwrap_dbus_error (g_error_copy (error), &err);
251
252         if (err) {
253                 g_debug ("GDBus connection is closed%s: %s", remote_peer_vanished ? ", remote peer vanished" : "", err->message);
254                 g_error_free (err);
255         } else if (active_cal_clients) {
256                 g_debug ("GDBus connection is closed%s", remote_peer_vanished ? ", remote peer vanished" : "");
257         }
258
259         UNLOCK_FACTORY ();
260 }
261
262 static void
263 gdbus_cal_factory_connection_gone_cb (GDBusConnection *connection,
264                                       const gchar *sender_name,
265                                       const gchar *object_path,
266                                       const gchar *interface_name,
267                                       const gchar *signal_name,
268                                       GVariant *parameters,
269                                       gpointer user_data)
270 {
271         /* signal subscription takes care of correct parameters,
272          * thus just do what is to be done here */
273         gdbus_cal_factory_closed_cb (connection, TRUE, NULL, user_data);
274 }
275
276 static gboolean
277 gdbus_cal_factory_activate (GCancellable *cancellable,
278                             GError **error)
279 {
280         GDBusConnection *connection;
281
282         LOCK_FACTORY ();
283
284         if (G_LIKELY (cal_factory != NULL)) {
285                 UNLOCK_FACTORY ();
286                 return TRUE;
287         }
288
289         cal_factory = e_gdbus_cal_factory_proxy_new_for_bus_sync (
290                 G_BUS_TYPE_SESSION,
291                 G_DBUS_PROXY_FLAGS_NONE,
292                 CALENDAR_DBUS_SERVICE_NAME,
293                 "/org/gnome/evolution/dataserver/CalendarFactory",
294                 cancellable, error);
295
296         if (cal_factory == NULL) {
297                 UNLOCK_FACTORY ();
298                 return FALSE;
299         }
300
301         connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (cal_factory));
302         cal_connection_closed_id = g_dbus_connection_signal_subscribe (
303                 connection,
304                 NULL,                                           /* sender */
305                 "org.freedesktop.DBus",                         /* interface */
306                 "NameOwnerChanged",                             /* member */
307                 "/org/freedesktop/DBus",                        /* object_path */
308                 "org.gnome.evolution.dataserver.Calendar",      /* arg0 */
309                 G_DBUS_SIGNAL_FLAGS_NONE,
310                 gdbus_cal_factory_connection_gone_cb, NULL, NULL);
311
312         g_signal_connect (
313                 connection, "closed",
314                 G_CALLBACK (gdbus_cal_factory_closed_cb), NULL);
315
316         UNLOCK_FACTORY ();
317
318         return TRUE;
319 }
320
321 static void gdbus_cal_client_disconnect (ECalClient *client);
322
323 /*
324  * Called when the calendar server dies.
325  */
326 static void
327 gdbus_cal_client_closed_cb (GDBusConnection *connection,
328                             gboolean remote_peer_vanished,
329                             GError *error,
330                             ECalClient *client)
331 {
332         GError *err = NULL;
333
334         g_assert (E_IS_CAL_CLIENT (client));
335
336         if (error)
337                 unwrap_dbus_error (g_error_copy (error), &err);
338
339         if (err) {
340                 g_debug (G_STRLOC ": ECalClient GDBus connection is closed%s: %s", remote_peer_vanished ? ", remote peer vanished" : "", err->message);
341                 g_error_free (err);
342         } else {
343                 g_debug (G_STRLOC ": ECalClient GDBus connection is closed%s", remote_peer_vanished ? ", remote peer vanished" : "");
344         }
345
346         gdbus_cal_client_disconnect (client);
347
348         e_client_emit_backend_died (E_CLIENT (client));
349 }
350
351 static void
352 gdbus_cal_client_connection_gone_cb (GDBusConnection *connection,
353                                      const gchar *sender_name,
354                                      const gchar *object_path,
355                                      const gchar *interface_name,
356                                      const gchar *signal_name,
357                                      GVariant *parameters,
358                                      gpointer user_data)
359 {
360         /* signal subscription takes care of correct parameters,
361          * thus just do what is to be done here */
362         gdbus_cal_client_closed_cb (connection, TRUE, NULL, user_data);
363 }
364
365 static void
366 gdbus_cal_client_disconnect (ECalClient *client)
367 {
368         g_return_if_fail (E_IS_CAL_CLIENT (client));
369
370         /* Ensure that everything relevant is NULL */
371         LOCK_FACTORY ();
372
373         if (client->priv->dbus_proxy != NULL) {
374                 GDBusConnection *connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (client->priv->dbus_proxy));
375
376                 g_signal_handlers_disconnect_by_func (connection, gdbus_cal_client_closed_cb, client);
377                 g_dbus_connection_signal_unsubscribe (connection, client->priv->gone_signal_id);
378                 client->priv->gone_signal_id = 0;
379
380                 e_gdbus_cal_call_close_sync (client->priv->dbus_proxy, NULL, NULL);
381                 g_object_unref (client->priv->dbus_proxy);
382                 client->priv->dbus_proxy = NULL;
383         }
384
385         UNLOCK_FACTORY ();
386 }
387
388 static void
389 backend_error_cb (EGdbusCal *object,
390                   const gchar *message,
391                   ECalClient *client)
392 {
393         g_return_if_fail (E_IS_CAL_CLIENT (client));
394         g_return_if_fail (message != NULL);
395
396         e_client_emit_backend_error (E_CLIENT (client), message);
397 }
398
399 static void
400 readonly_cb (EGdbusCal *object,
401              gboolean readonly,
402              ECalClient *client)
403 {
404         g_return_if_fail (E_IS_CAL_CLIENT (client));
405
406         e_client_set_readonly (E_CLIENT (client), readonly);
407 }
408
409 static void
410 online_cb (EGdbusCal *object,
411            gboolean is_online,
412            ECalClient *client)
413 {
414         g_return_if_fail (E_IS_CAL_CLIENT (client));
415
416         e_client_set_online (E_CLIENT (client), is_online);
417 }
418
419 static void
420 opened_cb (EGdbusCal *object,
421            const gchar * const *error_strv,
422            ECalClient *client)
423 {
424         GError *error = NULL;
425
426         g_return_if_fail (E_IS_CAL_CLIENT (client));
427         g_return_if_fail (error_strv != NULL);
428         g_return_if_fail (e_gdbus_templates_decode_error (error_strv, &error));
429
430         e_client_emit_opened (E_CLIENT (client), error);
431
432         if (error)
433                 g_error_free (error);
434 }
435
436 static void
437 free_busy_data_cb (EGdbusCal *object,
438                    const gchar * const *free_busy_strv,
439                    ECalClient *client)
440 {
441         GSList *ecalcomps = NULL;
442         gint ii;
443
444         g_return_if_fail (E_IS_CAL_CLIENT (client));
445         g_return_if_fail (free_busy_strv != NULL);
446
447         for (ii = 0; free_busy_strv[ii]; ii++) {
448                 ECalComponent *comp;
449                 icalcomponent *icalcomp;
450                 icalcomponent_kind kind;
451
452                 icalcomp = icalcomponent_new_from_string (free_busy_strv[ii]);
453                 if (!icalcomp)
454                         continue;
455
456                 kind = icalcomponent_isa (icalcomp);
457                 if (kind == ICAL_VFREEBUSY_COMPONENT) {
458                         comp = e_cal_component_new ();
459                         if (!e_cal_component_set_icalcomponent (comp, icalcomp)) {
460                                 icalcomponent_free (icalcomp);
461                                 g_object_unref (G_OBJECT (comp));
462                                 continue;
463                         }
464
465                         ecalcomps = g_slist_prepend (ecalcomps, comp);
466                 } else {
467                         icalcomponent_free (icalcomp);
468                 }
469         }
470
471         ecalcomps = g_slist_reverse (ecalcomps);
472
473         g_signal_emit (client, signals[FREE_BUSY_DATA], 0, ecalcomps);
474
475         e_client_util_free_object_slist (ecalcomps);
476 }
477
478 static void
479 backend_property_changed_cb (EGdbusCal *object,
480                              const gchar * const *name_value_strv,
481                              ECalClient *client)
482 {
483         gchar *prop_name = NULL, *prop_value = NULL;
484
485         g_return_if_fail (E_IS_CAL_CLIENT (client));
486         g_return_if_fail (name_value_strv != NULL);
487         g_return_if_fail (e_gdbus_templates_decode_two_strings (name_value_strv, &prop_name, &prop_value));
488         g_return_if_fail (prop_name != NULL);
489         g_return_if_fail (*prop_name);
490         g_return_if_fail (prop_value != NULL);
491
492         e_client_emit_backend_property_changed (E_CLIENT (client), prop_name, prop_value);
493
494         g_free (prop_name);
495         g_free (prop_value);
496 }
497
498 static EDataCalObjType
499 convert_type (ECalClientSourceType type)
500 {
501         switch (type) {
502         case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
503                 return Event;
504         case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
505                 return Todo;
506         case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
507                 return Journal;
508         default:
509                 return AnyType;
510         }
511
512         return AnyType;
513 }
514
515 /*
516  * Converts a GSList of icalcomponents into a NULL-terminated array of
517  * valid UTF-8 strings, suitable for sending over DBus.
518  */
519 static gchar **
520 icalcomponent_slist_to_utf8_icomp_array (GSList *icalcomponents)
521 {
522         gchar **array;
523         const GSList *l;
524         gint i = 0;
525
526         array = g_new0 (gchar *, g_slist_length (icalcomponents) + 1);
527         for (l = icalcomponents; l != NULL; l = l->next) {
528                 gchar *comp_str = icalcomponent_as_ical_string_r ((icalcomponent *) l->data);
529                 array[i++] = e_util_utf8_make_valid (comp_str);
530                 g_free (comp_str);
531         }
532
533         return array;
534 }
535
536 /*
537  * Converts a GSList of icalcomponents into a GSList of strings.
538  */
539 static GSList *
540 icalcomponent_slist_to_string_slist (GSList *icalcomponents)
541 {
542         GSList *strings = NULL;
543         const GSList *l;
544
545         for (l = icalcomponents; l != NULL; l = l->next) {
546                 strings = g_slist_prepend (strings, icalcomponent_as_ical_string_r ((icalcomponent *) l->data));
547         }
548
549         return g_slist_reverse (strings);
550 }
551
552 static gboolean
553 cal_client_get_backend_property_from_cache_finish (EClient *client,
554                                                    GAsyncResult *result,
555                                                    gchar **prop_value,
556                                                    GError **error)
557 {
558         GSimpleAsyncResult *simple;
559         GError *local_error = NULL;
560
561         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
562         g_return_val_if_fail (result != NULL, FALSE);
563         g_return_val_if_fail (prop_value != NULL, FALSE);
564         g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (client), cal_client_get_backend_property_from_cache_finish), FALSE);
565
566         simple = G_SIMPLE_ASYNC_RESULT (result);
567
568         if (g_simple_async_result_propagate_error (simple, &local_error)) {
569                 e_client_unwrap_dbus_error (client, local_error, error);
570                 return FALSE;
571         }
572
573         *prop_value = g_strdup (g_simple_async_result_get_op_res_gpointer (simple));
574
575         return *prop_value != NULL;
576 }
577
578 static void
579 cal_client_dispose (GObject *object)
580 {
581         EClient *client;
582
583         client = E_CLIENT (object);
584
585         e_client_cancel_all (client);
586
587         gdbus_cal_client_disconnect (E_CAL_CLIENT (client));
588
589         /* Chain up to parent's dispose() method. */
590         G_OBJECT_CLASS (e_cal_client_parent_class)->dispose (object);
591 }
592
593 static void
594 cal_client_finalize (GObject *object)
595 {
596         ECalClient *client;
597         ECalClientPrivate *priv;
598
599         client = E_CAL_CLIENT (object);
600
601         priv = client->priv;
602
603         g_free (priv->cache_dir);
604         priv->cache_dir = NULL;
605
606         if (priv->default_zone && priv->default_zone != icaltimezone_get_utc_timezone ())
607                 icaltimezone_free (priv->default_zone, 1);
608         priv->default_zone = NULL;
609
610         g_mutex_lock (&priv->zone_cache_lock);
611         g_hash_table_destroy (priv->zone_cache);
612         priv->zone_cache = NULL;
613         g_mutex_unlock (&priv->zone_cache_lock);
614         g_mutex_clear (&priv->zone_cache_lock);
615
616         /* Chain up to parent's finalize() method. */
617         G_OBJECT_CLASS (e_cal_client_parent_class)->finalize (object);
618
619         LOCK_FACTORY ();
620         active_cal_clients--;
621         if (!active_cal_clients)
622                 gdbus_cal_factory_disconnect (NULL);
623         UNLOCK_FACTORY ();
624 }
625
626 static GDBusProxy *
627 cal_client_get_dbus_proxy (EClient *client)
628 {
629         ECalClientPrivate *priv;
630
631         priv = E_CAL_CLIENT_GET_PRIVATE (client);
632
633         return priv->dbus_proxy;
634 }
635
636 static void
637 cal_client_unwrap_dbus_error (EClient *client,
638                               GError *dbus_error,
639                               GError **out_error)
640 {
641         unwrap_dbus_error (dbus_error, out_error);
642 }
643
644 static void
645 cal_client_retrieve_capabilities (EClient *client,
646                                   GCancellable *cancellable,
647                                   GAsyncReadyCallback callback,
648                                   gpointer user_data)
649 {
650         g_return_if_fail (E_IS_CAL_CLIENT (client));
651
652         e_client_get_backend_property (client, CLIENT_BACKEND_PROPERTY_CAPABILITIES, cancellable, callback, user_data);
653 }
654
655 static gboolean
656 cal_client_retrieve_capabilities_finish (EClient *client,
657                                          GAsyncResult *result,
658                                          gchar **capabilities,
659                                          GError **error)
660 {
661         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
662
663         return e_client_get_backend_property_finish (client, result, capabilities, error);
664 }
665
666 static gboolean
667 cal_client_retrieve_capabilities_sync (EClient *client,
668                                        gchar **capabilities,
669                                        GCancellable *cancellable,
670                                        GError **error)
671 {
672         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
673
674         return e_client_get_backend_property_sync (client, CLIENT_BACKEND_PROPERTY_CAPABILITIES, capabilities, cancellable, error);
675 }
676
677 static void
678 cal_client_get_backend_property (EClient *client,
679                                  const gchar *prop_name,
680                                  GCancellable *cancellable,
681                                  GAsyncReadyCallback callback,
682                                  gpointer user_data)
683 {
684         gchar *prop_value;
685
686         prop_value = e_client_get_backend_property_from_cache (client, prop_name);
687         if (prop_value) {
688                 e_client_finish_async_without_dbus (client, cancellable, callback, user_data, cal_client_get_backend_property_from_cache_finish, prop_value, g_free);
689         } else {
690                 e_client_proxy_call_string_with_res_op_data (
691                         client, prop_name, cancellable, callback, user_data, cal_client_get_backend_property, prop_name,
692                         e_gdbus_cal_call_get_backend_property,
693                         NULL, NULL, e_gdbus_cal_call_get_backend_property_finish, NULL, NULL);
694         }
695 }
696
697 static gboolean
698 cal_client_get_backend_property_finish (EClient *client,
699                                         GAsyncResult *result,
700                                         gchar **prop_value,
701                                         GError **error)
702 {
703         gchar *str = NULL;
704         gboolean res;
705
706         g_return_val_if_fail (prop_value != NULL, FALSE);
707
708         if (g_simple_async_result_get_source_tag (G_SIMPLE_ASYNC_RESULT (result)) == cal_client_get_backend_property_from_cache_finish) {
709                 res = cal_client_get_backend_property_from_cache_finish (client, result, &str, error);
710         } else {
711                 res = e_client_proxy_call_finish_string (client, result, &str, error, cal_client_get_backend_property);
712                 if (res && str) {
713                         const gchar *prop_name = g_object_get_data (G_OBJECT (result), "res-op-data");
714
715                         if (prop_name && *prop_name)
716                                 e_client_update_backend_property_cache (client, prop_name, str);
717                 }
718         }
719
720         *prop_value = str;
721
722         return res;
723 }
724
725 static gboolean
726 cal_client_get_backend_property_sync (EClient *client,
727                                       const gchar *prop_name,
728                                       gchar **prop_value,
729                                       GCancellable *cancellable,
730                                       GError **error)
731 {
732         ECalClient *cal_client;
733         gchar *prop_val;
734         gboolean res;
735
736         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
737
738         cal_client = E_CAL_CLIENT (client);
739
740         if (cal_client->priv->dbus_proxy == NULL) {
741                 set_proxy_gone_error (error);
742                 return FALSE;
743         }
744
745         prop_val = e_client_get_backend_property_from_cache (client, prop_name);
746         if (prop_val) {
747                 g_return_val_if_fail (prop_value != NULL, FALSE);
748
749                 *prop_value = prop_val;
750
751                 return TRUE;
752         }
753
754         res = e_client_proxy_call_sync_string__string (client, prop_name, prop_value, cancellable, error, e_gdbus_cal_call_get_backend_property_sync);
755
756         if (res && prop_value)
757                 e_client_update_backend_property_cache (client, prop_name, *prop_value);
758
759         return res;
760 }
761
762 static void
763 cal_client_set_backend_property (EClient *client,
764                                  const gchar *prop_name,
765                                  const gchar *prop_value,
766                                  GCancellable *cancellable,
767                                  GAsyncReadyCallback callback,
768                                  gpointer user_data)
769 {
770         gchar **prop_name_value;
771
772         prop_name_value = e_gdbus_cal_encode_set_backend_property (prop_name, prop_value);
773
774         e_client_proxy_call_strv (
775                 client, (const gchar * const *) prop_name_value, cancellable, callback, user_data, cal_client_set_backend_property,
776                 e_gdbus_cal_call_set_backend_property,
777                 e_gdbus_cal_call_set_backend_property_finish, NULL, NULL, NULL, NULL);
778
779         g_strfreev (prop_name_value);
780 }
781
782 static gboolean
783 cal_client_set_backend_property_finish (EClient *client,
784                                         GAsyncResult *result,
785                                         GError **error)
786 {
787         return e_client_proxy_call_finish_void (client, result, error, cal_client_set_backend_property);
788 }
789
790 static gboolean
791 cal_client_set_backend_property_sync (EClient *client,
792                                       const gchar *prop_name,
793                                       const gchar *prop_value,
794                                       GCancellable *cancellable,
795                                       GError **error)
796 {
797         ECalClient *cal_client;
798         gboolean res;
799         gchar **prop_name_value;
800
801         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
802
803         cal_client = E_CAL_CLIENT (client);
804
805         if (cal_client->priv->dbus_proxy == NULL) {
806                 set_proxy_gone_error (error);
807                 return FALSE;
808         }
809
810         prop_name_value = e_gdbus_cal_encode_set_backend_property (prop_name, prop_value);
811         res = e_client_proxy_call_sync_strv__void (client, (const gchar * const *) prop_name_value, cancellable, error, e_gdbus_cal_call_set_backend_property_sync);
812         g_strfreev (prop_name_value);
813
814         return res;
815 }
816
817 static void
818 cal_client_open (EClient *client,
819                  gboolean only_if_exists,
820                  GCancellable *cancellable,
821                  GAsyncReadyCallback callback,
822                  gpointer user_data)
823 {
824         e_client_proxy_call_boolean (
825                 client, only_if_exists, cancellable, callback, user_data, cal_client_open,
826                 e_gdbus_cal_call_open,
827                 e_gdbus_cal_call_open_finish, NULL, NULL, NULL, NULL);
828 }
829
830 static gboolean
831 cal_client_open_finish (EClient *client,
832                         GAsyncResult *result,
833                         GError **error)
834 {
835         return e_client_proxy_call_finish_void (client, result, error, cal_client_open);
836 }
837
838 static gboolean
839 cal_client_open_sync (EClient *client,
840                       gboolean only_if_exists,
841                       GCancellable *cancellable,
842                       GError **error)
843 {
844         ECalClient *cal_client;
845
846         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
847
848         cal_client = E_CAL_CLIENT (client);
849
850         if (cal_client->priv->dbus_proxy == NULL) {
851                 set_proxy_gone_error (error);
852                 return FALSE;
853         }
854
855         return e_client_proxy_call_sync_boolean__void (client, only_if_exists, cancellable, error, e_gdbus_cal_call_open_sync);
856 }
857
858 static void
859 cal_client_refresh (EClient *client,
860                     GCancellable *cancellable,
861                     GAsyncReadyCallback callback,
862                     gpointer user_data)
863 {
864         e_client_proxy_call_void (
865                 client, cancellable, callback, user_data, cal_client_refresh,
866                 e_gdbus_cal_call_refresh,
867                 e_gdbus_cal_call_refresh_finish, NULL, NULL, NULL, NULL);
868 }
869
870 static gboolean
871 cal_client_refresh_finish (EClient *client,
872                            GAsyncResult *result,
873                            GError **error)
874 {
875         return e_client_proxy_call_finish_void (client, result, error, cal_client_refresh);
876 }
877
878 static gboolean
879 cal_client_refresh_sync (EClient *client,
880                          GCancellable *cancellable,
881                          GError **error)
882 {
883         ECalClient *cal_client;
884
885         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
886
887         cal_client = E_CAL_CLIENT (client);
888
889         if (cal_client->priv->dbus_proxy == NULL) {
890                 set_proxy_gone_error (error);
891                 return FALSE;
892         }
893
894         return e_client_proxy_call_sync_void__void (client, cancellable, error, e_gdbus_cal_call_refresh_sync);
895 }
896
897 static void
898 cal_client_add_cached_timezone (ETimezoneCache *cache,
899                                 icaltimezone *zone)
900 {
901         ECalClientPrivate *priv;
902         const gchar *tzid;
903         gboolean timezone_added = FALSE;
904
905         priv = E_CAL_CLIENT_GET_PRIVATE (cache);
906
907         g_mutex_lock (&priv->zone_cache_lock);
908
909         tzid = icaltimezone_get_tzid (zone);
910
911         /* Avoid replacing an existing cache entry.  We don't want to
912          * invalidate any icaltimezone pointers that may have already
913          * been returned through e_timezone_cache_get_timezone(). */
914         if (!g_hash_table_contains (priv->zone_cache, tzid)) {
915                 icalcomponent *icalcomp;
916                 icaltimezone *cached_zone;
917
918                 cached_zone = icaltimezone_new ();
919                 icalcomp = icaltimezone_get_component (zone);
920                 icalcomp = icalcomponent_new_clone (icalcomp);
921                 icaltimezone_set_component (cached_zone, icalcomp);
922
923                 g_hash_table_insert (
924                         priv->zone_cache,
925                         g_strdup (tzid), cached_zone);
926
927                 timezone_added = TRUE;
928         }
929
930         g_mutex_unlock (&priv->zone_cache_lock);
931
932         /* FIXME Should emit this from an idle GSource on
933          *       a stored GMainContext, but we don't have
934          *       a stored GMainContext.  Check back after
935          *       the D-Bus API rewrite. */
936         if (timezone_added)
937                 g_signal_emit_by_name (cache, "timezone-added", zone);
938 }
939
940 static icaltimezone *
941 cal_client_get_cached_timezone (ETimezoneCache *cache,
942                                 const gchar *tzid)
943 {
944         ECalClientPrivate *priv;
945         icaltimezone *zone = NULL;
946         icaltimezone *builtin_zone = NULL;
947         icalcomponent *icalcomp;
948         icalproperty *prop;
949         const gchar *builtin_tzid;
950
951         priv = E_CAL_CLIENT_GET_PRIVATE (cache);
952
953         if (g_str_equal (tzid, "UTC"))
954                 return icaltimezone_get_utc_timezone ();
955
956         g_mutex_lock (&priv->zone_cache_lock);
957
958         /* See if we already have it in the cache. */
959         zone = g_hash_table_lookup (priv->zone_cache, tzid);
960
961         if (zone != NULL)
962                 goto exit;
963
964         /* Try to replace the original time zone with a more complete
965          * and/or potentially updated built-in time zone.  Note this also
966          * applies to TZIDs which match built-in time zones exactly: they
967          * are extracted via icaltimezone_get_builtin_timezone_from_tzid()
968          * below without a roundtrip to the backend. */
969
970         builtin_tzid = e_cal_match_tzid (tzid);
971
972         if (builtin_tzid != NULL)
973                 builtin_zone = icaltimezone_get_builtin_timezone_from_tzid (
974                         builtin_tzid);
975
976         if (builtin_zone == NULL)
977                 goto exit;
978
979         /* Use the built-in time zone *and* rename it.  Likely the caller
980          * is asking for a specific TZID because it has an event with such
981          * a TZID.  Returning an icaltimezone with a different TZID would
982          * lead to broken VCALENDARs in the caller. */
983
984         icalcomp = icaltimezone_get_component (builtin_zone);
985         icalcomp = icalcomponent_new_clone (icalcomp);
986
987         prop = icalcomponent_get_first_property (
988                 icalcomp, ICAL_ANY_PROPERTY);
989
990         while (prop != NULL) {
991                 if (icalproperty_isa (prop) == ICAL_TZID_PROPERTY) {
992                         icalproperty_set_value_from_string (prop, tzid, "NO");
993                         break;
994                 }
995
996                 prop = icalcomponent_get_next_property (
997                         icalcomp, ICAL_ANY_PROPERTY);
998         }
999
1000         if (icalcomp != NULL) {
1001                 zone = icaltimezone_new ();
1002                 if (icaltimezone_set_component (zone, icalcomp)) {
1003                         tzid = icaltimezone_get_tzid (zone);
1004                         g_hash_table_insert (
1005                                 priv->zone_cache,
1006                                 g_strdup (tzid), zone);
1007                 } else {
1008                         icalcomponent_free (icalcomp);
1009                         icaltimezone_free (zone, 1);
1010                         zone = NULL;
1011                 }
1012         }
1013
1014 exit:
1015         g_mutex_unlock (&priv->zone_cache_lock);
1016
1017         return zone;
1018 }
1019
1020 static GList *
1021 cal_client_list_cached_timezones (ETimezoneCache *cache)
1022 {
1023         ECalClientPrivate *priv;
1024         GList *list;
1025
1026         priv = E_CAL_CLIENT_GET_PRIVATE (cache);
1027
1028         g_mutex_lock (&priv->zone_cache_lock);
1029
1030         list = g_hash_table_get_values (priv->zone_cache);
1031
1032         g_mutex_unlock (&priv->zone_cache_lock);
1033
1034         return list;
1035 }
1036
1037 static void
1038 e_cal_client_class_init (ECalClientClass *class)
1039 {
1040         GObjectClass *object_class;
1041         EClientClass *client_class;
1042
1043         g_type_class_add_private (class, sizeof (ECalClientPrivate));
1044
1045         object_class = G_OBJECT_CLASS (class);
1046         object_class->dispose = cal_client_dispose;
1047         object_class->finalize = cal_client_finalize;
1048
1049         client_class = E_CLIENT_CLASS (class);
1050         client_class->get_dbus_proxy                    = cal_client_get_dbus_proxy;
1051         client_class->unwrap_dbus_error                 = cal_client_unwrap_dbus_error;
1052         client_class->retrieve_capabilities             = cal_client_retrieve_capabilities;
1053         client_class->retrieve_capabilities_finish      = cal_client_retrieve_capabilities_finish;
1054         client_class->retrieve_capabilities_sync        = cal_client_retrieve_capabilities_sync;
1055         client_class->get_backend_property              = cal_client_get_backend_property;
1056         client_class->get_backend_property_finish       = cal_client_get_backend_property_finish;
1057         client_class->get_backend_property_sync         = cal_client_get_backend_property_sync;
1058         client_class->set_backend_property              = cal_client_set_backend_property;
1059         client_class->set_backend_property_finish       = cal_client_set_backend_property_finish;
1060         client_class->set_backend_property_sync         = cal_client_set_backend_property_sync;
1061         client_class->open                              = cal_client_open;
1062         client_class->open_finish                       = cal_client_open_finish;
1063         client_class->open_sync                         = cal_client_open_sync;
1064         client_class->refresh                           = cal_client_refresh;
1065         client_class->refresh_finish                    = cal_client_refresh_finish;
1066         client_class->refresh_sync                      = cal_client_refresh_sync;
1067
1068         signals[FREE_BUSY_DATA] = g_signal_new (
1069                 "free-busy-data",
1070                 G_OBJECT_CLASS_TYPE (class),
1071                 G_SIGNAL_RUN_FIRST,
1072                 G_STRUCT_OFFSET (ECalClientClass, free_busy_data),
1073                 NULL, NULL,
1074                 g_cclosure_marshal_VOID__POINTER,
1075                 G_TYPE_NONE, 1,
1076                 G_TYPE_POINTER);
1077 }
1078
1079 static void
1080 e_cal_client_timezone_cache_init (ETimezoneCacheInterface *interface)
1081 {
1082         interface->add_timezone = cal_client_add_cached_timezone;
1083         interface->get_timezone = cal_client_get_cached_timezone;
1084         interface->list_timezones = cal_client_list_cached_timezones;
1085 }
1086
1087 static void
1088 e_cal_client_init (ECalClient *client)
1089 {
1090         GHashTable *zone_cache;
1091
1092         zone_cache = g_hash_table_new_full (
1093                 (GHashFunc) g_str_hash,
1094                 (GEqualFunc) g_str_equal,
1095                 (GDestroyNotify) g_free,
1096                 (GDestroyNotify) free_zone_cb);
1097
1098         LOCK_FACTORY ();
1099         active_cal_clients++;
1100         UNLOCK_FACTORY ();
1101
1102         client->priv = E_CAL_CLIENT_GET_PRIVATE (client);
1103         client->priv->source_type = E_CAL_CLIENT_SOURCE_TYPE_LAST;
1104         client->priv->default_zone = icaltimezone_get_utc_timezone ();
1105         g_mutex_init (&client->priv->zone_cache_lock);
1106         client->priv->zone_cache = zone_cache;
1107 }
1108
1109 /**
1110  * e_cal_client_new:
1111  * @source: An #ESource pointer
1112  * @source_type: source type of the calendar
1113  * @error: A #GError pointer
1114  *
1115  * Creates a new #ECalClient corresponding to the given source.  There are
1116  * only two operations that are valid on this calendar at this point:
1117  * e_client_open(), and e_client_remove().
1118  *
1119  * Returns: a new but unopened #ECalClient.
1120  *
1121  * Since: 3.2
1122  **/
1123 ECalClient *
1124 e_cal_client_new (ESource *source,
1125                   ECalClientSourceType source_type,
1126                   GError **error)
1127 {
1128         ECalClient *client;
1129         GError *err = NULL;
1130         GDBusConnection *connection;
1131         const gchar *uid;
1132         gchar **strv;
1133         gchar *object_path = NULL;
1134
1135         g_return_val_if_fail (E_IS_SOURCE (source), NULL);
1136         g_return_val_if_fail (
1137                 source_type == E_CAL_CLIENT_SOURCE_TYPE_EVENTS ||
1138                 source_type == E_CAL_CLIENT_SOURCE_TYPE_TASKS ||
1139                 source_type == E_CAL_CLIENT_SOURCE_TYPE_MEMOS, NULL);
1140
1141         LOCK_FACTORY ();
1142         /* XXX Oops, e_cal_client_new() forgot to take a GCancellable. */
1143         if (!gdbus_cal_factory_activate (NULL, &err)) {
1144                 UNLOCK_FACTORY ();
1145                 if (err) {
1146                         unwrap_dbus_error (err, &err);
1147                         g_warning ("%s: Failed to run calendar factory: %s", G_STRFUNC, err->message);
1148                         g_propagate_error (error, err);
1149                 } else {
1150                         g_warning ("%s: Failed to run calendar factory: Unknown error", G_STRFUNC);
1151                         g_set_error_literal (error, E_CLIENT_ERROR, E_CLIENT_ERROR_DBUS_ERROR, _("Failed to run calendar factory"));
1152                 }
1153
1154                 return NULL;
1155         }
1156
1157         uid = e_source_get_uid (source);
1158         strv = e_gdbus_cal_factory_encode_get_cal (uid, convert_type (source_type));
1159         if (!strv) {
1160                 g_set_error_literal (error, E_CLIENT_ERROR, E_CLIENT_ERROR_OTHER_ERROR, _("Other error"));
1161                 return NULL;
1162         }
1163
1164         client = g_object_new (E_TYPE_CAL_CLIENT, "source", source, NULL);
1165         client->priv->source_type = source_type;
1166
1167         UNLOCK_FACTORY ();
1168
1169         e_gdbus_cal_factory_call_get_cal_sync (
1170                 G_DBUS_PROXY (cal_factory),
1171                 (const gchar * const *) strv,
1172                 &object_path, NULL, &err);
1173
1174         g_strfreev (strv);
1175
1176         /* Sanity check. */
1177         g_return_val_if_fail (
1178                 ((object_path != NULL) && (err == NULL)) ||
1179                 ((object_path == NULL) && (err != NULL)), NULL);
1180
1181         if (err != NULL) {
1182                 unwrap_dbus_error (err, &err);
1183                 g_propagate_error (error, err);
1184                 g_object_unref (client);
1185                 return NULL;
1186         }
1187
1188         connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (cal_factory));
1189
1190         client->priv->dbus_proxy = G_DBUS_PROXY (e_gdbus_cal_proxy_new_sync (
1191                 connection,
1192                 G_DBUS_PROXY_FLAGS_NONE,
1193                 CALENDAR_DBUS_SERVICE_NAME,
1194                 object_path,
1195                 NULL, &err));
1196
1197         g_free (object_path);
1198
1199         /* Sanity check. */
1200         g_return_val_if_fail (
1201                 ((object_path != NULL) && (err == NULL)) ||
1202                 ((object_path == NULL) && (err != NULL)), NULL);
1203
1204         if (err != NULL) {
1205                 unwrap_dbus_error (err, &err);
1206                 g_propagate_error (error, err);
1207                 g_object_unref (client);
1208                 return NULL;
1209         }
1210
1211         client->priv->gone_signal_id = g_dbus_connection_signal_subscribe (
1212                 connection,
1213                 "org.freedesktop.DBus",                         /* sender */
1214                 "org.freedesktop.DBus",                         /* interface */
1215                 "NameOwnerChanged",                             /* member */
1216                 "/org/freedesktop/DBus",                        /* object_path */
1217                 "org.gnome.evolution.dataserver.Calendar",      /* arg0 */
1218                 G_DBUS_SIGNAL_FLAGS_NONE,
1219                 gdbus_cal_client_connection_gone_cb, client, NULL);
1220
1221         g_signal_connect (
1222                 connection, "closed",
1223                 G_CALLBACK (gdbus_cal_client_closed_cb), client);
1224
1225         g_signal_connect (
1226                 client->priv->dbus_proxy, "backend_error",
1227                 G_CALLBACK (backend_error_cb), client);
1228
1229         g_signal_connect (
1230                 client->priv->dbus_proxy, "readonly",
1231                 G_CALLBACK (readonly_cb), client);
1232
1233         g_signal_connect (
1234                 client->priv->dbus_proxy, "online",
1235                 G_CALLBACK (online_cb), client);
1236
1237         g_signal_connect (
1238                 client->priv->dbus_proxy, "opened",
1239                 G_CALLBACK (opened_cb), client);
1240
1241         g_signal_connect (
1242                 client->priv->dbus_proxy, "free-busy-data",
1243                 G_CALLBACK (free_busy_data_cb), client);
1244
1245         g_signal_connect (
1246                 client->priv->dbus_proxy, "backend-property-changed",
1247                 G_CALLBACK (backend_property_changed_cb), client);
1248
1249         return client;
1250 }
1251
1252 /**
1253  * e_cal_client_get_source_type:
1254  * @client: A calendar client.
1255  *
1256  * Gets the source type of the calendar client.
1257  *
1258  * Returns: an #ECalClientSourceType value corresponding
1259  * to the source type of the calendar client.
1260  *
1261  * Since: 3.2
1262  **/
1263 ECalClientSourceType
1264 e_cal_client_get_source_type (ECalClient *client)
1265 {
1266         g_return_val_if_fail (E_IS_CAL_CLIENT (client), E_CAL_CLIENT_SOURCE_TYPE_LAST);
1267
1268         return client->priv->source_type;
1269 }
1270
1271 /**
1272  * e_cal_client_get_local_attachment_store:
1273  * @client: A calendar client.
1274  *
1275  * Queries the URL where the calendar attachments are
1276  * serialized in the local filesystem. This enable clients
1277  * to operate with the reference to attachments rather than the data itself
1278  * unless it specifically uses the attachments for open/sending
1279  * operations.
1280  *
1281  * Returns: The URL where the attachments are serialized in the
1282  * local filesystem.
1283  *
1284  * Since: 3.2
1285  **/
1286 const gchar *
1287 e_cal_client_get_local_attachment_store (ECalClient *client)
1288 {
1289         gchar *cache_dir = NULL;
1290         GError *error = NULL;
1291
1292         g_return_val_if_fail (E_IS_CAL_CLIENT (client), NULL);
1293
1294         if (client->priv->cache_dir || !client->priv->dbus_proxy)
1295                 return client->priv->cache_dir;
1296
1297         cache_dir = e_client_get_backend_property_from_cache (E_CLIENT (client), CLIENT_BACKEND_PROPERTY_CACHE_DIR);
1298         if (!cache_dir)
1299                 e_gdbus_cal_call_get_backend_property_sync (client->priv->dbus_proxy, CLIENT_BACKEND_PROPERTY_CACHE_DIR, &cache_dir, NULL, &error);
1300
1301         if (error == NULL) {
1302                 client->priv->cache_dir = cache_dir;
1303         } else {
1304                 unwrap_dbus_error (error, &error);
1305                 g_warning ("%s", error->message);
1306                 g_error_free (error);
1307         }
1308
1309         return client->priv->cache_dir;
1310 }
1311
1312 /* icaltimezone_copy does a shallow copy while icaltimezone_free tries to free the entire 
1313  * the contents inside the structure with libical 0.43. Use this, till eds allows older libical.
1314 */
1315 static icaltimezone *
1316 copy_timezone (icaltimezone *ozone)
1317 {
1318         icaltimezone *zone = NULL;
1319         const gchar *tzid;
1320
1321         tzid = icaltimezone_get_tzid (ozone);
1322
1323         if (g_strcmp0 (tzid, "UTC") != 0) {
1324                 icalcomponent *comp;
1325
1326                 comp = icaltimezone_get_component (ozone);
1327                 if (comp) {
1328                         zone = icaltimezone_new ();
1329                         icaltimezone_set_component (zone, icalcomponent_new_clone (comp));
1330                 }
1331         }
1332
1333         if (!zone)
1334                 zone = icaltimezone_get_utc_timezone ();
1335
1336         return zone;
1337 }
1338
1339 /**
1340  * e_cal_client_set_default_timezone:
1341  * @client: A calendar client.
1342  * @zone: A timezone object.
1343  *
1344  * Sets the default timezone to use to resolve DATE and floating DATE-TIME
1345  * values. This will typically be from the user's timezone setting. Call this
1346  * before using any other object fetching functions.
1347  *
1348  * Since: 3.2
1349  **/
1350 void
1351 e_cal_client_set_default_timezone (ECalClient *client, /* const */ icaltimezone *zone)
1352 {
1353         g_return_if_fail (E_IS_CAL_CLIENT (client));
1354         g_return_if_fail (zone != NULL);
1355
1356         if (client->priv->default_zone != icaltimezone_get_utc_timezone ())
1357                 icaltimezone_free (client->priv->default_zone, 1);
1358
1359         if (zone == icaltimezone_get_utc_timezone ())
1360                 client->priv->default_zone = zone;
1361         else
1362                 client->priv->default_zone = copy_timezone (zone);
1363 }
1364
1365 /**
1366  * e_cal_client_get_default_timezone:
1367  * @client: A calendar client.
1368  *
1369  * Returns: Default timezone previously set with e_cal_client_set_default_timezone().
1370  * Returned pointer is owned by the @client and should not be freed.
1371  *
1372  * Since: 3.2
1373  **/
1374 /* const */ icaltimezone *
1375 e_cal_client_get_default_timezone (ECalClient *client)
1376 {
1377         g_return_val_if_fail (E_IS_CAL_CLIENT (client), NULL);
1378
1379         return client->priv->default_zone;
1380 }
1381
1382 /**
1383  * e_cal_client_check_one_alarm_only:
1384  * @client: A calendar client.
1385  *
1386  * Checks if a calendar supports only one alarm per component.
1387  *
1388  * Returns: TRUE if the calendar allows only one alarm, FALSE otherwise.
1389  *
1390  * Since: 3.2
1391  **/
1392 gboolean
1393 e_cal_client_check_one_alarm_only (ECalClient *client)
1394 {
1395         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
1396
1397         return e_client_check_capability (E_CLIENT (client), CAL_STATIC_CAPABILITY_ONE_ALARM_ONLY);
1398 }
1399
1400 /**
1401  * e_cal_client_check_save_schedules:
1402  * @client: A calendar client.
1403  *
1404  * Checks whether the calendar saves schedules.
1405  *
1406  * Returns: TRUE if it saves schedules, FALSE otherwise.
1407  *
1408  * Since: 3.2
1409  **/
1410 gboolean
1411 e_cal_client_check_save_schedules (ECalClient *client)
1412 {
1413         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
1414
1415         return e_client_check_capability (E_CLIENT (client), CAL_STATIC_CAPABILITY_SAVE_SCHEDULES);
1416 }
1417
1418 /**
1419  * e_cal_client_check_organizer_must_attend:
1420  * @client: A calendar client.
1421  *
1422  * Checks if a calendar forces organizers of meetings to be also attendees.
1423  *
1424  * Returns: TRUE if the calendar forces organizers to attend meetings,
1425  * FALSE otherwise.
1426  *
1427  * Since: 3.2
1428  **/
1429 gboolean
1430 e_cal_client_check_organizer_must_attend (ECalClient *client)
1431 {
1432         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
1433
1434         return e_client_check_capability (E_CLIENT (client), CAL_STATIC_CAPABILITY_ORGANIZER_MUST_ATTEND);
1435 }
1436
1437 /**
1438  * e_cal_client_check_organizer_must_accept:
1439  * @client: A calendar client.
1440  *
1441  * Checks whether a calendar requires organizer to accept their attendance to
1442  * meetings.
1443  *
1444  * Returns: TRUE if the calendar requires organizers to accept, FALSE
1445  * otherwise.
1446  *
1447  * Since: 3.2
1448  **/
1449 gboolean
1450 e_cal_client_check_organizer_must_accept (ECalClient *client)
1451 {
1452         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
1453
1454         return e_client_check_capability (E_CLIENT (client), CAL_STATIC_CAPABILITY_ORGANIZER_MUST_ACCEPT);
1455 }
1456
1457 /**
1458  * e_cal_client_check_recurrences_no_master:
1459  * @client: A calendar client.
1460  *
1461  * Checks if the calendar has a master object for recurrences.
1462  *
1463  * Returns: TRUE if the calendar has a master object for recurrences,
1464  * FALSE otherwise.
1465  *
1466  * Since: 3.2
1467  **/
1468 gboolean
1469 e_cal_client_check_recurrences_no_master (ECalClient *client)
1470 {
1471         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
1472
1473         return e_client_check_capability (E_CLIENT (client), CAL_STATIC_CAPABILITY_RECURRENCES_NO_MASTER);
1474 }
1475
1476 /**
1477  * e_cal_client_free_icalcomp_slist:
1478  * @icalcomps: (element-type icalcomponent): list of icalcomponent objects
1479  *
1480  * Frees each element of the @icalcomps list and the list itself.
1481  * Each element is an object of type #icalcomponent.
1482  *
1483  * Since: 3.2
1484  **/
1485 void
1486 e_cal_client_free_icalcomp_slist (GSList *icalcomps)
1487 {
1488         g_slist_foreach (icalcomps, (GFunc) icalcomponent_free, NULL);
1489         g_slist_free (icalcomps);
1490 }
1491
1492 /**
1493  * e_cal_client_free_ecalcomp_slist:
1494  * @ecalcomps: (element-type ECalComponent): list of #ECalComponent objects
1495  *
1496  * Frees each element of the @ecalcomps list and the list itself.
1497  * Each element is an object of type #ECalComponent.
1498  *
1499  * Since: 3.2
1500  **/
1501 void
1502 e_cal_client_free_ecalcomp_slist (GSList *ecalcomps)
1503 {
1504         g_slist_foreach (ecalcomps, (GFunc) g_object_unref, NULL);
1505         g_slist_free (ecalcomps);
1506 }
1507
1508 /**
1509  * e_cal_client_resolve_tzid_cb:
1510  * @tzid: ID of the timezone to resolve.
1511  * @data: Closure data for the callback, in this case #ECalClient.
1512  *
1513  * Resolves TZIDs for the recurrence generator.
1514  *
1515  * Returns: The timezone identified by the @tzid argument, or %NULL if
1516  * it could not be found.
1517  *
1518  * Since: 3.2
1519  */
1520 icaltimezone *
1521 e_cal_client_resolve_tzid_cb (const gchar *tzid,
1522                               gpointer data)
1523 {
1524         ECalClient *client = data;
1525         icaltimezone *zone = NULL;
1526         GError *error = NULL;
1527
1528         g_return_val_if_fail (E_IS_CAL_CLIENT (client), NULL);
1529
1530         e_cal_client_get_timezone_sync (client, tzid, &zone, NULL, &error);
1531
1532         if (error) {
1533                 g_debug ("%s: Failed to find '%s' timezone: %s", G_STRFUNC, tzid, error->message);
1534                 g_error_free (error);
1535         }
1536
1537         return zone;
1538 }
1539
1540 struct comp_instance {
1541         ECalComponent *comp;
1542         time_t start;
1543         time_t end;
1544 };
1545
1546 struct instances_info {
1547         GSList **instances;
1548         icaltimezone *start_zone;
1549         icaltimezone *end_zone;
1550 };
1551
1552 /* Called from cal_recur_generate_instances(); adds an instance to the list */
1553 static gboolean
1554 add_instance (ECalComponent *comp,
1555               time_t start,
1556               time_t end,
1557               gpointer data)
1558 {
1559         GSList **list;
1560         struct comp_instance *ci;
1561         icalcomponent *icalcomp;
1562         struct instances_info *instances_hold;
1563
1564         instances_hold = data;
1565         list = instances_hold->instances;
1566
1567         ci = g_new (struct comp_instance, 1);
1568
1569         icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
1570
1571         /* add the instance to the list */
1572         ci->comp = e_cal_component_new ();
1573         e_cal_component_set_icalcomponent (ci->comp, icalcomp);
1574
1575         /* make sure we return an instance */
1576         if (e_cal_util_component_has_recurrences (icalcomp) &&
1577             !(icalcomponent_get_first_property (icalcomp, ICAL_RECURRENCEID_PROPERTY))) {
1578                 ECalComponentRange *range;
1579                 struct icaltimetype itt;
1580                 ECalComponentDateTime dtstart, dtend;
1581
1582                 /* update DTSTART */
1583                 dtstart.value = NULL;
1584                 dtstart.tzid = NULL;
1585
1586                 e_cal_component_get_dtstart (comp, &dtstart);
1587
1588                 if (instances_hold->start_zone) {
1589                         itt = icaltime_from_timet_with_zone (start, dtstart.value && dtstart.value->is_date, instances_hold->start_zone);
1590                         g_free ((gchar *) dtstart.tzid);
1591                         dtstart.tzid = g_strdup (icaltimezone_get_tzid (instances_hold->start_zone));
1592                 } else {
1593                         itt = icaltime_from_timet (start, dtstart.value && dtstart.value->is_date);
1594                         if (dtstart.tzid) {
1595                                 g_free ((gchar *) dtstart.tzid);
1596                                 dtstart.tzid = NULL;
1597                         }
1598                 }
1599
1600                 g_free (dtstart.value);
1601                 dtstart.value = &itt;
1602                 e_cal_component_set_dtstart (ci->comp, &dtstart);
1603
1604                 /* set the RECUR-ID for the instance */
1605                 range = g_new0 (ECalComponentRange, 1);
1606                 range->type = E_CAL_COMPONENT_RANGE_SINGLE;
1607                 range->datetime = dtstart;
1608
1609                 e_cal_component_set_recurid (ci->comp, range);
1610
1611                 g_free (range);
1612                 g_free ((gchar *) dtstart.tzid);
1613
1614                 /* Update DTEND */
1615                 dtend.value = NULL;
1616                 dtend.tzid = NULL;
1617
1618                 e_cal_component_get_dtend (comp, &dtend);
1619
1620                 if (instances_hold->end_zone) {
1621                         itt = icaltime_from_timet_with_zone (end, dtend.value && dtend.value->is_date, instances_hold->end_zone);
1622                         g_free ((gchar *) dtend.tzid);
1623                         dtend.tzid = g_strdup (icaltimezone_get_tzid (instances_hold->end_zone));
1624                 } else {
1625                         itt = icaltime_from_timet (end, dtend.value && dtend.value->is_date);
1626                         if (dtend.tzid) {
1627                                 g_free ((gchar *) dtend.tzid);
1628                                 dtend.tzid = NULL;
1629                         }
1630                 }
1631
1632                 g_free (dtend.value);
1633                 dtend.value = &itt;
1634                 e_cal_component_set_dtend (ci->comp, &dtend);
1635
1636                 g_free ((gchar *) dtend.tzid);
1637         }
1638
1639         ci->start = start;
1640         ci->end = end;
1641
1642         *list = g_slist_prepend (*list, ci);
1643
1644         return TRUE;
1645 }
1646
1647 /* Used from g_slist_sort(); compares two struct comp_instance structures */
1648 static gint
1649 compare_comp_instance (gconstpointer a,
1650                        gconstpointer b)
1651 {
1652         const struct comp_instance *cia, *cib;
1653         time_t diff;
1654
1655         cia = a;
1656         cib = b;
1657
1658         diff = cia->start - cib->start;
1659         return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
1660 }
1661
1662 static GSList *
1663 process_detached_instances (GSList *instances,
1664                             GSList *detached_instances)
1665 {
1666         struct comp_instance *ci, *cid;
1667         GSList *dl, *unprocessed_instances = NULL;
1668
1669         for (dl = detached_instances; dl != NULL; dl = dl->next) {
1670                 GSList *il;
1671                 const gchar *uid;
1672                 gboolean processed;
1673                 ECalComponentRange recur_id, instance_recur_id;
1674
1675                 processed = FALSE;
1676                 recur_id.type = E_CAL_COMPONENT_RANGE_SINGLE;
1677                 instance_recur_id.type = E_CAL_COMPONENT_RANGE_SINGLE;
1678
1679                 cid = dl->data;
1680                 e_cal_component_get_uid (cid->comp, &uid);
1681                 e_cal_component_get_recurid (cid->comp, &recur_id);
1682
1683                 /* search for coincident instances already expanded */
1684                 for (il = instances; il != NULL; il = il->next) {
1685                         const gchar *instance_uid;
1686                         gint cmp;
1687
1688                         ci = il->data;
1689                         e_cal_component_get_uid (ci->comp, &instance_uid);
1690                         e_cal_component_get_recurid (ci->comp, &instance_recur_id);
1691                         if (strcmp (uid, instance_uid) == 0) {
1692                                 gchar *i_rid = NULL, *d_rid = NULL;
1693
1694                                 i_rid = e_cal_component_get_recurid_as_string (ci->comp);
1695                                 d_rid = e_cal_component_get_recurid_as_string (cid->comp);
1696
1697                                 if (i_rid && d_rid && strcmp (i_rid, d_rid) == 0) {
1698                                         g_object_unref (ci->comp);
1699                                         ci->comp = g_object_ref (cid->comp);
1700                                         ci->start = cid->start;
1701                                         ci->end = cid->end;
1702
1703                                         processed = TRUE;
1704                                 } else {
1705                                         if (!instance_recur_id.datetime.value ||
1706                                             !recur_id.datetime.value) {
1707                                                 /*
1708                                                  * Prevent obvious segfault by ignoring missing
1709                                                  * recurrency ids. Real problem might be elsewhere,
1710                                                  * but anything is better than crashing...
1711                                                  */
1712                                                 g_log (
1713                                                         G_LOG_DOMAIN,
1714                                                         G_LOG_LEVEL_CRITICAL,
1715                                                         "UID %s: instance RECURRENCE-ID %s + detached instance RECURRENCE-ID %s: cannot compare",
1716                                                         uid,
1717                                                         i_rid,
1718                                                         d_rid);
1719
1720                                                 e_cal_component_free_datetime (&instance_recur_id.datetime);
1721                                                 g_free (i_rid);
1722                                                 g_free (d_rid);
1723                                                 continue;
1724                                         }
1725                                         cmp = icaltime_compare (
1726                                                 *instance_recur_id.datetime.value,
1727                                                 *recur_id.datetime.value);
1728                                         if ((recur_id.type == E_CAL_COMPONENT_RANGE_THISPRIOR && cmp <= 0) ||
1729                                                 (recur_id.type == E_CAL_COMPONENT_RANGE_THISFUTURE && cmp >= 0)) {
1730                                                 ECalComponent *comp;
1731
1732                                                 comp = e_cal_component_new ();
1733                                                 e_cal_component_set_icalcomponent (
1734                                                         comp,
1735                                                         icalcomponent_new_clone (e_cal_component_get_icalcomponent (cid->comp)));
1736                                                 e_cal_component_set_recurid (comp, &instance_recur_id);
1737
1738                                                 /* replace the generated instances */
1739                                                 g_object_unref (ci->comp);
1740                                                 ci->comp = comp;
1741                                         }
1742                                 }
1743                                 g_free (i_rid);
1744                                 g_free (d_rid);
1745                         }
1746                         e_cal_component_free_datetime (&instance_recur_id.datetime);
1747                 }
1748
1749                 e_cal_component_free_datetime (&recur_id.datetime);
1750
1751                 if (!processed)
1752                         unprocessed_instances = g_slist_prepend (unprocessed_instances, cid);
1753         }
1754
1755         /* add the unprocessed instances (ie, detached instances with no master object */
1756         while (unprocessed_instances != NULL) {
1757                 cid = unprocessed_instances->data;
1758                 ci = g_new0 (struct comp_instance, 1);
1759                 ci->comp = g_object_ref (cid->comp);
1760                 ci->start = cid->start;
1761                 ci->end = cid->end;
1762                 instances = g_slist_append (instances, ci);
1763
1764                 unprocessed_instances = g_slist_remove (unprocessed_instances, cid);
1765         }
1766
1767         return instances;
1768 }
1769
1770 static void
1771 generate_instances (ECalClient *client,
1772                     time_t start,
1773                     time_t end,
1774                     GSList *objects,
1775                     GCancellable *cancellable,
1776                     ECalRecurInstanceFn cb,
1777                     gpointer cb_data)
1778 {
1779         GSList *instances, *detached_instances = NULL;
1780         GSList *l;
1781         ECalClientPrivate *priv;
1782
1783         priv = client->priv;
1784
1785         instances = NULL;
1786
1787         for (l = objects; l && !g_cancellable_is_cancelled (cancellable); l = l->next) {
1788                 ECalComponent *comp;
1789                 icaltimezone *default_zone;
1790
1791                 if (priv->default_zone)
1792                         default_zone = priv->default_zone;
1793                 else
1794                         default_zone = icaltimezone_get_utc_timezone ();
1795
1796                 comp = l->data;
1797                 if (e_cal_component_is_instance (comp)) {
1798                         struct comp_instance *ci;
1799                         ECalComponentDateTime dtstart, dtend;
1800                         icaltimezone *start_zone = NULL, *end_zone = NULL;
1801
1802                         /* keep the detached instances apart */
1803                         ci = g_new0 (struct comp_instance, 1);
1804                         ci->comp = g_object_ref (comp);
1805
1806                         e_cal_component_get_dtstart (comp, &dtstart);
1807                         e_cal_component_get_dtend (comp, &dtend);
1808
1809                         /* For DATE-TIME values with a TZID, we use
1810                          * e_cal_resolve_tzid_cb to resolve the TZID.
1811                          * For DATE values and DATE-TIME values without a
1812                          * TZID (i.e. floating times) we use the default
1813                          * timezone. */
1814                         if (dtstart.tzid && dtstart.value && !dtstart.value->is_date) {
1815                                 start_zone = e_cal_client_resolve_tzid_cb (dtstart.tzid, client);
1816                                 if (!start_zone)
1817                                         start_zone = default_zone;
1818                         } else {
1819                                 start_zone = default_zone;
1820                         }
1821
1822                         if (dtend.tzid && dtend.value && !dtend.value->is_date) {
1823                                 end_zone = e_cal_client_resolve_tzid_cb (dtend.tzid, client);
1824                                 if (!end_zone)
1825                                         end_zone = default_zone;
1826                         } else {
1827                                 end_zone = default_zone;
1828                         }
1829
1830                         ci->start = icaltime_as_timet_with_zone (*dtstart.value, start_zone);
1831
1832                         if (dtend.value)
1833                                 ci->end = icaltime_as_timet_with_zone (*dtend.value, end_zone);
1834                         else if (icaltime_is_date (*dtstart.value))
1835                                 ci->end = time_day_end (ci->start);
1836                         else
1837                                 ci->end = ci->start;
1838
1839                         e_cal_component_free_datetime (&dtstart);
1840                         e_cal_component_free_datetime (&dtend);
1841
1842                         if (ci->start <= end && ci->end >= start) {
1843                                 detached_instances = g_slist_prepend (detached_instances, ci);
1844                         } else {
1845                                 /* it doesn't fit to our time range, thus skip it */
1846                                 g_object_unref (G_OBJECT (ci->comp));
1847                                 g_free (ci);
1848                         }
1849                 } else {
1850                         ECalComponentDateTime datetime;
1851                         icaltimezone *start_zone = NULL, *end_zone = NULL;
1852                         struct instances_info *instances_hold;
1853
1854                         /* Get the start timezone */
1855                         e_cal_component_get_dtstart (comp, &datetime);
1856                         if (datetime.tzid)
1857                                 e_cal_client_get_timezone_sync (client, datetime.tzid, &start_zone, cancellable, NULL);
1858                         else
1859                                 start_zone = NULL;
1860                         e_cal_component_free_datetime (&datetime);
1861
1862                         /* Get the end timezone */
1863                         e_cal_component_get_dtend (comp, &datetime);
1864                         if (datetime.tzid)
1865                                 e_cal_client_get_timezone_sync (client, datetime.tzid, &end_zone, cancellable, NULL);
1866                         else
1867                                 end_zone = NULL;
1868                         e_cal_component_free_datetime (&datetime);
1869
1870                         instances_hold = g_new0 (struct instances_info, 1);
1871                         instances_hold->instances = &instances;
1872                         instances_hold->start_zone = start_zone;
1873                         instances_hold->end_zone = end_zone;
1874
1875                         e_cal_recur_generate_instances (
1876                                 comp, start, end, add_instance, instances_hold,
1877                                 e_cal_client_resolve_tzid_cb, client,
1878                                 default_zone);
1879
1880                         g_free (instances_hold);
1881                 }
1882         }
1883
1884         g_slist_foreach (objects, (GFunc) g_object_unref, NULL);
1885         g_slist_free (objects);
1886
1887         /* Generate instances and spew them out */
1888
1889         if (!g_cancellable_is_cancelled (cancellable)) {
1890                 instances = g_slist_sort (instances, compare_comp_instance);
1891                 instances = process_detached_instances (instances, detached_instances);
1892         }
1893
1894         for (l = instances; l && !g_cancellable_is_cancelled (cancellable); l = l->next) {
1895                 struct comp_instance *ci;
1896                 gboolean result;
1897
1898                 ci = l->data;
1899
1900                 result = (* cb) (ci->comp, ci->start, ci->end, cb_data);
1901
1902                 if (!result)
1903                         break;
1904         }
1905
1906         /* Clean up */
1907
1908         for (l = instances; l; l = l->next) {
1909                 struct comp_instance *ci;
1910
1911                 ci = l->data;
1912                 g_object_unref (G_OBJECT (ci->comp));
1913                 g_free (ci);
1914         }
1915
1916         g_slist_free (instances);
1917
1918         for (l = detached_instances; l; l = l->next) {
1919                 struct comp_instance *ci;
1920
1921                 ci = l->data;
1922                 g_object_unref (G_OBJECT (ci->comp));
1923                 g_free (ci);
1924         }
1925
1926         g_slist_free (detached_instances);
1927 }
1928
1929 static GSList *
1930 get_objects_sync (ECalClient *client,
1931                   time_t start,
1932                   time_t end,
1933                   const gchar *uid)
1934 {
1935         GSList *objects = NULL;
1936
1937         /* Generate objects */
1938         if (uid && *uid) {
1939                 GError *error = NULL;
1940                 gint tries = 0;
1941
1942  try_again:
1943                 if (!e_cal_client_get_objects_for_uid_sync (client, uid, &objects, NULL, &error)) {
1944                         if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_BUSY) && tries <= 10) {
1945                                 tries++;
1946                                 g_usleep (500);
1947                                 g_clear_error (&error);
1948
1949                                 goto try_again;
1950                         }
1951
1952                         unwrap_dbus_error (error, &error);
1953                         g_message ("Failed to get recurrence objects for uid %s \n", error ? error->message : "Unknown error");
1954                         g_clear_error (&error);
1955                         return NULL;
1956                 }
1957         } else {
1958                 gchar *iso_start, *iso_end;
1959                 gchar *query;
1960
1961                 iso_start = isodate_from_time_t (start);
1962                 if (!iso_start)
1963                         return NULL;
1964
1965                 iso_end = isodate_from_time_t (end);
1966                 if (!iso_end) {
1967                         g_free (iso_start);
1968                         return NULL;
1969                 }
1970
1971                 query = g_strdup_printf (
1972                         "(occur-in-time-range? (make-time \"%s\") (make-time \"%s\"))",
1973                         iso_start, iso_end);
1974                 g_free (iso_start);
1975                 g_free (iso_end);
1976                 if (!e_cal_client_get_object_list_as_comps_sync (client, query, &objects, NULL, NULL)) {
1977                         g_free (query);
1978                         return NULL;
1979                 }
1980                 g_free (query);
1981         }
1982
1983         return objects;
1984 }
1985
1986 struct get_objects_async_data
1987 {
1988         GCancellable *cancellable;
1989         ECalClient *client;
1990         time_t start;
1991         time_t end;
1992         ECalRecurInstanceFn cb;
1993         gpointer cb_data;
1994         GDestroyNotify destroy_cb_data;
1995         gchar *uid;
1996         gchar *query;
1997         guint tries;
1998         void (* ready_cb) (struct get_objects_async_data *goad, GSList *objects);
1999         icaltimezone *start_zone;
2000         icaltimezone *end_zone;
2001         ECalComponent *comp;
2002 };
2003
2004 static void
2005 free_get_objects_async_data (struct get_objects_async_data *goad)
2006 {
2007         if (!goad)
2008                 return;
2009
2010         if (goad->cancellable)
2011                 g_object_unref (goad->cancellable);
2012         if (goad->destroy_cb_data)
2013                 goad->destroy_cb_data (goad->cb_data);
2014         if (goad->client)
2015                 g_object_unref (goad->client);
2016         if (goad->comp)
2017                 g_object_unref (goad->comp);
2018         g_free (goad->query);
2019         g_free (goad->uid);
2020         g_free (goad);
2021 }
2022
2023 static gboolean repeat_get_objects_for_uid_timeout_cb (gpointer user_data);
2024
2025 static void
2026 got_objects_for_uid_cb (GObject *source_object,
2027                         GAsyncResult *result,
2028                         gpointer user_data)
2029 {
2030         struct get_objects_async_data *goad = user_data;
2031         GSList *objects = NULL;
2032         GError *error = NULL;
2033
2034         g_return_if_fail (source_object != NULL);
2035         g_return_if_fail (result != NULL);
2036         g_return_if_fail (goad != NULL);
2037         g_return_if_fail (goad->client == E_CAL_CLIENT (source_object));
2038
2039         if (!e_cal_client_get_objects_for_uid_finish (goad->client, result, &objects, &error)) {
2040                 if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) ||
2041                     g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
2042                         free_get_objects_async_data (goad);
2043                         g_clear_error (&error);
2044                         return;
2045                 }
2046
2047                 if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_BUSY) && goad->tries < 10) {
2048                         goad->tries++;
2049                         g_timeout_add (250, repeat_get_objects_for_uid_timeout_cb, goad);
2050                         g_clear_error (&error);
2051                         return;
2052                 }
2053
2054                 g_clear_error (&error);
2055                 objects = NULL;
2056         }
2057
2058         g_return_if_fail (goad->ready_cb != NULL);
2059
2060         /* takes care of the objects and goad */
2061         goad->ready_cb (goad, objects);
2062 }
2063
2064 static gboolean
2065 repeat_get_objects_for_uid_timeout_cb (gpointer user_data)
2066 {
2067         struct get_objects_async_data *goad = user_data;
2068
2069         g_return_val_if_fail (goad != NULL, FALSE);
2070
2071         e_cal_client_get_objects_for_uid (goad->client, goad->uid, goad->cancellable, got_objects_for_uid_cb, goad);
2072
2073         return FALSE;
2074 }
2075
2076 static gboolean repeat_get_object_list_as_comps_timeout_cb (gpointer user_data);
2077
2078 static void
2079 got_object_list_as_comps_cb (GObject *source_object,
2080                              GAsyncResult *result,
2081                              gpointer user_data)
2082 {
2083         struct get_objects_async_data *goad = user_data;
2084         GSList *objects = NULL;
2085         GError *error = NULL;
2086
2087         g_return_if_fail (source_object != NULL);
2088         g_return_if_fail (result != NULL);
2089         g_return_if_fail (goad != NULL);
2090         g_return_if_fail (goad->client == E_CAL_CLIENT (source_object));
2091
2092         if (!e_cal_client_get_object_list_as_comps_finish (goad->client, result, &objects, &error)) {
2093                 if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) ||
2094                     g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
2095                         free_get_objects_async_data (goad);
2096                         g_clear_error (&error);
2097                         return;
2098                 }
2099
2100                 if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_BUSY) && goad->tries < 10) {
2101                         goad->tries++;
2102                         g_timeout_add (250, repeat_get_object_list_as_comps_timeout_cb, goad);
2103                         g_clear_error (&error);
2104                         return;
2105                 }
2106
2107                 g_clear_error (&error);
2108                 objects = NULL;
2109         }
2110
2111         g_return_if_fail (goad->ready_cb != NULL);
2112
2113         /* takes care of the objects and goad */
2114         goad->ready_cb (goad, objects);
2115 }
2116
2117 static gboolean
2118 repeat_get_object_list_as_comps_timeout_cb (gpointer user_data)
2119 {
2120         struct get_objects_async_data *goad = user_data;
2121
2122         g_return_val_if_fail (goad != NULL, FALSE);
2123
2124         e_cal_client_get_object_list_as_comps (goad->client, goad->query, goad->cancellable, got_object_list_as_comps_cb, goad);
2125
2126         return FALSE;
2127 }
2128
2129 /* ready_cb may take care of both arguments, goad and objects; objects can be also NULL */
2130 static void
2131 get_objects_async (void (*ready_cb) (struct get_objects_async_data *goad,
2132                                      GSList *objects),
2133                    struct get_objects_async_data *goad)
2134 {
2135         g_return_if_fail (ready_cb != NULL);
2136         g_return_if_fail (goad != NULL);
2137
2138         goad->ready_cb = ready_cb;
2139
2140         if (goad->uid && *goad->uid) {
2141                 e_cal_client_get_objects_for_uid (goad->client, goad->uid, goad->cancellable, got_objects_for_uid_cb, goad);
2142         } else {
2143                 gchar *iso_start, *iso_end;
2144
2145                 iso_start = isodate_from_time_t (goad->start);
2146                 if (!iso_start) {
2147                         free_get_objects_async_data (goad);
2148                         return;
2149                 }
2150
2151                 iso_end = isodate_from_time_t (goad->end);
2152                 if (!iso_end) {
2153                         g_free (iso_start);
2154                         free_get_objects_async_data (goad);
2155                         return;
2156                 }
2157
2158                 goad->query = g_strdup_printf ("(occur-in-time-range? (make-time \"%s\") (make-time \"%s\"))", iso_start, iso_end);
2159
2160                 g_free (iso_start);
2161                 g_free (iso_end);
2162
2163                 e_cal_client_get_object_list_as_comps (goad->client, goad->query, goad->cancellable, got_object_list_as_comps_cb, goad);
2164         }
2165 }
2166
2167 static void
2168 generate_instances_got_objects_cb (struct get_objects_async_data *goad,
2169                                    GSList *objects)
2170 {
2171         g_return_if_fail (goad != NULL);
2172
2173         /* generate_instaces () frees 'objects' slist */
2174         if (objects)
2175                 generate_instances (goad->client, goad->start, goad->end, objects, goad->cancellable, goad->cb, goad->cb_data);
2176
2177         free_get_objects_async_data (goad);
2178 }
2179
2180 /**
2181  * e_cal_client_generate_instances:
2182  * @client: A calendar client.
2183  * @start: Start time for query.
2184  * @end: End time for query.
2185  * @cancellable: a #GCancellable; can be %NULL
2186  * @cb: Callback for each generated instance.
2187  * @cb_data: Closure data for the callback.
2188  * @destroy_cb_data: Function to call when the processing is done, to free @cb_data; can be %NULL.
2189  *
2190  * Does a combination of e_cal_client_get_object_list() and
2191  * e_cal_client_recur_generate_instances(). Unlike e_cal_client_generate_instances_sync(),
2192  * this returns immediately and the @cb callback is called asynchronously.
2193  *
2194  * The callback function should do a g_object_ref() of the calendar component
2195  * it gets passed if it intends to keep it around, since it will be unref'ed
2196  * as soon as the callback returns.
2197  *
2198  * Since: 3.2
2199  **/
2200 void
2201 e_cal_client_generate_instances (ECalClient *client,
2202                                  time_t start,
2203                                  time_t end,
2204                                  GCancellable *cancellable,
2205                                  ECalRecurInstanceFn cb,
2206                                  gpointer cb_data,
2207                                  GDestroyNotify destroy_cb_data)
2208 {
2209         struct get_objects_async_data *goad;
2210         GCancellable *use_cancellable;
2211
2212         g_return_if_fail (E_IS_CAL_CLIENT (client));
2213         g_return_if_fail (e_client_is_opened (E_CLIENT (client)));
2214
2215         g_return_if_fail (start >= 0);
2216         g_return_if_fail (end >= 0);
2217         g_return_if_fail (cb != NULL);
2218
2219         use_cancellable = cancellable;
2220         if (!use_cancellable)
2221                 use_cancellable = g_cancellable_new ();
2222
2223         goad = g_new0 (struct get_objects_async_data, 1);
2224         goad->cancellable = g_object_ref (use_cancellable);
2225         goad->client = g_object_ref (client);
2226         goad->start = start;
2227         goad->end = end;
2228         goad->cb = cb;
2229         goad->cb_data = cb_data;
2230         goad->destroy_cb_data = destroy_cb_data;
2231
2232         get_objects_async (generate_instances_got_objects_cb, goad);
2233
2234         if (use_cancellable != cancellable)
2235                 g_object_unref (use_cancellable);
2236 }
2237
2238 /**
2239  * e_cal_client_generate_instances_sync:
2240  * @client: A calendar client
2241  * @start: Start time for query
2242  * @end: End time for query
2243  * @cb: (closure cb_data) (scope call): Callback for each generated instance
2244  * @cb_data: (closure): Closure data for the callback
2245  *
2246  * Does a combination of e_cal_client_get_object_list() and
2247  * e_cal_client_recur_generate_instances().
2248  *
2249  * The callback function should do a g_object_ref() of the calendar component
2250  * it gets passed if it intends to keep it around, since it will be unreffed
2251  * as soon as the callback returns.
2252  *
2253  * Since: 3.2
2254  **/
2255 void
2256 e_cal_client_generate_instances_sync (ECalClient *client,
2257                                       time_t start,
2258                                       time_t end,
2259                                       ECalRecurInstanceFn cb,
2260                                       gpointer cb_data)
2261 {
2262         GSList *objects = NULL;
2263
2264         g_return_if_fail (E_IS_CAL_CLIENT (client));
2265         g_return_if_fail (e_client_is_opened (E_CLIENT (client)));
2266
2267         g_return_if_fail (start >= 0);
2268         g_return_if_fail (end >= 0);
2269         g_return_if_fail (cb != NULL);
2270
2271         objects = get_objects_sync (client, start, end, NULL);
2272         if (!objects)
2273                 return;
2274
2275         /* generate_instaces frees 'objects' slist */
2276         generate_instances (client, start, end, objects, NULL, cb, cb_data);
2277 }
2278
2279 /* also frees 'instances' GSList */
2280 static void
2281 process_instances (ECalComponent *comp,
2282                    GSList *instances,
2283                    ECalRecurInstanceFn cb,
2284                    gpointer cb_data)
2285 {
2286         gchar *rid;
2287         gboolean result;
2288
2289         g_return_if_fail (comp != NULL);
2290         g_return_if_fail (cb != NULL);
2291
2292         rid = e_cal_component_get_recurid_as_string (comp);
2293
2294         /* Reverse the instances list because the add_instance() function is prepending */
2295         instances = g_slist_reverse (instances);
2296
2297         /* now only return back the instances for the given object */
2298         result = TRUE;
2299         while (instances != NULL) {
2300                 struct comp_instance *ci;
2301                 gchar *instance_rid = NULL;
2302
2303                 ci = instances->data;
2304
2305                 if (result) {
2306                         instance_rid = e_cal_component_get_recurid_as_string (ci->comp);
2307
2308                         if (rid && *rid) {
2309                                 if (instance_rid && *instance_rid && strcmp (rid, instance_rid) == 0)
2310                                         result = (* cb) (ci->comp, ci->start, ci->end, cb_data);
2311                         } else
2312                                 result = (* cb)  (ci->comp, ci->start, ci->end, cb_data);
2313                 }
2314
2315                 /* remove instance from list */
2316                 instances = g_slist_remove (instances, ci);
2317                 g_object_unref (ci->comp);
2318                 g_free (ci);
2319                 g_free (instance_rid);
2320         }
2321
2322         /* clean up */
2323         g_free (rid);
2324 }
2325
2326 static void
2327 generate_instances_for_object_got_objects_cb (struct get_objects_async_data *goad,
2328                                               GSList *objects)
2329 {
2330         struct instances_info *instances_hold;
2331         GSList *instances = NULL;
2332
2333         g_return_if_fail (goad != NULL);
2334
2335         instances_hold = g_new0 (struct instances_info, 1);
2336         instances_hold->instances = &instances;
2337         instances_hold->start_zone = goad->start_zone;
2338         instances_hold->end_zone = goad->end_zone;
2339
2340         /* generate all instances in the given time range */
2341         generate_instances (goad->client, goad->start, goad->end, objects, goad->cancellable, add_instance, instances_hold);
2342
2343         /* it also frees 'instances' GSList */
2344         process_instances (goad->comp, *(instances_hold->instances), goad->cb, goad->cb_data);
2345
2346         /* clean up */
2347         free_get_objects_async_data (goad);
2348         g_free (instances_hold);
2349 }
2350
2351 /**
2352  * e_cal_client_generate_instances_for_object:
2353  * @client: A calendar client.
2354  * @icalcomp: Object to generate instances from.
2355  * @start: Start time for query.
2356  * @end: End time for query.
2357  * @cancellable: a #GCancellable; can be %NULL
2358  * @cb: Callback for each generated instance.
2359  * @cb_data: Closure data for the callback.
2360  * @destroy_cb_data: Function to call when the processing is done, to free @cb_data; can be %NULL.
2361  *
2362  * Does a combination of e_cal_client_get_object_list() and
2363  * e_cal_client_recur_generate_instances(), like e_cal_client_generate_instances(), but
2364  * for a single object. Unlike e_cal_client_generate_instances_for_object_sync(),
2365  * this returns immediately and the @cb callback is called asynchronously.
2366  *
2367  * The callback function should do a g_object_ref() of the calendar component
2368  * it gets passed if it intends to keep it around, since it will be unref'ed
2369  * as soon as the callback returns.
2370  *
2371  * Since: 3.2
2372  **/
2373 void
2374 e_cal_client_generate_instances_for_object (ECalClient *client,
2375                                             icalcomponent *icalcomp,
2376                                             time_t start,
2377                                             time_t end,
2378                                             GCancellable *cancellable,
2379                                             ECalRecurInstanceFn cb,
2380                                             gpointer cb_data,
2381                                             GDestroyNotify destroy_cb_data)
2382 {
2383         ECalComponent *comp;
2384         const gchar *uid;
2385         ECalComponentDateTime datetime;
2386         icaltimezone *start_zone = NULL, *end_zone = NULL;
2387         gboolean is_single_instance = FALSE;
2388         struct get_objects_async_data *goad;
2389         GCancellable *use_cancellable;
2390
2391         g_return_if_fail (E_IS_CAL_CLIENT (client));
2392         g_return_if_fail (e_client_is_opened (E_CLIENT (client)));
2393
2394         g_return_if_fail (start >= 0);
2395         g_return_if_fail (end >= 0);
2396         g_return_if_fail (cb != NULL);
2397
2398         comp = e_cal_component_new ();
2399         e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (icalcomp));
2400
2401         if (!e_cal_component_has_recurrences (comp))
2402                 is_single_instance = TRUE;
2403
2404         /* If the backend stores it as individual instances and does not
2405          * have a master object - do not expand */
2406         if (is_single_instance || e_client_check_capability (E_CLIENT (client), CAL_STATIC_CAPABILITY_RECURRENCES_NO_MASTER)) {
2407                 /* return the same instance */
2408                 (* cb)  (comp, icaltime_as_timet_with_zone (icalcomponent_get_dtstart (icalcomp), client->priv->default_zone),
2409                                 icaltime_as_timet_with_zone (icalcomponent_get_dtend (icalcomp), client->priv->default_zone), cb_data);
2410                 g_object_unref (comp);
2411
2412                 if (destroy_cb_data)
2413                         destroy_cb_data (cb_data);
2414                 return;
2415         }
2416
2417         e_cal_component_get_uid (comp, &uid);
2418
2419         /* Get the start timezone */
2420         e_cal_component_get_dtstart (comp, &datetime);
2421         if (datetime.tzid)
2422                 e_cal_client_get_timezone_sync (client, datetime.tzid, &start_zone, NULL, NULL);
2423         else
2424                 start_zone = NULL;
2425         e_cal_component_free_datetime (&datetime);
2426
2427         /* Get the end timezone */
2428         e_cal_component_get_dtend (comp, &datetime);
2429         if (datetime.tzid)
2430                 e_cal_client_get_timezone_sync (client, datetime.tzid, &end_zone, NULL, NULL);
2431         else
2432                 end_zone = NULL;
2433         e_cal_component_free_datetime (&datetime);
2434
2435         use_cancellable = cancellable;
2436         if (!use_cancellable)
2437                 use_cancellable = g_cancellable_new ();
2438
2439         goad = g_new0 (struct get_objects_async_data, 1);
2440         goad->cancellable = g_object_ref (use_cancellable);
2441         goad->client = g_object_ref (client);
2442         goad->start = start;
2443         goad->end = end;
2444         goad->cb = cb;
2445         goad->cb_data = cb_data;
2446         goad->destroy_cb_data = destroy_cb_data;
2447         goad->start_zone = start_zone;
2448         goad->end_zone = end_zone;
2449         goad->comp = comp;
2450         goad->uid = g_strdup (uid);
2451
2452         get_objects_async (generate_instances_for_object_got_objects_cb, goad);
2453
2454         if (use_cancellable != cancellable)
2455                 g_object_unref (use_cancellable);
2456 }
2457
2458 /**
2459  * e_cal_client_generate_instances_for_object_sync:
2460  * @client: A calendar client
2461  * @icalcomp: Object to generate instances from
2462  * @start: Start time for query
2463  * @end: End time for query
2464  * @cb: (closure cb_data) (scope call): Callback for each generated instance
2465  * @cb_data: (closure): Closure data for the callback
2466  *
2467  * Does a combination of e_cal_client_get_object_list() and
2468  * e_cal_client_recur_generate_instances(), like e_cal_client_generate_instances_sync(), but
2469  * for a single object.
2470  *
2471  * The callback function should do a g_object_ref() of the calendar component
2472  * it gets passed if it intends to keep it around, since it will be unref'ed
2473  * as soon as the callback returns.
2474  *
2475  * Since: 3.2
2476  **/
2477 void
2478 e_cal_client_generate_instances_for_object_sync (ECalClient *client,
2479                                                  icalcomponent *icalcomp,
2480                                                  time_t start,
2481                                                  time_t end,
2482                                                  ECalRecurInstanceFn cb,
2483                                                  gpointer cb_data)
2484 {
2485         ECalComponent *comp;
2486         const gchar *uid;
2487         GSList *instances = NULL;
2488         ECalComponentDateTime datetime;
2489         icaltimezone *start_zone = NULL, *end_zone = NULL;
2490         struct instances_info *instances_hold;
2491         gboolean is_single_instance = FALSE;
2492
2493         g_return_if_fail (E_IS_CAL_CLIENT (client));
2494         g_return_if_fail (e_client_is_opened (E_CLIENT (client)));
2495
2496         g_return_if_fail (start >= 0);
2497         g_return_if_fail (end >= 0);
2498         g_return_if_fail (cb != NULL);
2499
2500         comp = e_cal_component_new ();
2501         e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (icalcomp));
2502
2503         if (!e_cal_component_has_recurrences (comp))
2504                 is_single_instance = TRUE;
2505
2506         /* If the backend stores it as individual instances and does not
2507          * have a master object - do not expand */
2508         if (is_single_instance || e_client_check_capability (E_CLIENT (client), CAL_STATIC_CAPABILITY_RECURRENCES_NO_MASTER)) {
2509                 /* return the same instance */
2510                 (* cb)  (comp, icaltime_as_timet_with_zone (icalcomponent_get_dtstart (icalcomp), client->priv->default_zone),
2511                                 icaltime_as_timet_with_zone (icalcomponent_get_dtend (icalcomp), client->priv->default_zone), cb_data);
2512                 g_object_unref (comp);
2513                 return;
2514         }
2515
2516         e_cal_component_get_uid (comp, &uid);
2517
2518         /* Get the start timezone */
2519         e_cal_component_get_dtstart (comp, &datetime);
2520         if (datetime.tzid)
2521                 e_cal_client_get_timezone_sync (client, datetime.tzid, &start_zone, NULL, NULL);
2522         else
2523                 start_zone = NULL;
2524         e_cal_component_free_datetime (&datetime);
2525
2526         /* Get the end timezone */
2527         e_cal_component_get_dtend (comp, &datetime);
2528         if (datetime.tzid)
2529                 e_cal_client_get_timezone_sync (client, datetime.tzid, &end_zone, NULL, NULL);
2530         else
2531                 end_zone = NULL;
2532         e_cal_component_free_datetime (&datetime);
2533
2534         instances_hold = g_new0 (struct instances_info, 1);
2535         instances_hold->instances = &instances;
2536         instances_hold->start_zone = start_zone;
2537         instances_hold->end_zone = end_zone;
2538
2539         /* generate all instances in the given time range */
2540         generate_instances (client, start, end, get_objects_sync (client, start, end, uid), NULL, add_instance, instances_hold);
2541
2542         /* it also frees 'instances' GSList */
2543         process_instances (comp, *(instances_hold->instances), cb, cb_data);
2544
2545         /* clean up */
2546         g_object_unref (comp);
2547         g_free (instances_hold);
2548 }
2549
2550 typedef struct _ForeachTZIDCallbackData ForeachTZIDCallbackData;
2551 struct _ForeachTZIDCallbackData {
2552         ECalClient *client;
2553         GHashTable *timezone_hash;
2554         gboolean success;
2555 };
2556
2557 /* This adds the VTIMEZONE given by the TZID parameter to the GHashTable in
2558  * data. */
2559 static void
2560 foreach_tzid_callback (icalparameter *param,
2561                        gpointer cbdata)
2562 {
2563         ForeachTZIDCallbackData *data = cbdata;
2564         const gchar *tzid;
2565         icaltimezone *zone = NULL;
2566         icalcomponent *vtimezone_comp;
2567         gchar *vtimezone_as_string;
2568
2569         /* Get the TZID string from the parameter. */
2570         tzid = icalparameter_get_tzid (param);
2571         if (!tzid)
2572                 return;
2573
2574         /* Check if we've already added it to the GHashTable. */
2575         if (g_hash_table_lookup (data->timezone_hash, tzid))
2576                 return;
2577
2578         if (!e_cal_client_get_timezone_sync (data->client, tzid, &zone, NULL, NULL) || !zone) {
2579                 data->success = FALSE;
2580                 return;
2581         }
2582
2583         /* Convert it to a string and add it to the hash. */
2584         vtimezone_comp = icaltimezone_get_component (zone);
2585         if (!vtimezone_comp)
2586                 return;
2587
2588         vtimezone_as_string = icalcomponent_as_ical_string_r (vtimezone_comp);
2589
2590         g_hash_table_insert (data->timezone_hash, (gchar *) tzid, vtimezone_as_string);
2591 }
2592
2593 /* This appends the value string to the GString given in data. */
2594 static void
2595 append_timezone_string (gpointer key,
2596                         gpointer value,
2597                         gpointer data)
2598 {
2599         GString *vcal_string = data;
2600
2601         g_string_append (vcal_string, value);
2602         g_free (value);
2603 }
2604
2605 /* This simply frees the hash values. */
2606 static void
2607 free_timezone_string (gpointer key,
2608                       gpointer value,
2609                       gpointer data)
2610 {
2611         g_free (value);
2612 }
2613
2614 /**
2615  * e_cal_client_get_component_as_string:
2616  * @client: A calendar client.
2617  * @icalcomp: A calendar component object.
2618  *
2619  * Gets a calendar component as an iCalendar string, with a toplevel
2620  * VCALENDAR component and all VTIMEZONEs needed for the component.
2621  *
2622  * Returns: the component as a complete iCalendar string, or NULL on
2623  * failure. The string should be freed with g_free().
2624  *
2625  * Since: 3.2
2626  **/
2627 gchar *
2628 e_cal_client_get_component_as_string (ECalClient *client,
2629                                       icalcomponent *icalcomp)
2630 {
2631         GHashTable *timezone_hash;
2632         GString *vcal_string;
2633         ForeachTZIDCallbackData cbdata;
2634         gchar *obj_string;
2635
2636         g_return_val_if_fail (E_IS_CAL_CLIENT (client), NULL);
2637         g_return_val_if_fail (icalcomp != NULL, NULL);
2638
2639         timezone_hash = g_hash_table_new (g_str_hash, g_str_equal);
2640
2641         /* Add any timezones needed to the hash. We use a hash since we only
2642          * want to add each timezone once at most. */
2643         cbdata.client = client;
2644         cbdata.timezone_hash = timezone_hash;
2645         cbdata.success = TRUE;
2646         icalcomponent_foreach_tzid (icalcomp, foreach_tzid_callback, &cbdata);
2647         if (!cbdata.success) {
2648                 g_hash_table_foreach (timezone_hash, free_timezone_string, NULL);
2649                 return NULL;
2650         }
2651
2652         /* Create the start of a VCALENDAR, to add the VTIMEZONES to,
2653          * and remember its length so we know if any VTIMEZONEs get added. */
2654         vcal_string = g_string_new (NULL);
2655         g_string_append (
2656                 vcal_string,
2657                 "BEGIN:VCALENDAR\n"
2658                 "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
2659                 "VERSION:2.0\n"
2660                 "METHOD:PUBLISH\n");
2661
2662         /* Now concatenate all the timezone strings. This also frees the
2663          * timezone strings as it goes. */
2664         g_hash_table_foreach (timezone_hash, append_timezone_string, vcal_string);
2665
2666         /* Get the string for the VEVENT/VTODO. */
2667         obj_string = icalcomponent_as_ical_string_r (icalcomp);
2668
2669         /* If there were any timezones to send, create a complete VCALENDAR,
2670          * else just send the VEVENT/VTODO string. */
2671         g_string_append (vcal_string, obj_string);
2672         g_string_append (vcal_string, "END:VCALENDAR\n");
2673         g_free (obj_string);
2674
2675         obj_string = g_string_free (vcal_string, FALSE);
2676
2677         g_hash_table_destroy (timezone_hash);
2678
2679         return obj_string;
2680 }
2681
2682 static gboolean
2683 complete_string_exchange (gboolean res,
2684                           gchar *out_string,
2685                           gchar **result,
2686                           GError **error)
2687 {
2688         g_return_val_if_fail (result != NULL, FALSE);
2689
2690         if (res && out_string) {
2691                 if (*out_string) {
2692                         *result = out_string;
2693                 } else {
2694                         /* empty string is returned as NULL */
2695                         *result = NULL;
2696                         g_free (out_string);
2697                 }
2698         } else {
2699                 *result = NULL;
2700                 g_free (out_string);
2701                 res = FALSE;
2702
2703                 if (error && !*error)
2704                         g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_INVALID_ARG, NULL));
2705         }
2706
2707         return res;
2708 }
2709
2710 static gboolean
2711 complete_strv_exchange (gboolean res,
2712                         gchar **out_strings,
2713                         GSList **result,
2714                         GError **error)
2715 {
2716         g_return_val_if_fail (result != NULL, FALSE);
2717
2718         if (res && out_strings) {
2719                 *result = e_client_util_strv_to_slist ((const gchar * const*) out_strings);
2720         } else {
2721                 *result = NULL;
2722                 res = FALSE;
2723
2724                 if (error && !*error)
2725                         g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_INVALID_ARG, NULL));
2726         }
2727
2728         g_strfreev (out_strings);
2729
2730         return res;
2731 }
2732
2733 static gboolean
2734 cal_client_get_default_object_from_cache_finish (EClient *client,
2735                                                  GAsyncResult *result,
2736                                                  gchar **prop_value,
2737                                                  GError **error)
2738 {
2739         GSimpleAsyncResult *simple;
2740         GError *local_error = NULL;
2741
2742         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
2743         g_return_val_if_fail (result != NULL, FALSE);
2744         g_return_val_if_fail (prop_value != NULL, FALSE);
2745         g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (client), cal_client_get_default_object_from_cache_finish), FALSE);
2746
2747         simple = G_SIMPLE_ASYNC_RESULT (result);
2748
2749         if (g_simple_async_result_propagate_error (simple, &local_error)) {
2750                 e_client_unwrap_dbus_error (client, local_error, error);
2751                 return FALSE;
2752         }
2753
2754         *prop_value = g_strdup (g_simple_async_result_get_op_res_gpointer (simple));
2755
2756         return *prop_value != NULL;
2757 }
2758
2759 /**
2760  * e_cal_client_get_default_object:
2761  * @client: an #ECalClient
2762  * @cancellable: a #GCancellable; can be %NULL
2763  * @callback: callback to call when a result is ready
2764  * @user_data: user data for the @callback
2765  *
2766  * Retrives an #icalcomponent from the backend that contains the default
2767  * values for properties needed. The call is finished
2768  * by e_cal_client_get_default_object_finish() from the @callback.
2769  *
2770  * Since: 3.2
2771  **/
2772 void
2773 e_cal_client_get_default_object (ECalClient *client,
2774                                  GCancellable *cancellable,
2775                                  GAsyncReadyCallback callback,
2776                                  gpointer user_data)
2777 {
2778         gchar *prop_value;
2779         EClient *base_client = E_CLIENT (client);
2780
2781         prop_value = e_client_get_backend_property_from_cache (base_client, CAL_BACKEND_PROPERTY_DEFAULT_OBJECT);
2782         if (prop_value) {
2783                 e_client_finish_async_without_dbus (base_client, cancellable, callback, user_data, cal_client_get_default_object_from_cache_finish, prop_value, g_free);
2784         } else {
2785                 e_client_proxy_call_string (
2786                         base_client, CAL_BACKEND_PROPERTY_DEFAULT_OBJECT, cancellable, callback, user_data, e_cal_client_get_default_object,
2787                         e_gdbus_cal_call_get_backend_property,
2788                         NULL, NULL, e_gdbus_cal_call_get_backend_property_finish, NULL, NULL);
2789         }
2790 }
2791
2792 static gboolean
2793 complete_get_object (gboolean res,
2794                      gchar *out_string,
2795                      icalcomponent **icalcomp,
2796                      gboolean ensure_unique_uid,
2797                      GError **error)
2798 {
2799         g_return_val_if_fail (icalcomp != NULL, FALSE);
2800
2801         if (res && out_string) {
2802                 *icalcomp = icalparser_parse_string (out_string);
2803                 if (!*icalcomp) {
2804                         g_propagate_error (error, e_cal_client_error_create (E_CAL_CLIENT_ERROR_INVALID_OBJECT, NULL));
2805                         res = FALSE;
2806                 } else if (ensure_unique_uid && icalcomponent_get_uid (*icalcomp)) {
2807                         /* make sure the UID is always unique */
2808                         gchar *new_uid = e_cal_component_gen_uid ();
2809
2810                         icalcomponent_set_uid (*icalcomp, new_uid);
2811                         g_free (new_uid);
2812                 }
2813         } else {
2814                 *icalcomp = NULL;
2815                 res = FALSE;
2816         }
2817
2818         g_free (out_string);
2819
2820         return res;
2821 }
2822
2823 /**
2824  * e_cal_client_get_default_object_finish:
2825  * @client: an #ECalClient
2826  * @result: a #GAsyncResult
2827  * @icalcomp: (out): Return value for the default calendar object.
2828  * @error: (out): a #GError to set an error, if any
2829  *
2830  * Finishes previous call of e_cal_client_get_default_object() and
2831  * sets @icalcomp to an #icalcomponent from the backend that contains
2832  * the default values for properties needed. This @icalcomp should be
2833  * freed with icalcomponent_free().
2834  *
2835  * Returns: %TRUE if successful, %FALSE otherwise.
2836  *
2837  * Since: 3.2
2838  **/
2839 gboolean
2840 e_cal_client_get_default_object_finish (ECalClient *client,
2841                                         GAsyncResult *result,
2842                                         icalcomponent **icalcomp,
2843                                         GError **error)
2844 {
2845         gboolean res;
2846         gchar *out_string = NULL;
2847
2848         g_return_val_if_fail (icalcomp != NULL, FALSE);
2849
2850         if (g_simple_async_result_get_source_tag (G_SIMPLE_ASYNC_RESULT (result)) == cal_client_get_default_object_from_cache_finish) {
2851                 res = cal_client_get_default_object_from_cache_finish (E_CLIENT (client), result, &out_string, error);
2852         } else {
2853                 res = e_client_proxy_call_finish_string (E_CLIENT (client), result, &out_string, error, e_cal_client_get_default_object);
2854         }
2855
2856         return complete_get_object (res, out_string, icalcomp, TRUE, error);
2857 }
2858
2859 /**
2860  * e_cal_client_get_default_object_sync:
2861  * @client: an #ECalClient
2862  * @icalcomp: (out): Return value for the default calendar object.
2863  * @cancellable: a #GCancellable; can be %NULL
2864  * @error: (out): a #GError to set an error, if any
2865  *
2866  * Retrives an #icalcomponent from the backend that contains the default
2867  * values for properties needed. This @icalcomp should be freed with
2868  * icalcomponent_free().
2869  *
2870  * Returns: %TRUE if successful, %FALSE otherwise.
2871  *
2872  * Since: 3.2
2873  **/
2874 gboolean
2875 e_cal_client_get_default_object_sync (ECalClient *client,
2876                                       icalcomponent **icalcomp,
2877                                       GCancellable *cancellable,
2878                                       GError **error)
2879 {
2880         gboolean res;
2881         gchar *out_string = NULL;
2882
2883         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
2884         g_return_val_if_fail (icalcomp != NULL, FALSE);
2885
2886         if (client->priv->dbus_proxy == NULL) {
2887                 set_proxy_gone_error (error);
2888                 return FALSE;
2889         }
2890
2891         out_string = e_client_get_backend_property_from_cache (E_CLIENT (client), CAL_BACKEND_PROPERTY_DEFAULT_OBJECT);
2892         if (out_string)
2893                 res = TRUE;
2894         else
2895                 res = e_client_proxy_call_sync_string__string (E_CLIENT (client), CAL_BACKEND_PROPERTY_DEFAULT_OBJECT, &out_string, cancellable, error, e_gdbus_cal_call_get_backend_property_sync);
2896
2897         return complete_get_object (res, out_string, icalcomp, TRUE, error);
2898 }
2899
2900 static gboolean
2901 complete_get_object_master (ECalClientSourceType source_type,
2902                             gboolean res,
2903                             gchar *out_string,
2904                             icalcomponent **icalcomp,
2905                             GError **error)
2906 {
2907         g_return_val_if_fail (icalcomp != NULL, FALSE);
2908
2909         if (res && out_string) {
2910                 icalcomponent *tmp_comp = icalparser_parse_string (out_string);
2911                 if (!tmp_comp) {
2912                         *icalcomp = NULL;
2913                         g_propagate_error (error, e_cal_client_error_create (E_CAL_CLIENT_ERROR_INVALID_OBJECT, NULL));
2914                         res = FALSE;
2915                 } else {
2916                         icalcomponent_kind kind;
2917                         icalcomponent *master_comp = NULL;
2918
2919                         switch (source_type) {
2920                         case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
2921                                 kind = ICAL_VEVENT_COMPONENT;
2922                                 break;
2923                         case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
2924                                 kind = ICAL_VTODO_COMPONENT;
2925                                 break;
2926                         case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
2927                                 kind = ICAL_VJOURNAL_COMPONENT;
2928                                 break;
2929                         default:
2930                                 icalcomponent_free (tmp_comp);
2931                                 *icalcomp = NULL;
2932                                 res = FALSE;
2933
2934                                 g_warn_if_reached ();
2935                         }
2936
2937                         if (res && icalcomponent_isa (tmp_comp) == kind) {
2938                                 *icalcomp = tmp_comp;
2939                                 tmp_comp = NULL;
2940                         } else if (res && icalcomponent_isa (tmp_comp) == ICAL_VCALENDAR_COMPONENT) {
2941                                 for (master_comp = icalcomponent_get_first_component (tmp_comp, kind);
2942                                      master_comp;
2943                                      master_comp = icalcomponent_get_next_component (tmp_comp, kind)) {
2944                                         if (!icalcomponent_get_uid (master_comp))
2945                                                 continue;
2946
2947                                         if (icaltime_is_null_time (icalcomponent_get_recurrenceid (master_comp)) ||
2948                                             !icaltime_is_valid_time (icalcomponent_get_recurrenceid (master_comp)))
2949                                                 break;
2950                                 }
2951
2952                                 if (!master_comp)
2953                                         master_comp = icalcomponent_get_first_component (tmp_comp, kind);
2954
2955                                 *icalcomp = master_comp ? icalcomponent_new_clone (master_comp) : NULL;
2956                         }
2957
2958                         if (tmp_comp)
2959                                 icalcomponent_free (tmp_comp);
2960                 }
2961         } else {
2962                 *icalcomp = NULL;
2963                 res = FALSE;
2964         }
2965
2966         g_free (out_string);
2967
2968         return res && *icalcomp;
2969 }
2970
2971 /**
2972  * e_cal_client_get_object:
2973  * @client: an #ECalClient
2974  * @uid: Unique identifier for a calendar component.
2975  * @rid: Recurrence identifier.
2976  * @cancellable: a #GCancellable; can be %NULL
2977  * @callback: callback to call when a result is ready
2978  * @user_data: user data for the @callback
2979  *
2980  * Queries a calendar for a calendar component object based on its unique
2981  * identifier. The call is finished by e_cal_client_get_object_finish()
2982  * from the @callback.
2983  *
2984  * Use e_cal_client_get_objects_for_uid() to get list of all
2985  * objects for the given uid, which includes master object and
2986  * all detached instances.
2987  *
2988  * Since: 3.2
2989  **/
2990 void
2991 e_cal_client_get_object (ECalClient *client,
2992                          const gchar *uid,
2993                          const gchar *rid,
2994                          GCancellable *cancellable,
2995                          GAsyncReadyCallback callback,
2996                          gpointer user_data)
2997 {
2998         gchar **strv;
2999
3000         g_return_if_fail (uid != NULL);
3001
3002         strv = e_gdbus_cal_encode_get_object (uid, rid);
3003
3004         e_client_proxy_call_strv (
3005                 E_CLIENT (client), (const gchar * const *) strv, cancellable, callback, user_data, e_cal_client_get_object,
3006                 e_gdbus_cal_call_get_object,
3007                 NULL, NULL, e_gdbus_cal_call_get_object_finish, NULL, NULL);
3008
3009         g_strfreev (strv);
3010 }
3011
3012 /**
3013  * e_cal_client_get_object_finish:
3014  * @client: an #ECalClient
3015  * @result: a #GAsyncResult
3016  * @icalcomp: (out): Return value for the calendar component object.
3017  * @error: (out): a #GError to set an error, if any
3018  *
3019  * Finishes previous call of e_cal_client_get_object() and
3020  * sets @icalcomp to queried component. This function always returns
3021  * master object for a case of @rid being NULL or an empty string.
3022  * This component should be freed with icalcomponent_free().
3023  *
3024  * Use e_cal_client_get_objects_for_uid() to get list of all
3025  * objects for the given uid, which includes master object and
3026  * all detached instances.
3027  *
3028  * Returns: %TRUE if successful, %FALSE otherwise.
3029  *
3030  * Since: 3.2
3031  **/
3032 gboolean
3033 e_cal_client_get_object_finish (ECalClient *client,
3034                                 GAsyncResult *result,
3035                                 icalcomponent **icalcomp,
3036                                 GError **error)
3037 {
3038         gboolean res;
3039         gchar *out_string = NULL;
3040
3041         g_return_val_if_fail (icalcomp != NULL, FALSE);
3042
3043         res = e_client_proxy_call_finish_string (E_CLIENT (client), result, &out_string, error, e_cal_client_get_object);
3044
3045         return complete_get_object_master (e_cal_client_get_source_type (client), res, out_string, icalcomp, error);
3046 }
3047
3048 /**
3049  * e_cal_client_get_object_sync:
3050  * @client: an #ECalClient
3051  * @uid: Unique identifier for a calendar component.
3052  * @rid: Recurrence identifier.
3053  * @icalcomp: (out): Return value for the calendar component object.
3054  * @cancellable: a #GCancellable; can be %NULL
3055  * @error: (out): a #GError to set an error, if any
3056  *
3057  * Queries a calendar for a calendar component object based
3058  * on its unique identifier. This function always returns
3059  * master object for a case of @rid being NULL or an empty string.
3060  * This component should be freed with icalcomponent_free().
3061  *
3062  * Use e_cal_client_get_objects_for_uid_sync() to get list of all
3063  * objects for the given uid, which includes master object and
3064  * all detached instances.
3065  *
3066  * Returns: %TRUE if successful, %FALSE otherwise.
3067  *
3068  * Since: 3.2
3069  **/
3070 gboolean
3071 e_cal_client_get_object_sync (ECalClient *client,
3072                               const gchar *uid,
3073                               const gchar *rid,
3074                               icalcomponent **icalcomp,
3075                               GCancellable *cancellable,
3076                               GError **error)
3077 {
3078         gboolean res;
3079         gchar *out_string = NULL, **strv;
3080
3081         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
3082         g_return_val_if_fail (uid != NULL, FALSE);
3083         g_return_val_if_fail (icalcomp != NULL, FALSE);
3084
3085         if (client->priv->dbus_proxy == NULL) {
3086                 set_proxy_gone_error (error);
3087                 return FALSE;
3088         }
3089
3090         strv = e_gdbus_cal_encode_get_object (uid, rid);
3091         res = e_client_proxy_call_sync_strv__string (E_CLIENT (client), (const gchar * const *) strv, &out_string, cancellable, error, e_gdbus_cal_call_get_object_sync);
3092         g_strfreev (strv);
3093
3094         return complete_get_object_master (e_cal_client_get_source_type (client), res, out_string, icalcomp, error);
3095 }
3096
3097 /**
3098  * e_cal_client_get_objects_for_uid:
3099  * @client: an #ECalClient
3100  * @uid: Unique identifier for a calendar component
3101  * @cancellable: a #GCancellable; can be %NULL
3102  * @callback: callback to call when a result is ready
3103  * @user_data: user data for the @callback
3104  *
3105  * Queries a calendar for all calendar components with the given unique
3106  * ID. This will return any recurring event and all its detached recurrences.
3107  * For non-recurring events, it will just return the object with that ID.
3108  * The call is finished by e_cal_client_get_objects_for_uid_finish() from
3109  * the @callback.
3110  *
3111  * Since: 3.2
3112  **/
3113 void
3114 e_cal_client_get_objects_for_uid (ECalClient *client,
3115                                   const gchar *uid,
3116                                   GCancellable *cancellable,
3117                                   GAsyncReadyCallback callback,
3118                                   gpointer user_data)
3119 {
3120         gchar **strv;
3121
3122         g_return_if_fail (uid != NULL);
3123
3124         strv = e_gdbus_cal_encode_get_object (uid, "");
3125
3126         e_client_proxy_call_strv (
3127                 E_CLIENT (client), (const gchar * const *) strv, cancellable, callback, user_data, e_cal_client_get_objects_for_uid,
3128                 e_gdbus_cal_call_get_object,
3129                 NULL, NULL, e_gdbus_cal_call_get_object_finish, NULL, NULL);
3130
3131         g_strfreev (strv);
3132 }
3133
3134 static gboolean
3135 complete_get_objects_for_uid (ECalClientSourceType source_type,
3136                               gboolean res,
3137                               gchar *out_string,
3138                               GSList **ecalcomps,
3139                               GError **error)
3140 {
3141         icalcomponent *icalcomp = NULL;
3142         icalcomponent_kind kind;
3143         ECalComponent *comp;
3144
3145         res = complete_get_object (res, out_string, &icalcomp, FALSE, error);
3146         if (!res || !icalcomp)
3147                 return FALSE;
3148
3149         kind = icalcomponent_isa (icalcomp);
3150         if ((kind == ICAL_VEVENT_COMPONENT && source_type == E_CAL_CLIENT_SOURCE_TYPE_EVENTS) ||
3151             (kind == ICAL_VTODO_COMPONENT && source_type == E_CAL_CLIENT_SOURCE_TYPE_TASKS) ||
3152             (kind == ICAL_VJOURNAL_COMPONENT && source_type == E_CAL_CLIENT_SOURCE_TYPE_MEMOS)) {
3153                 comp = e_cal_component_new ();
3154                 e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (icalcomp));
3155                 *ecalcomps = g_slist_append (NULL, comp);
3156         } else if (kind == ICAL_VCALENDAR_COMPONENT) {
3157                 icalcomponent *subcomp;
3158                 icalcomponent_kind kind_to_find;
3159
3160                 switch (source_type) {
3161                 case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
3162                         kind_to_find = ICAL_VTODO_COMPONENT;
3163                         break;
3164                 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
3165                         kind_to_find = ICAL_VJOURNAL_COMPONENT;
3166                         break;
3167                 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
3168                 default:
3169                         kind_to_find = ICAL_VEVENT_COMPONENT;
3170                         break;
3171                 }
3172
3173                 *ecalcomps = NULL;
3174                 subcomp = icalcomponent_get_first_component (icalcomp, kind_to_find);
3175                 while (subcomp) {
3176                         comp = e_cal_component_new ();
3177                         e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (subcomp));
3178                         *ecalcomps = g_slist_prepend (*ecalcomps, comp);
3179                         subcomp = icalcomponent_get_next_component (icalcomp, kind_to_find);
3180                 }
3181
3182                 *ecalcomps = g_slist_reverse (*ecalcomps);
3183         }
3184
3185         icalcomponent_free (icalcomp);
3186
3187         return TRUE;
3188 }
3189
3190 /**
3191  * e_cal_client_get_objects_for_uid_finish:
3192  * @client: an #ECalClient
3193  * @result: a #GAsyncResult
3194  * @ecalcomps: (out) (transfer full) (element-type ECalComponent): Return value
3195  * for the list of objects obtained from the backend
3196  * @error: (out): a #GError to set an error, if any
3197  *
3198  * Finishes previous call of e_cal_client_get_objects_for_uid() and
3199  * sets @ecalcomps to a list of #ECalComponent<!-- -->s corresponding to
3200  * found components for a given uid of the same type as this client.
3201  * This list should be freed with e_cal_client_free_ecalcomp_slist().
3202  *
3203  * Returns: %TRUE if successful, %FALSE otherwise.
3204  *
3205  * Since: 3.2
3206  **/
3207 gboolean
3208 e_cal_client_get_objects_for_uid_finish (ECalClient *client,
3209                                          GAsyncResult *result,
3210                                          GSList **ecalcomps,
3211                                          GError **error)
3212 {
3213         gboolean res;
3214         gchar *out_string = NULL;
3215
3216         g_return_val_if_fail (ecalcomps != NULL, FALSE);
3217
3218         res = e_client_proxy_call_finish_string (E_CLIENT (client), result, &out_string, error, e_cal_client_get_objects_for_uid);
3219
3220         return complete_get_objects_for_uid (e_cal_client_get_source_type (client), res, out_string, ecalcomps, error);
3221 }
3222
3223 /**
3224  * e_cal_client_get_objects_for_uid_sync:
3225  * @client: an #ECalClient
3226  * @uid: Unique identifier for a calendar component
3227  * @ecalcomps: (out) (transfer full) (element-type ECalComponent): Return value
3228  * for the list of objects obtained from the backend
3229  * @cancellable: (allow-none): a #GCancellable; can be %NULL
3230  * @error: (out): a #GError to set an error, if any
3231  *
3232  * Queries a calendar for all calendar components with the given unique
3233  * ID. This will return any recurring event and all its detached recurrences.
3234  * For non-recurring events, it will just return the object with that ID.
3235  * This list should be freed with e_cal_client_free_ecalcomp_slist().
3236  *
3237  * Returns: %TRUE if successful, %FALSE otherwise.
3238  *
3239  * Since: 3.2
3240  **/
3241 gboolean
3242 e_cal_client_get_objects_for_uid_sync (ECalClient *client,
3243                                        const gchar *uid,
3244                                        GSList **ecalcomps,
3245                                        GCancellable *cancellable,
3246                                        GError **error)
3247 {
3248         gboolean res;
3249         gchar *out_string = NULL, **strv = NULL;
3250
3251         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
3252         g_return_val_if_fail (uid != NULL, FALSE);
3253         g_return_val_if_fail (ecalcomps != NULL, FALSE);
3254
3255         if (client->priv->dbus_proxy == NULL) {
3256                 set_proxy_gone_error (error);
3257                 return FALSE;
3258         }
3259
3260         strv = e_gdbus_cal_encode_get_object (uid, "");
3261         res = e_client_proxy_call_sync_strv__string (E_CLIENT (client), (const gchar * const *) strv, &out_string, cancellable, error, e_gdbus_cal_call_get_object_sync);
3262         g_strfreev (strv);
3263
3264         return complete_get_objects_for_uid (e_cal_client_get_source_type (client), res, out_string, ecalcomps, error);
3265 }
3266
3267 /**
3268  * e_cal_client_get_object_list:
3269  * @client: an #ECalClient
3270  * @sexp: an S-expression representing the query
3271  * @cancellable: a #GCancellable; can be %NULL
3272  * @callback: callback to call when a result is ready
3273  * @user_data: user data for the @callback
3274  *
3275  * Gets a list of objects from the calendar that match the query specified
3276  * by the @sexp argument, returning matching objects as a list of #icalcomponent-s.
3277  * The call is finished by e_cal_client_get_object_list_finish() from
3278  * the @callback.
3279  *
3280  * Since: 3.2
3281  **/
3282 void
3283 e_cal_client_get_object_list (ECalClient *client,
3284                               const gchar *sexp,
3285                               GCancellable *cancellable,
3286                               GAsyncReadyCallback callback,
3287                               gpointer user_data)
3288 {
3289         gchar *gdbus_sexp = NULL;
3290
3291         g_return_if_fail (sexp != NULL);
3292
3293         e_client_proxy_call_string (
3294                 E_CLIENT (client), e_util_ensure_gdbus_string (sexp, &gdbus_sexp), cancellable, callback, user_data, e_cal_client_get_object_list,
3295                 e_gdbus_cal_call_get_object_list,
3296                 NULL, NULL, NULL, e_gdbus_cal_call_get_object_list_finish, NULL);
3297
3298         g_free (gdbus_sexp);
3299 }
3300
3301 static gboolean
3302 complete_get_object_list (gboolean res,
3303                           gchar **out_strv,
3304                           GSList **icalcomps,
3305                           GError **error)
3306 {
3307         g_return_val_if_fail (icalcomps != NULL, FALSE);
3308
3309         *icalcomps = NULL;
3310
3311         if (res && out_strv) {
3312                 gint ii;
3313                 icalcomponent *icalcomp;
3314
3315                 for (ii = 0; out_strv[ii]; ii++) {
3316                         icalcomp = icalcomponent_new_from_string (out_strv[ii]);
3317
3318                         if (!icalcomp)
3319                                 continue;
3320
3321                         *icalcomps = g_slist_prepend (*icalcomps, icalcomp);
3322                 }
3323
3324                 *icalcomps = g_slist_reverse (*icalcomps);
3325         } else {
3326                 res = FALSE;
3327         }
3328
3329         g_strfreev (out_strv);
3330
3331         return res;
3332 }
3333
3334 /**
3335  * e_cal_client_get_object_list_finish:
3336  * @client: an #ECalClient
3337  * @result: a #GAsyncResult
3338  * @icalcomps: (out) (element-type icalcomponent): list of matching
3339  * #icalcomponent<!-- -->s
3340  * @error: (out): a #GError to set an error, if any
3341  *
3342  * Finishes previous call of e_cal_client_get_object_list() and
3343  * sets @icalcomps to a matching list of #icalcomponent-s.
3344  * This list should be freed with e_cal_client_free_icalcomp_slist().
3345  *
3346  * Returns: %TRUE if successful, %FALSE otherwise.
3347  *
3348  * Since: 3.2
3349  **/
3350 gboolean
3351 e_cal_client_get_object_list_finish (ECalClient *client,
3352                                      GAsyncResult *result,
3353                                      GSList **icalcomps,
3354                                      GError **error)
3355 {
3356         gboolean res;
3357         gchar **out_strv = NULL;
3358
3359         g_return_val_if_fail (icalcomps != NULL, FALSE);
3360
3361         res = e_client_proxy_call_finish_strv (E_CLIENT (client), result, &out_strv, error, e_cal_client_get_object_list);
3362
3363         return complete_get_object_list (res, out_strv, icalcomps, error);
3364 }
3365
3366 /**
3367  * e_cal_client_get_object_list_sync:
3368  * @client: an #ECalClient
3369  * @sexp: an S-expression representing the query
3370  * @icalcomps: (out) (element-type icalcomponent): list of matching
3371  * #icalcomponent<!-- -->s
3372  * @cancellable: (allow-none): a #GCancellable; can be %NULL
3373  * @error: (out): a #GError to set an error, if any
3374  *
3375  * Gets a list of objects from the calendar that match the query specified
3376  * by the @sexp argument. The objects will be returned in the @icalcomps
3377  * argument, which is a list of #icalcomponent.
3378  * This list should be freed with e_cal_client_free_icalcomp_slist().
3379  *
3380  * Returns: %TRUE if successful, %FALSE otherwise.
3381  *
3382  * Since: 3.2
3383  **/
3384 gboolean
3385 e_cal_client_get_object_list_sync (ECalClient *client,
3386                                    const gchar *sexp,
3387                                    GSList **icalcomps,
3388                                    GCancellable *cancellable,
3389                                    GError **error)
3390 {
3391         gboolean res;
3392         gchar **out_strv = NULL, *gdbus_sexp = NULL;
3393
3394         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
3395         g_return_val_if_fail (sexp != NULL, FALSE);
3396         g_return_val_if_fail (icalcomps != NULL, FALSE);
3397
3398         if (client->priv->dbus_proxy == NULL) {
3399                 set_proxy_gone_error (error);
3400                 return FALSE;
3401         }
3402
3403         res = e_client_proxy_call_sync_string__strv (E_CLIENT (client), e_util_ensure_gdbus_string (sexp, &gdbus_sexp), &out_strv, cancellable, error, e_gdbus_cal_call_get_object_list_sync);
3404         g_free (gdbus_sexp);
3405
3406         return complete_get_object_list (res, out_strv, icalcomps, error);
3407 }
3408
3409 /**
3410  * e_cal_client_get_object_list_as_comps:
3411  * @client: an #ECalClient
3412  * @sexp: an S-expression representing the query
3413  * @cancellable: a #GCancellable; can be %NULL
3414  * @callback: callback to call when a result is ready
3415  * @user_data: user data for the @callback
3416  *
3417  * Gets a list of objects from the calendar that match the query specified
3418  * by the @sexp argument, returning matching objects as a list of #ECalComponent-s.
3419  * The call is finished by e_cal_client_get_object_list_as_comps_finish() from
3420  * the @callback.
3421  *
3422  * Since: 3.2
3423  **/
3424 void
3425 e_cal_client_get_object_list_as_comps (ECalClient *client,
3426                                        const gchar *sexp,
3427                                        GCancellable *cancellable,
3428                                        GAsyncReadyCallback callback,
3429                                        gpointer user_data)
3430 {
3431         gchar *gdbus_sexp = NULL;
3432
3433         g_return_if_fail (sexp != NULL);
3434
3435         e_client_proxy_call_string (
3436                 E_CLIENT (client), e_util_ensure_gdbus_string (sexp, &gdbus_sexp), cancellable, callback, user_data, e_cal_client_get_object_list_as_comps,
3437                 e_gdbus_cal_call_get_object_list,
3438                 NULL, NULL, NULL, e_gdbus_cal_call_get_object_list_finish, NULL);
3439
3440         g_free (gdbus_sexp);
3441 }
3442
3443 static gboolean
3444 complete_get_object_list_as_comps (gboolean res,
3445                                    gchar **out_strv,
3446                                    GSList **ecalcomps,
3447                                    GError **error)
3448 {
3449         GSList *icalcomps = NULL;
3450
3451         g_return_val_if_fail (ecalcomps != NULL, FALSE);
3452
3453         *ecalcomps = NULL;
3454
3455         res = complete_get_object_list (res, out_strv, &icalcomps, error);
3456
3457         if (res) {
3458                 GSList *iter;
3459
3460                 for (iter = icalcomps; iter; iter = iter->next) {
3461                         ECalComponent *comp;
3462
3463                         comp = e_cal_component_new ();
3464                         /* takes ownership of the icalcomp, thus free only the list at the end */
3465                         if (e_cal_component_set_icalcomponent (comp, iter->data))
3466                                 *ecalcomps = g_slist_prepend (*ecalcomps, comp);
3467                         else
3468                                 icalcomponent_free (iter->data);
3469                 }
3470
3471                 g_slist_free (icalcomps);
3472
3473                 *ecalcomps = g_slist_reverse (*ecalcomps);
3474         } else {
3475                 e_cal_client_free_icalcomp_slist (icalcomps);
3476         }
3477
3478         return res;
3479 }
3480
3481 /**
3482  * e_cal_client_get_object_list_as_comps_finish:
3483  * @client: an #ECalClient
3484  * @result: a #GAsyncResult
3485  * @ecalcomps: (out) (element-type ECalComponent): list of matching
3486  * #ECalComponent<!-- -->s
3487  * @error: (out): a #GError to set an error, if any
3488  *
3489  * Finishes previous call of e_cal_client_get_object_list_as_comps() and
3490  * sets @ecalcomps to a matching list of #ECalComponent-s.
3491  * This list should be freed with e_cal_client_free_ecalcomp_slist().
3492  *
3493  * Returns: %TRUE if successful, %FALSE otherwise.
3494  *
3495  * Since: 3.2
3496  **/
3497 gboolean
3498 e_cal_client_get_object_list_as_comps_finish (ECalClient *client,
3499                                               GAsyncResult *result,
3500                                               GSList **ecalcomps,
3501                                               GError **error)
3502 {
3503         gboolean res;
3504         gchar **out_strv = NULL;
3505
3506         g_return_val_if_fail (ecalcomps != NULL, FALSE);
3507
3508         res = e_client_proxy_call_finish_strv (E_CLIENT (client), result, &out_strv, error, e_cal_client_get_object_list_as_comps);
3509
3510         return complete_get_object_list_as_comps (res, out_strv, ecalcomps, error);
3511 }
3512
3513 /**
3514  * e_cal_client_get_object_list_as_comps_sync:
3515  * @client: an #ECalClient
3516  * @sexp: an S-expression representing the query
3517  * @ecalcomps: (out) (element-type ECalComponent): list of matching
3518  * #ECalComponent<!-- -->s
3519  * @cancellable: (allow-none): a #GCancellable; can be %NULL
3520  * @error: (out): a #GError to set an error, if any
3521  *
3522  * Gets a list of objects from the calendar that match the query specified
3523  * by the @sexp argument. The objects will be returned in the @ecalcomps
3524  * argument, which is a list of #ECalComponent.
3525  * This list should be freed with e_cal_client_free_ecalcomp_slist().
3526  *
3527  * Returns: %TRUE if successful, %FALSE otherwise.
3528  *
3529  * Since: 3.2
3530  **/
3531 gboolean
3532 e_cal_client_get_object_list_as_comps_sync (ECalClient *client,
3533                                             const gchar *sexp,
3534                                             GSList **ecalcomps,
3535                                             GCancellable *cancellable,
3536                                             GError **error)
3537 {
3538         gboolean res;
3539         gchar **out_strv = NULL, *gdbus_sexp = NULL;
3540
3541         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
3542         g_return_val_if_fail (sexp != NULL, FALSE);
3543         g_return_val_if_fail (ecalcomps != NULL, FALSE);
3544
3545         if (client->priv->dbus_proxy == NULL) {
3546                 set_proxy_gone_error (error);
3547                 return FALSE;
3548         }
3549
3550         res = e_client_proxy_call_sync_string__strv (E_CLIENT (client), e_util_ensure_gdbus_string (sexp, &gdbus_sexp), &out_strv, cancellable, error, e_gdbus_cal_call_get_object_list_sync);
3551         g_free (gdbus_sexp);
3552
3553         return complete_get_object_list_as_comps (res, out_strv, ecalcomps, error);
3554 }
3555
3556 /**
3557  * e_cal_client_get_free_busy:
3558  * @client: an #ECalClient
3559  * @start: Start time for query
3560  * @end: End time for query
3561  * @users: (element-type utf8): List of users to retrieve free/busy information for
3562  * @cancellable: (allow-none): a #GCancellable; can be %NULL
3563  * @callback: callback to call when a result is ready
3564  * @user_data: user data for the @callback
3565  *
3566  * Begins retrieval of free/busy information from the calendar server
3567  * as a list of #ECalComponent-s. Connect to "free-busy-data" signal
3568  * to receive chunks of free/busy components.
3569  * The call is finished by e_cal_client_get_free_busy_finish() from
3570  * the @callback.
3571  *
3572  * Since: 3.2
3573  **/
3574 void
3575 e_cal_client_get_free_busy (ECalClient *client,
3576                             time_t start,
3577                             time_t end,
3578                             const GSList *users,
3579                             GCancellable *cancellable,
3580                             GAsyncReadyCallback callback,
3581                             gpointer user_data)
3582 {
3583         gchar **strv;
3584
3585         g_return_if_fail (start > 0);
3586         g_return_if_fail (end > 0);
3587
3588         strv = e_gdbus_cal_encode_get_free_busy (start, end, users);
3589
3590         e_client_proxy_call_strv (
3591                 E_CLIENT (client), (const gchar * const *) strv, cancellable, callback, user_data, e_cal_client_get_free_busy,
3592                 e_gdbus_cal_call_get_free_busy,
3593                 e_gdbus_cal_call_get_free_busy_finish, NULL, NULL, NULL, NULL);
3594
3595         g_strfreev (strv);
3596 }
3597
3598 /**
3599  * e_cal_client_get_free_busy_finish:
3600  * @client: an #ECalClient
3601  * @result: a #GAsyncResult
3602  * @error: (out): a #GError to set an error, if any
3603  *
3604  * Finishes previous call of e_cal_client_get_free_busy().
3605  * All VFREEBUSY #ECalComponent-s were received by "free-busy-data" signal.
3606  *
3607  * Returns: %TRUE if successful, %FALSE otherwise.
3608  *
3609  * Since: 3.2
3610  **/
3611 gboolean
3612 e_cal_client_get_free_busy_finish (ECalClient *client,
3613                                    GAsyncResult *result,
3614                                    GError **error)
3615 {
3616         return e_client_proxy_call_finish_void (E_CLIENT (client), result, error, e_cal_client_get_free_busy);
3617 }
3618
3619 /**
3620  * e_cal_client_get_free_busy_sync:
3621  * @client: an #ECalClient
3622  * @start: Start time for query
3623  * @end: End time for query
3624  * @users: (element-type utf8): List of users to retrieve free/busy information for
3625  * @cancellable: (allow-none): a #GCancellable; can be %NULL
3626  * @error: (out): a #GError to set an error, if any
3627  *
3628  * Gets free/busy information from the calendar server.
3629  * All VFREEBUSY #ECalComponent-s were received by "free-busy-data" signal.
3630  *
3631  * Returns: %TRUE if successful, %FALSE otherwise.
3632  *
3633  * Since: 3.2
3634  **/
3635 gboolean
3636 e_cal_client_get_free_busy_sync (ECalClient *client,
3637                                  time_t start,
3638                                  time_t end,
3639                                  const GSList *users,
3640                                  GCancellable *cancellable,
3641                                  GError **error)
3642 {
3643         gboolean res;
3644         gchar **strv;
3645
3646         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
3647
3648         if (client->priv->dbus_proxy == NULL) {
3649                 set_proxy_gone_error (error);
3650                 return FALSE;
3651         }
3652
3653         strv = e_gdbus_cal_encode_get_free_busy (start, end, users);
3654         res = e_client_proxy_call_sync_strv__void (E_CLIENT (client), (const gchar * const *) strv, cancellable, error, e_gdbus_cal_call_get_free_busy_sync);
3655         g_strfreev (strv);
3656
3657         return res;
3658 }
3659
3660 /**
3661  * e_cal_client_create_object:
3662  * @client: an #ECalClient
3663  * @icalcomp: The component to create
3664  * @cancellable: a #GCancellable; can be %NULL
3665  * @callback: callback to call when a result is ready
3666  * @user_data: user data for the @callback
3667  *
3668  * Requests the calendar backend to create the object specified by the @icalcomp
3669  * argument. Some backends would assign a specific UID to the newly created object,
3670  * but this function does not modify the original @icalcomp if its UID changes.
3671  * The call is finished by e_cal_client_create_object_finish() from
3672  * the @callback.
3673  *
3674  * Since: 3.2
3675  **/
3676 void
3677 e_cal_client_create_object (ECalClient *client,
3678                             /* const */ icalcomponent *icalcomp,
3679                             GCancellable *cancellable,
3680                             GAsyncReadyCallback callback,
3681                             gpointer user_data)
3682 {
3683         gchar *comp_str, *gdbus_comp = NULL;
3684         const gchar *strv[2];
3685
3686         g_return_if_fail (icalcomp != NULL);
3687
3688         comp_str = icalcomponent_as_ical_string_r (icalcomp);
3689         strv[0] = e_util_ensure_gdbus_string (comp_str, &gdbus_comp);
3690         strv[1] = NULL;
3691
3692         g_return_if_fail (strv[0] != NULL);
3693
3694         e_client_proxy_call_strv (
3695                 E_CLIENT (client), strv, cancellable, callback, user_data, e_cal_client_create_object,
3696                 e_gdbus_cal_call_create_objects,
3697                 NULL, NULL, NULL, e_gdbus_cal_call_create_objects_finish, NULL);
3698
3699         g_free (comp_str);
3700         g_free (gdbus_comp);
3701 }
3702
3703 /**
3704  * e_cal_client_create_object_finish:
3705  * @client: an #ECalClient
3706  * @result: a #GAsyncResult
3707  * @uid: (out): Return value for the UID assigned to the new component by the calendar backend
3708  * @error: (out): a #GError to set an error, if any
3709  *
3710  * Finishes previous call of e_cal_client_create_object() and
3711  * sets @uid to newly assigned UID for the created object.
3712  * This @uid should be freed with g_free().
3713  *
3714  * Returns: %TRUE if successful, %FALSE otherwise.
3715  *
3716  * Since: 3.2
3717  **/
3718 gboolean
3719 e_cal_client_create_object_finish (ECalClient *client,
3720                                    GAsyncResult *result,
3721                                    gchar **uid,
3722                                    GError **error)
3723 {
3724         gboolean res;
3725         gchar **out_strings = NULL;
3726         gchar *out_string = NULL;
3727
3728         g_return_val_if_fail (uid != NULL, FALSE);
3729
3730         res = e_client_proxy_call_finish_strv (E_CLIENT (client), result, &out_strings, error, e_cal_client_create_object);
3731
3732         if (res && out_strings) {
3733                 out_string = g_strdup (out_strings[0]);
3734                 g_strfreev (out_strings);
3735         }
3736
3737         return complete_string_exchange (res, out_string, uid, error);
3738 }
3739
3740 /**
3741  * e_cal_client_create_object_sync:
3742  * @client: an #ECalClient
3743  * @icalcomp: The component to create
3744  * @uid: (out): Return value for the UID assigned to the new component by the calendar backend
3745  * @cancellable: a #GCancellable; can be %NULL
3746  * @error: (out): a #GError to set an error, if any
3747  *
3748  * Requests the calendar backend to create the object specified by the @icalcomp
3749  * argument. Some backends would assign a specific UID to the newly created object,
3750  * in those cases that UID would be returned in the @uid argument. This function
3751  * does not modify the original @icalcomp if its UID changes.
3752  * Returned @uid should be freed with g_free().
3753  *
3754  * Returns: %TRUE if successful, %FALSE otherwise.
3755  *
3756  * Since: 3.2
3757  **/
3758 gboolean
3759 e_cal_client_create_object_sync (ECalClient *client,
3760                                  /* const */ icalcomponent *icalcomp,
3761                                  gchar **uid,
3762                                  GCancellable *cancellable,
3763                                  GError **error)
3764 {
3765         gboolean res;
3766         gchar *comp_str, *gdbus_comp = NULL;
3767         const gchar *strv[2];
3768         gchar **out_strings = NULL;
3769         gchar *out_string = NULL;
3770
3771         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
3772         g_return_val_if_fail (icalcomp != NULL, FALSE);
3773         g_return_val_if_fail (uid != NULL, FALSE);
3774
3775         if (client->priv->dbus_proxy == NULL) {
3776                 set_proxy_gone_error (error);
3777                 return FALSE;
3778         }
3779
3780         comp_str = icalcomponent_as_ical_string_r (icalcomp);
3781         strv[0] = e_util_ensure_gdbus_string (comp_str, &gdbus_comp);
3782         strv[1] = NULL;
3783
3784         g_return_val_if_fail (strv[0] != NULL, FALSE);
3785
3786         res = e_client_proxy_call_sync_strv__strv (E_CLIENT (client), strv, &out_strings, cancellable, error, e_gdbus_cal_call_create_objects_sync);
3787
3788         g_free (comp_str);
3789         g_free (gdbus_comp);
3790
3791         if (res && out_strings) {
3792                 out_string = g_strdup (out_strings[0]);
3793                 g_strfreev (out_strings);
3794         }
3795
3796         return complete_string_exchange (res, out_string, uid, error);
3797 }
3798
3799 /**
3800  * e_cal_client_create_objects:
3801  * @client: an #ECalClient
3802  * @icalcomps: (element-type icalcomponent): The components to create
3803  * @cancellable: (allow-none): a #GCancellable; can be %NULL
3804  * @callback: callback to call when a result is ready
3805  * @user_data: user data for the @callback
3806  *
3807  * Requests the calendar backend to create the objects specified by the @icalcomps
3808  * argument. Some backends would assign a specific UID to the newly created object,
3809  * but this function does not modify the original @icalcomps if their UID changes.
3810  * The call is finished by e_cal_client_create_objects_finish() from
3811  * the @callback.
3812  *
3813  * Since: 3.6
3814  **/
3815 void
3816 e_cal_client_create_objects (ECalClient *client,
3817                              GSList *icalcomps,
3818                              GCancellable *cancellable,
3819                              GAsyncReadyCallback callback,
3820                              gpointer user_data)
3821 {
3822         gchar **array;
3823
3824         g_return_if_fail (E_IS_CAL_CLIENT (client));
3825         g_return_if_fail (icalcomps != NULL);
3826
3827         array = icalcomponent_slist_to_utf8_icomp_array (icalcomps);
3828
3829         e_client_proxy_call_strv (
3830                 E_CLIENT (client), (const gchar * const *) array, cancellable, callback, user_data, e_cal_client_create_objects,
3831                 e_gdbus_cal_call_create_objects,
3832                 NULL, NULL, NULL, e_gdbus_cal_call_create_objects_finish, NULL);
3833
3834         g_strfreev (array);
3835 }
3836
3837 /**
3838  * e_cal_client_create_objects_finish:
3839  * @client: an #ECalClient
3840  * @result: a #GAsyncResult
3841  * @uids: (out) (element-type utf8): Return value for the UIDs assigned to the
3842  * new components by the calendar backend
3843  * @error: (out): a #GError to set an error, if any
3844  *
3845  * Finishes previous call of e_cal_client_create_objects() and
3846  * sets @uids to newly assigned UIDs for the created objects.
3847  * This @uids should be freed with e_client_util_free_string_slist().
3848  *
3849  * Returns: %TRUE if successful, %FALSE otherwise.
3850  *
3851  * Since: 3.6
3852  **/
3853 gboolean
3854 e_cal_client_create_objects_finish (ECalClient *client,
3855                                     GAsyncResult *result,
3856                                     GSList **uids,
3857                                     GError **error)
3858 {
3859         gboolean res;
3860         gchar **out_strings = NULL;
3861
3862         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
3863         g_return_val_if_fail (uids != NULL, FALSE);
3864
3865         res = e_client_proxy_call_finish_strv (E_CLIENT (client), result, &out_strings, error, e_cal_client_create_objects);
3866
3867         return complete_strv_exchange (res, out_strings, uids, error);
3868 }
3869
3870 /**
3871  * e_cal_client_create_objects_sync:
3872  * @client: an #ECalClient
3873  * @icalcomps: (element-type icalcomponent): The components to create
3874  * @uids: (out) (element-type utf8): Return value for the UIDs assigned to the
3875  * new components by the calendar backend
3876  * @cancellable: (allow-none): a #GCancellable; can be %NULL
3877  * @error: (out): a #GError to set an error, if any
3878  *
3879  * Requests the calendar backend to create the objects specified by the @icalcomps
3880  * argument. Some backends would assign a specific UID to the newly created objects,
3881  * in those cases these UIDs would be returned in the @uids argument. This function
3882  * does not modify the original @icalcomps if their UID changes.
3883  * Returned @uid should be freed with e_client_util_free_string_slist().
3884  *
3885  * Returns: %TRUE if successful, %FALSE otherwise.
3886  *
3887  * Since: 3.6
3888  **/
3889 gboolean
3890 e_cal_client_create_objects_sync (ECalClient *client,
3891                                   GSList *icalcomps,
3892                                   GSList **uids,
3893                                   GCancellable *cancellable,
3894                                   GError **error)
3895 {
3896         gboolean res;
3897         gchar **array, **out_strings = NULL;
3898
3899         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
3900         g_return_val_if_fail (icalcomps != NULL, FALSE);
3901         g_return_val_if_fail (uids != NULL, FALSE);
3902
3903         if (client->priv->dbus_proxy == NULL) {
3904                 set_proxy_gone_error (error);
3905                 return FALSE;
3906         }
3907
3908         array = icalcomponent_slist_to_utf8_icomp_array (icalcomps);
3909
3910         res = e_client_proxy_call_sync_strv__strv (E_CLIENT (client), (const gchar * const *) array, &out_strings, cancellable, error, e_gdbus_cal_call_create_objects_sync);
3911
3912         return complete_strv_exchange (res, out_strings, uids, error);
3913 }
3914
3915 /**
3916  * e_cal_client_modify_object:
3917  * @client: an #ECalClient
3918  * @icalcomp: Component to modify
3919  * @mod: Type of modification
3920  * @cancellable: a #GCancellable; can be %NULL
3921  * @callback: callback to call when a result is ready
3922  * @user_data: user data for the @callback
3923  *
3924  * Requests the calendar backend to modify an existing object. If the object
3925  * does not exist on the calendar, an error will be returned.
3926  *
3927  * For recurrent appointments, the @mod argument specifies what to modify,
3928  * if all instances (CALOBJ_MOD_ALL), a single instance (CALOBJ_MOD_THIS),
3929  * or a specific set of instances (CALOBJ_MOD_THISNADPRIOR and
3930  * CALOBJ_MOD_THISANDFUTURE).
3931  *
3932  * The call is finished by e_cal_client_modify_object_finish() from
3933  * the @callback.
3934  *
3935  * Since: 3.2
3936  **/
3937 void
3938 e_cal_client_modify_object (ECalClient *client,
3939                             /* const */ icalcomponent *icalcomp,
3940                             CalObjModType mod,
3941                             GCancellable *cancellable,
3942                             GAsyncReadyCallback callback,
3943                             gpointer user_data)
3944 {
3945         gchar *comp_str, **strv;
3946         GSList comp_strings = {0,};
3947
3948         g_return_if_fail (icalcomp != NULL);
3949
3950         comp_str = icalcomponent_as_ical_string_r (icalcomp);
3951         comp_strings.data = comp_str;
3952         strv = e_gdbus_cal_encode_modify_objects (&comp_strings, mod);
3953
3954         e_client_proxy_call_strv (
3955                 E_CLIENT (client), (const gchar * const *) strv, cancellable, callback, user_data, e_cal_client_modify_object,
3956                 e_gdbus_cal_call_modify_objects,
3957                 e_gdbus_cal_call_modify_objects_finish, NULL, NULL, NULL, NULL);
3958
3959         g_strfreev (strv);
3960         g_free (comp_str);
3961 }
3962
3963 /**
3964  * e_cal_client_modify_object_finish:
3965  * @client: an #ECalClient
3966  * @result: a #GAsyncResult
3967  * @error: (out): a #GError to set an error, if any
3968  *
3969  * Finishes previous call of e_cal_client_modify_object().
3970  *
3971  * Returns: %TRUE if successful, %FALSE otherwise.
3972  *
3973  * Since: 3.2
3974  **/
3975 gboolean
3976 e_cal_client_modify_object_finish (ECalClient *client,
3977                                    GAsyncResult *result,
3978                                    GError **error)
3979 {
3980         return e_client_proxy_call_finish_void (E_CLIENT (client), result, error, e_cal_client_modify_object);
3981 }
3982
3983 /**
3984  * e_cal_client_modify_object_sync:
3985  * @client: an #ECalClient
3986  * @icalcomp: Component to modify
3987  * @mod: Type of modification
3988  * @cancellable: a #GCancellable; can be %NULL
3989  * @error: (out): a #GError to set an error, if any
3990  *
3991  * Requests the calendar backend to modify an existing object. If the object
3992  * does not exist on the calendar, an error will be returned.
3993  *
3994  * For recurrent appointments, the @mod argument specifies what to modify,
3995  * if all instances (CALOBJ_MOD_ALL), a single instance (CALOBJ_MOD_THIS),
3996  * or a specific set of instances (CALOBJ_MOD_THISNADPRIOR and
3997  * CALOBJ_MOD_THISANDFUTURE).
3998  *
3999  * Returns: %TRUE if successful, %FALSE otherwise.
4000  *
4001  * Since: 3.2
4002  **/
4003 gboolean
4004 e_cal_client_modify_object_sync (ECalClient *client,
4005                                  /* const */ icalcomponent *icalcomp,
4006                                  CalObjModType mod,
4007                                  GCancellable *cancellable,
4008                                  GError **error)
4009 {
4010         gboolean res;
4011         gchar *comp_str, **strv;
4012         GSList comp_strings = {0,};
4013
4014         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
4015         g_return_val_if_fail (icalcomp != NULL, FALSE);
4016
4017         if (client->priv->dbus_proxy == NULL) {
4018                 set_proxy_gone_error (error);
4019                 return FALSE;
4020         }
4021
4022         comp_str = icalcomponent_as_ical_string_r (icalcomp);
4023         comp_strings.data = comp_str;
4024         strv = e_gdbus_cal_encode_modify_objects (&comp_strings, mod);
4025
4026         res = e_client_proxy_call_sync_strv__void (E_CLIENT (client), (const gchar * const *) strv, cancellable, error, e_gdbus_cal_call_modify_objects_sync);
4027
4028         g_strfreev (strv);
4029         g_free (comp_str);
4030
4031         return res;
4032 }
4033
4034 /**
4035  * e_cal_client_modify_objects:
4036  * @client: an #ECalClient
4037  * @comps: (element-type icalcomponent): Components to modify
4038  * @mod: Type of modification
4039  * @cancellable: (allow-none): a #GCancellable; can be %NULL
4040  * @callback: callback to call when a result is ready
4041  * @user_data: user data for the @callback
4042  *
4043  * Requests the calendar backend to modify existing objects. If an object
4044  * does not exist on the calendar, an error will be returned.
4045  *
4046  * For recurrent appointments, the @mod argument specifies what to modify,
4047  * if all instances (CALOBJ_MOD_ALL), a single instance (CALOBJ_MOD_THIS),
4048  * or a specific set of instances (CALOBJ_MOD_THISNADPRIOR and
4049  * CALOBJ_MOD_THISANDFUTURE).
4050  *
4051  * The call is finished by e_cal_client_modify_objects_finish() from
4052  * the @callback.
4053  *
4054  * Since: 3.6
4055  **/
4056 void
4057 e_cal_client_modify_objects (ECalClient *client,
4058                              /* const */ GSList *comps,
4059                              CalObjModType mod,
4060                              GCancellable *cancellable,
4061                              GAsyncReadyCallback callback,
4062                              gpointer user_data)
4063 {
4064         GSList *comp_strings;
4065         gchar **strv;
4066
4067         g_return_if_fail (E_IS_CAL_CLIENT (client));
4068         g_return_if_fail (comps != NULL);
4069
4070         comp_strings = icalcomponent_slist_to_string_slist (comps);
4071         strv = e_gdbus_cal_encode_modify_objects (comp_strings, mod);
4072
4073         e_client_proxy_call_strv (
4074                 E_CLIENT (client), (const gchar * const *) strv, cancellable, callback, user_data, e_cal_client_modify_objects,
4075                 e_gdbus_cal_call_modify_objects,
4076                 e_gdbus_cal_call_modify_objects_finish, NULL, NULL, NULL, NULL);
4077
4078         g_strfreev (strv);
4079         e_client_util_free_string_slist (comp_strings);
4080 }
4081
4082 /**
4083  * e_cal_client_modify_objects_finish:
4084  * @client: an #ECalClient
4085  * @result: a #GAsyncResult
4086  * @error: (out): a #GError to set an error, if any
4087  *
4088  * Finishes previous call of e_cal_client_modify_objects().
4089  *
4090  * Returns: %TRUE if successful, %FALSE otherwise.
4091  *
4092  * Since: 3.6
4093  **/
4094 gboolean
4095 e_cal_client_modify_objects_finish (ECalClient *client,
4096                                     GAsyncResult *result,
4097                                     GError **error)
4098 {
4099         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
4100
4101         return e_client_proxy_call_finish_void (E_CLIENT (client), result, error, e_cal_client_modify_objects);
4102 }
4103
4104 /**
4105  * e_cal_client_modify_objects_sync:
4106  * @client: an #ECalClient
4107  * @comps: (element-type icalcomponent): Components to modify
4108  * @mod: Type of modification
4109  * @cancellable: (allow-none): a #GCancellable; can be %NULL
4110  * @error: (out): a #GError to set an error, if any
4111  *
4112  * Requests the calendar backend to modify existing objects. If an object
4113  * does not exist on the calendar, an error will be returned.
4114  *
4115  * For recurrent appointments, the @mod argument specifies what to modify,
4116  * if all instances (CALOBJ_MOD_ALL), a single instance (CALOBJ_MOD_THIS),
4117  * or a specific set of instances (CALOBJ_MOD_THISNADPRIOR and
4118  * CALOBJ_MOD_THISANDFUTURE).
4119  *
4120  * Returns: %TRUE if successful, %FALSE otherwise.
4121  *
4122  * Since: 3.6
4123  **/
4124 gboolean
4125 e_cal_client_modify_objects_sync (ECalClient *client,
4126                                   /* const */ GSList *comps,
4127                                   CalObjModType mod,
4128                                   GCancellable *cancellable,
4129                                   GError **error)
4130 {
4131         gboolean res;
4132         gchar **strv;
4133         GSList *comp_strings;
4134
4135         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
4136         g_return_val_if_fail (comps != NULL, FALSE);
4137
4138         if (client->priv->dbus_proxy == NULL) {
4139                 set_proxy_gone_error (error);
4140                 return FALSE;
4141         }
4142
4143         comp_strings = icalcomponent_slist_to_string_slist (comps);
4144         strv = e_gdbus_cal_encode_modify_objects (comp_strings, mod);
4145
4146         res = e_client_proxy_call_sync_strv__void (E_CLIENT (client), (const gchar * const *) strv, cancellable, error, e_gdbus_cal_call_modify_objects_sync);
4147
4148         g_strfreev (strv);
4149         e_client_util_free_string_slist (comp_strings);
4150
4151         return res;
4152 }
4153
4154 /**
4155  * e_cal_client_remove_object:
4156  * @client: an #ECalClient
4157  * @uid: UID of the object to remove
4158  * @rid: Recurrence ID of the specific recurrence to remove
4159  * @mod: Type of the removal
4160  * @cancellable: a #GCancellable; can be %NULL
4161  * @callback: callback to call when a result is ready
4162  * @user_data: user data for the @callback
4163  *
4164  * This function allows the removal of instances of a recurrent
4165  * appointment. By using a combination of the @uid, @rid and @mod
4166  * arguments, you can remove specific instances. If what you want
4167  * is to remove all instances, use #NULL @rid and CALOBJ_MOD_ALL
4168  * for the @mod.
4169  *
4170  * The call is finished by e_cal_client_remove_object_finish() from
4171  * the @callback.
4172  *
4173  * Since: 3.2
4174  **/
4175 void
4176 e_cal_client_remove_object (ECalClient *client,
4177                             const gchar *uid,
4178                             const gchar *rid,
4179                             CalObjModType mod,
4180                             GCancellable *cancellable,
4181                             GAsyncReadyCallback callback,
4182                             gpointer user_data)
4183 {
4184         gchar **strv;
4185         GSList ids = {0,};
4186         ECalComponentId id;
4187
4188         g_return_if_fail (uid != NULL);
4189
4190         id.uid = (gchar *) uid;
4191         id.rid = (gchar *) rid;
4192         ids.data = &id;
4193         strv = e_gdbus_cal_encode_remove_objects (&ids, mod);
4194
4195         e_client_proxy_call_strv (
4196                 E_CLIENT (client), (const gchar * const *) strv, cancellable, callback, user_data, e_cal_client_remove_object,
4197                 e_gdbus_cal_call_remove_objects,
4198                 e_gdbus_cal_call_remove_objects_finish, NULL, NULL, NULL, NULL);
4199
4200         g_strfreev (strv);
4201 }
4202
4203 /**
4204  * e_cal_client_remove_object_finish:
4205  * @client: an #ECalClient
4206  * @result: a #GAsyncResult
4207  * @error: (out): a #GError to set an error, if any
4208  *
4209  * Finishes previous call of e_cal_client_remove_object().
4210  *
4211  * Returns: %TRUE if successful, %FALSE otherwise.
4212  *
4213  * Since: 3.2
4214  **/
4215 gboolean
4216 e_cal_client_remove_object_finish (ECalClient *client,
4217                                    GAsyncResult *result,
4218                                    GError **error)
4219 {
4220         return e_client_proxy_call_finish_void (E_CLIENT (client), result, error, e_cal_client_remove_object);
4221 }
4222
4223 /**
4224  * e_cal_client_remove_object_sync:
4225  * @client: an #ECalClient
4226  * @uid: UID of the object to remove
4227  * @rid: Recurrence ID of the specific recurrence to remove
4228  * @mod: Type of the removal
4229  * @cancellable: a #GCancellable; can be %NULL
4230  * @error: (out): a #GError to set an error, if any
4231  *
4232  * This function allows the removal of instances of a recurrent
4233  * appointment. By using a combination of the @uid, @rid and @mod
4234  * arguments, you can remove specific instances. If what you want
4235  * is to remove all instances, use #NULL @rid and CALOBJ_MODE_THIS
4236  * for the @mod.
4237  *
4238  * Returns: %TRUE if successful, %FALSE otherwise.
4239  *
4240  * Since: 3.2
4241  **/
4242 gboolean
4243 e_cal_client_remove_object_sync (ECalClient *client,
4244                                  const gchar *uid,
4245                                  const gchar *rid,
4246                                  CalObjModType mod,
4247                                  GCancellable *cancellable,
4248                                  GError **error)
4249 {
4250         gboolean res;
4251         gchar **strv;
4252         GSList ids = {0,};
4253         ECalComponentId id;
4254
4255         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
4256         g_return_val_if_fail (uid != NULL, FALSE);
4257
4258         if (client->priv->dbus_proxy == NULL) {
4259                 set_proxy_gone_error (error);
4260                 return FALSE;
4261         }
4262
4263         id.uid = (gchar *) uid;
4264         id.rid = (gchar *) rid;
4265         ids.data = &id;
4266         strv = e_gdbus_cal_encode_remove_objects (&ids, mod);
4267
4268         res = e_client_proxy_call_sync_strv__void (E_CLIENT (client), (const gchar * const *) strv, cancellable, error, e_gdbus_cal_call_remove_objects_sync);
4269
4270         g_strfreev (strv);
4271
4272         return res;
4273 }
4274
4275 /**
4276  * e_cal_client_remove_objects:
4277  * @client: an #ECalClient
4278  * @ids: (element-type ECalComponentId): A list of #ECalComponentId objects
4279  * identifying the objects to remove
4280  * @mod: Type of the removal
4281  * @cancellable: a #GCancellable; can be %NULL
4282  * @callback: callback to call when a result is ready
4283  * @user_data: user data for the @callback
4284  *
4285  * This function allows the removal of instances of recurrent
4286  * appointments. #ECalComponentId objects can identify specific instances (if rid is not NULL).
4287  * If what you want is to remove all instances, use a #NULL rid in the #ECalComponentId and CALOBJ_MOD_ALL
4288  * for the @mod.
4289  *
4290  * The call is finished by e_cal_client_remove_objects_finish() from
4291  * the @callback.
4292  *
4293  * Since: 3.6
4294  **/
4295 void
4296 e_cal_client_remove_objects (ECalClient *client,
4297                              const GSList *ids,
4298                              CalObjModType mod,
4299                              GCancellable *cancellable,
4300                              GAsyncReadyCallback callback,
4301                              gpointer user_data)
4302 {
4303         gchar **strv;
4304
4305         g_return_if_fail (ids != NULL);
4306
4307         strv = e_gdbus_cal_encode_remove_objects (ids, mod);
4308
4309         e_client_proxy_call_strv (
4310                 E_CLIENT (client), (const gchar * const *) strv, cancellable, callback, user_data, e_cal_client_remove_objects,
4311                 e_gdbus_cal_call_remove_objects,
4312                 e_gdbus_cal_call_remove_objects_finish, NULL, NULL, NULL, NULL);
4313
4314         g_strfreev (strv);
4315 }
4316
4317 /**
4318  * e_cal_client_remove_objects_finish:
4319  * @client: an #ECalClient
4320  * @result: a #GAsyncResult
4321  * @error: (out): a #GError to set an error, if any
4322  *
4323  * Finishes previous call of e_cal_client_remove_objects().
4324  *
4325  * Returns: %TRUE if successful, %FALSE otherwise.
4326  *
4327  * Since: 3.6
4328  **/
4329 gboolean
4330 e_cal_client_remove_objects_finish (ECalClient *client,
4331                                     GAsyncResult *result,
4332                                     GError **error)
4333 {
4334         return e_client_proxy_call_finish_void (E_CLIENT (client), result, error, e_cal_client_remove_objects);
4335 }
4336
4337 /**
4338  * e_cal_client_remove_objects_sync:
4339  * @client: an #ECalClient
4340  * @ids: (element-type ECalComponentId): A list of #ECalComponentId objects
4341  * identifying the objects to remove
4342  * @mod: Type of the removal
4343  * @cancellable: (allow-none): a #GCancellable; can be %NULL
4344  * @error: (out): a #GError to set an error, if any
4345  *
4346  * This function allows the removal of instances of recurrent
4347  * appointments. #ECalComponentId objects can identify specific instances (if rid is not NULL).
4348  * If what you want is to remove all instances, use a #NULL rid in the #ECalComponentId and CALOBJ_MOD_ALL
4349  * for the @mod.
4350  *
4351  * Returns: %TRUE if successful, %FALSE otherwise.
4352  *
4353  * Since: 3.6
4354  **/
4355 gboolean
4356 e_cal_client_remove_objects_sync (ECalClient *client,
4357                                   const GSList *ids,
4358                                   CalObjModType mod,
4359                                   GCancellable *cancellable,
4360                                   GError **error)
4361 {
4362         gboolean res;
4363         gchar **strv;
4364
4365         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
4366         g_return_val_if_fail (ids != NULL, FALSE);
4367
4368         if (client->priv->dbus_proxy == NULL) {
4369                 set_proxy_gone_error (error);
4370                 return FALSE;
4371         }
4372
4373         strv = e_gdbus_cal_encode_remove_objects (ids, mod);
4374
4375         res = e_client_proxy_call_sync_strv__void (E_CLIENT (client), (const gchar * const *) strv, cancellable, error, e_gdbus_cal_call_remove_objects_sync);
4376
4377         g_strfreev (strv);
4378
4379         return res;
4380 }
4381
4382 /**
4383  * e_cal_client_receive_objects:
4384  * @client: an #ECalClient
4385  * @icalcomp: An #icalcomponent
4386  * @cancellable: a #GCancellable; can be %NULL
4387  * @callback: callback to call when a result is ready
4388  * @user_data: user data for the @callback
4389  *
4390  * Makes the backend receive the set of iCalendar objects specified in the
4391  * @icalcomp argument. This is used for iTIP confirmation/cancellation
4392  * messages for scheduled meetings.
4393  *
4394  * The call is finished by e_cal_client_receive_objects_finish() from
4395  * the @callback.
4396  *
4397  * Since: 3.2
4398  **/
4399 void
4400 e_cal_client_receive_objects (ECalClient *client,
4401                               /* const */ icalcomponent *icalcomp,
4402                               GCancellable *cancellable,
4403                               GAsyncReadyCallback callback,
4404                               gpointer user_data)
4405 {
4406         gchar *comp_str, *gdbus_comp = NULL;
4407
4408         g_return_if_fail (icalcomp != NULL);
4409
4410         comp_str = icalcomponent_as_ical_string_r (icalcomp);
4411
4412         e_client_proxy_call_string (
4413                 E_CLIENT (client), e_util_ensure_gdbus_string (comp_str, &gdbus_comp), cancellable, callback, user_data, e_cal_client_receive_objects,
4414                 e_gdbus_cal_call_receive_objects,
4415                 e_gdbus_cal_call_receive_objects_finish, NULL, NULL, NULL, NULL);
4416
4417         g_free (comp_str);
4418         g_free (gdbus_comp);
4419 }
4420
4421 /**
4422  * e_cal_client_receive_objects_finish:
4423  * @client: an #ECalClient
4424  * @result: a #GAsyncResult
4425  * @error: (out): a #GError to set an error, if any
4426  *
4427  * Finishes previous call of e_cal_client_receive_objects().
4428  *
4429  * Returns: %TRUE if successful, %FALSE otherwise.
4430  *
4431  * Since: 3.2
4432  **/
4433 gboolean
4434 e_cal_client_receive_objects_finish (ECalClient *client,
4435                                      GAsyncResult *result,
4436                                      GError **error)
4437 {
4438         return e_client_proxy_call_finish_void (E_CLIENT (client), result, error, e_cal_client_receive_objects);
4439 }
4440
4441 /**
4442  * e_cal_client_receive_objects_sync:
4443  * @client: an #ECalClient
4444  * @icalcomp: An #icalcomponent
4445  * @cancellable: a #GCancellable; can be %NULL
4446  * @error: (out): a #GError to set an error, if any
4447  *
4448  * Makes the backend receive the set of iCalendar objects specified in the
4449  * @icalcomp argument. This is used for iTIP confirmation/cancellation
4450  * messages for scheduled meetings.
4451  *
4452  * Returns: %TRUE if successful, %FALSE otherwise.
4453  *
4454  * Since: 3.2
4455  **/
4456 gboolean
4457 e_cal_client_receive_objects_sync (ECalClient *client,
4458                                    /* const */ icalcomponent *icalcomp,
4459                                    GCancellable *cancellable,
4460                                    GError **error)
4461 {
4462         gboolean res;
4463         gchar *comp_str, *gdbus_comp = NULL;
4464
4465         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
4466
4467         if (client->priv->dbus_proxy == NULL) {
4468                 set_proxy_gone_error (error);
4469                 return FALSE;
4470         }
4471
4472         comp_str = icalcomponent_as_ical_string_r (icalcomp);
4473
4474         res = e_client_proxy_call_sync_string__void (E_CLIENT (client), e_util_ensure_gdbus_string (comp_str, &gdbus_comp), cancellable, error, e_gdbus_cal_call_receive_objects_sync);
4475
4476         g_free (comp_str);
4477         g_free (gdbus_comp);
4478
4479         return res;
4480 }
4481
4482 /**
4483  * e_cal_client_send_objects:
4484  * @client: an #ECalClient
4485  * @icalcomp: An icalcomponent to be sent
4486  * @cancellable: a #GCancellable; can be %NULL
4487  * @callback: callback to call when a result is ready
4488  * @user_data: user data for the @callback
4489  *
4490  * Requests a calendar backend to send meeting information stored in @icalcomp.
4491  * The backend can modify this component and request a send to particular users.
4492  * The call is finished by e_cal_client_send_objects_finish() from
4493  * the @callback.
4494  *
4495  * Since: 3.2
4496  **/
4497 void
4498 e_cal_client_send_objects (ECalClient *client,
4499                            /* const */ icalcomponent *icalcomp,
4500                            GCancellable *cancellable,
4501                            GAsyncReadyCallback callback,
4502                            gpointer user_data)
4503 {
4504         gchar *comp_str, *gdbus_comp = NULL;
4505
4506         g_return_if_fail (icalcomp != NULL);
4507
4508         comp_str = icalcomponent_as_ical_string_r (icalcomp);
4509
4510         e_client_proxy_call_string (
4511                 E_CLIENT (client), e_util_ensure_gdbus_string (comp_str, &gdbus_comp), cancellable, callback, user_data, e_cal_client_send_objects,
4512                 e_gdbus_cal_call_send_objects,
4513                 NULL, NULL, NULL, e_gdbus_cal_call_send_objects_finish, NULL);
4514
4515         g_free (comp_str);
4516         g_free (gdbus_comp);
4517 }
4518
4519 static gboolean
4520 complete_send_objects (gboolean res,
4521                        gchar **out_strv,
4522                        GSList **users,
4523                        icalcomponent **modified_icalcomp,
4524                        GError **error)
4525 {
4526         g_return_val_if_fail (users != NULL, FALSE);
4527         g_return_val_if_fail (modified_icalcomp != NULL, FALSE);
4528
4529         *users = NULL;
4530         *modified_icalcomp = NULL;
4531
4532         if (res && out_strv) {
4533                 gchar *calobj = NULL;
4534
4535                 if (e_gdbus_cal_decode_send_objects ((const gchar * const *) out_strv, &calobj, users)) {
4536                         *modified_icalcomp = icalparser_parse_string (calobj);
4537                         if (!*modified_icalcomp) {
4538                                 g_propagate_error (error, e_cal_client_error_create (E_CAL_CLIENT_ERROR_INVALID_OBJECT, NULL));
4539                                 e_client_util_free_string_slist (*users);
4540                                 *users = NULL;
4541                                 res = FALSE;
4542                         }
4543                 } else {
4544                         g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_INVALID_ARG, NULL));
4545                         e_client_util_free_string_slist (*users);
4546                         *users = NULL;
4547                         res = FALSE;
4548                 }
4549
4550                 g_free (calobj);
4551         } else {
4552                 res = FALSE;
4553         }
4554
4555         g_strfreev (out_strv);
4556
4557         return res;
4558 }
4559
4560 /**
4561  * e_cal_client_send_objects_finish:
4562  * @client: an #ECalClient
4563  * @result: a #GAsyncResult
4564  * @users: (out) (element-type utf8): List of users to send
4565  * the @modified_icalcomp to
4566  * @modified_icalcomp: (out): Return value for the icalcomponent to be sent
4567  * @error: (out): a #GError to set an error, if any
4568  *
4569  * Finishes previous call of e_cal_client_send_objects() and
4570  * populates @users with a list of users to send @modified_icalcomp to.
4571  * The @users list should be freed with e_client_util_free_string_slist() and
4572  * the @modified_icalcomp should be freed with icalcomponent_free().
4573  *
4574  * Returns: %TRUE if successful, %FALSE otherwise.
4575  *
4576  * Since: 3.2
4577  **/
4578 gboolean
4579 e_cal_client_send_objects_finish (ECalClient *client,
4580                                   GAsyncResult *result,
4581                                   GSList **users,
4582                                   icalcomponent **modified_icalcomp,
4583                                   GError **error)
4584 {
4585         gboolean res;
4586         gchar **out_strv = NULL;
4587
4588         g_return_val_if_fail (users != NULL, FALSE);
4589         g_return_val_if_fail (modified_icalcomp != NULL, FALSE);
4590
4591         res = e_client_proxy_call_finish_strv (E_CLIENT (client), result, &out_strv, error, e_cal_client_send_objects);
4592
4593         return complete_send_objects (res, out_strv, users, modified_icalcomp, error);
4594 }
4595
4596 /**
4597  * e_cal_client_send_objects_sync:
4598  * @client: an #ECalClient
4599  * @icalcomp: An icalcomponent to be sent
4600  * @users: (out) (element-type utf8): List of users to send
4601  * the @modified_icalcomp to
4602  * @modified_icalcomp: (out): Return value for the icalcomponent to be sent
4603  * @cancellable: (allow-none): a #GCancellable; can be %NULL
4604  * @error: (out): a #GError to set an error, if any
4605  *
4606  * Requests a calendar backend to send meeting information stored in @icalcomp.
4607  * The backend can modify this component and request a send to users in the @users list.
4608  * The @users list should be freed with e_client_util_free_string_slist() and
4609  * the @modified_icalcomp should be freed with icalcomponent_free().
4610  *
4611  * Returns: %TRUE if successful, %FALSE otherwise.
4612  *
4613  * Since: 3.2
4614  **/
4615 gboolean
4616 e_cal_client_send_objects_sync (ECalClient *client,
4617                                 /* const */ icalcomponent *icalcomp,
4618                                 GSList **users,
4619                                 icalcomponent **modified_icalcomp,
4620                                 GCancellable *cancellable,
4621                                 GError **error)
4622 {
4623         gboolean res;
4624         gchar **out_strv = NULL, *comp_str, *gdbus_comp = NULL;
4625
4626         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
4627         g_return_val_if_fail (icalcomp != NULL, FALSE);
4628         g_return_val_if_fail (users != NULL, FALSE);
4629         g_return_val_if_fail (modified_icalcomp != NULL, FALSE);
4630
4631         if (client->priv->dbus_proxy == NULL) {
4632                 set_proxy_gone_error (error);
4633                 return FALSE;
4634         }
4635
4636         comp_str = icalcomponent_as_ical_string_r (icalcomp);
4637
4638         res = e_client_proxy_call_sync_string__strv (E_CLIENT (client), e_util_ensure_gdbus_string (comp_str, &gdbus_comp), &out_strv, cancellable, error, e_gdbus_cal_call_send_objects_sync);
4639
4640         g_free (comp_str);
4641         g_free (gdbus_comp);
4642
4643         return complete_send_objects (res, out_strv, users, modified_icalcomp, error);
4644 }
4645
4646 /**
4647  * e_cal_client_get_attachment_uris:
4648  * @client: an #ECalClient
4649  * @uid: Unique identifier for a calendar component
4650  * @rid: Recurrence identifier
4651  * @cancellable: a #GCancellable; can be %NULL
4652  * @callback: callback to call when a result is ready
4653  * @user_data: user data for the @callback
4654  *
4655  * Queries a calendar for a specified component's object attachment uris.
4656  * The call is finished by e_cal_client_get_attachment_uris_finish() from
4657  * the @callback.
4658  *
4659  * Since: 3.2
4660  **/
4661 void
4662 e_cal_client_get_attachment_uris (ECalClient *client,
4663                                   const gchar *uid,
4664                                   const gchar *rid,
4665                                   GCancellable *cancellable,
4666                                   GAsyncReadyCallback callback,
4667                                   gpointer user_data)
4668 {
4669         gchar **strv;
4670
4671         g_return_if_fail (uid != NULL);
4672
4673         strv = e_gdbus_cal_encode_get_attachment_uris (uid, rid);
4674
4675         e_client_proxy_call_strv (
4676                 E_CLIENT (client), (const gchar * const *) strv, cancellable, callback, user_data, e_cal_client_get_attachment_uris,
4677                 e_gdbus_cal_call_get_attachment_uris,
4678                 NULL, NULL, NULL, e_gdbus_cal_call_get_attachment_uris_finish, NULL);
4679
4680         g_strfreev (strv);
4681 }
4682
4683 /**
4684  * e_cal_client_get_attachment_uris_finish:
4685  * @client: an #ECalClient
4686  * @result: a #GAsyncResult
4687  * @attachment_uris: (out) (element-type utf8): Return the list of attachment
4688  * URIs
4689  * @error: (out): a #GError to set an error, if any
4690  *
4691  * Finishes previous call of e_cal_client_get_attachment_uris() and
4692  * sets @attachment_uris to uris for component's attachments.
4693  * The list should be freed with e_client_util_free_string_slist().
4694  *
4695  * Returns: %TRUE if successful, %FALSE otherwise.
4696  *
4697  * Since: 3.2
4698  **/
4699 gboolean
4700 e_cal_client_get_attachment_uris_finish (ECalClient *client,
4701                                          GAsyncResult *result,
4702                                          GSList **attachment_uris,
4703                                          GError **error)
4704 {
4705         gboolean res;
4706         gchar **out_strv = NULL;
4707
4708         g_return_val_if_fail (attachment_uris != NULL, FALSE);
4709
4710         res = e_client_proxy_call_finish_strv (E_CLIENT (client), result, &out_strv, error, e_cal_client_get_attachment_uris);
4711
4712         if (res && out_strv) {
4713                 *attachment_uris = e_client_util_strv_to_slist ((const gchar * const *) out_strv);
4714         } else {
4715                 *attachment_uris = NULL;
4716         }
4717
4718         g_strfreev (out_strv);
4719
4720         return res;
4721 }
4722
4723 /**
4724  * e_cal_client_get_attachment_uris_sync:
4725  * @client: an #ECalClient
4726  * @uid: Unique identifier for a calendar component
4727  * @rid: Recurrence identifier
4728  * @attachment_uris: (out) (element-type utf8): Return the list of attachment
4729  * URIs
4730  * @cancellable: (allow-none): a #GCancellable; can be %NULL
4731  * @error: (out): a #GError to set an error, if any
4732  *
4733  * Queries a calendar for a specified component's object attachment URIs.
4734  * The list should be freed with e_client_util_free_string_slist().
4735  *
4736  * Returns: %TRUE if successful, %FALSE otherwise.
4737  *
4738  * Since: 3.2
4739  **/
4740 gboolean
4741 e_cal_client_get_attachment_uris_sync (ECalClient *client,
4742                                        const gchar *uid,
4743                                        const gchar *rid,
4744                                        GSList **attachment_uris,
4745                                        GCancellable *cancellable,
4746                                        GError **error)
4747 {
4748         gboolean res;
4749         gchar **strv, **out_strv = NULL;
4750
4751         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
4752         g_return_val_if_fail (uid != NULL, FALSE);
4753         g_return_val_if_fail (attachment_uris != NULL, FALSE);
4754
4755         if (client->priv->dbus_proxy == NULL) {
4756                 set_proxy_gone_error (error);
4757                 return FALSE;
4758         }
4759
4760         strv = e_gdbus_cal_encode_get_attachment_uris (uid, rid);
4761
4762         res = e_client_proxy_call_sync_strv__strv (E_CLIENT (client), (const gchar * const *) strv, &out_strv, cancellable, error, e_gdbus_cal_call_get_attachment_uris_sync);
4763
4764         g_strfreev (strv);
4765
4766         if (res && out_strv) {
4767                 *attachment_uris = e_client_util_strv_to_slist ((const gchar * const *) out_strv);
4768         } else {
4769                 *attachment_uris = NULL;
4770         }
4771
4772         g_strfreev (out_strv);
4773
4774         return res;
4775 }
4776
4777 /**
4778  * e_cal_client_discard_alarm:
4779  * @client: an #ECalClient
4780  * @uid: Unique identifier for a calendar component
4781  * @rid: Recurrence identifier
4782  * @auid: Alarm identifier to remove
4783  * @cancellable: a #GCancellable; can be %NULL
4784  * @callback: callback to call when a result is ready
4785  * @user_data: user data for the @callback
4786  *
4787  * Removes alarm @auid from a given component identified by @uid and @rid.
4788  * The call is finished by e_cal_client_discard_alarm_finish() from
4789  * the @callback.
4790  *
4791  * Since: 3.2
4792  **/
4793 void
4794 e_cal_client_discard_alarm (ECalClient *client,
4795                             const gchar *uid,
4796                             const gchar *rid,
4797                             const gchar *auid,
4798                             GCancellable *cancellable,
4799                             GAsyncReadyCallback callback,
4800                             gpointer user_data)
4801 {
4802         gchar **strv;
4803
4804         g_return_if_fail (uid != NULL);
4805         g_return_if_fail (auid != NULL);
4806
4807         strv = e_gdbus_cal_encode_discard_alarm (uid, rid, auid);
4808
4809         e_client_proxy_call_strv (
4810                 E_CLIENT (client), (const gchar * const *) strv, cancellable, callback, user_data, e_cal_client_discard_alarm,
4811                 e_gdbus_cal_call_discard_alarm,
4812                 e_gdbus_cal_call_discard_alarm_finish, NULL, NULL, NULL, NULL);
4813
4814         g_strfreev (strv);
4815 }
4816
4817 /**
4818  * e_cal_client_discard_alarm_finish:
4819  * @client: an #ECalClient
4820  * @result: a #GAsyncResult
4821  * @error: (out): a #GError to set an error, if any
4822  *
4823  * Finishes previous call of e_cal_client_discard_alarm().
4824  *
4825  * Returns: %TRUE if successful, %FALSE otherwise.
4826  *
4827  * Since: 3.2
4828  **/
4829 gboolean
4830 e_cal_client_discard_alarm_finish (ECalClient *client,
4831                                    GAsyncResult *result,
4832                                    GError **error)
4833 {
4834         return e_client_proxy_call_finish_void (E_CLIENT (client), result, error, e_cal_client_discard_alarm);
4835 }
4836
4837 /**
4838  * e_cal_client_discard_alarm_sync:
4839  * @client: an #ECalClient
4840  * @uid: Unique identifier for a calendar component
4841  * @rid: Recurrence identifier
4842  * @auid: Alarm identifier to remove
4843  * @cancellable: a #GCancellable; can be %NULL
4844  * @error: (out): a #GError to set an error, if any
4845  *
4846  * Removes alarm @auid from a given component identified by @uid and @rid.
4847  *
4848  * Returns: %TRUE if successful, %FALSE otherwise.
4849  *
4850  * Since: 3.2
4851  **/
4852 gboolean
4853 e_cal_client_discard_alarm_sync (ECalClient *client,
4854                                  const gchar *uid,
4855                                  const gchar *rid,
4856                                  const gchar *auid,
4857                                  GCancellable *cancellable,
4858                                  GError **error)
4859 {
4860         gboolean res;
4861         gchar **strv;
4862
4863         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
4864         g_return_val_if_fail (uid != NULL, FALSE);
4865         g_return_val_if_fail (auid != NULL, FALSE);
4866
4867         if (client->priv->dbus_proxy == NULL) {
4868                 set_proxy_gone_error (error);
4869                 return FALSE;
4870         }
4871
4872         strv = e_gdbus_cal_encode_discard_alarm (uid, rid, auid);
4873
4874         res = e_client_proxy_call_sync_strv__void (E_CLIENT (client), (const gchar * const *) strv, cancellable, error, e_gdbus_cal_call_discard_alarm_sync);
4875
4876         g_strfreev (strv);
4877
4878         return res;
4879 }
4880
4881 /**
4882  * e_cal_client_get_view:
4883  * @client: an #ECalClient
4884  * @sexp: an S-expression representing the query.
4885  * @cancellable: a #GCancellable; can be %NULL
4886  * @callback: callback to call when a result is ready
4887  * @user_data: user data for the @callback
4888  *
4889  * Query @client with @sexp, creating an #ECalClientView.
4890  * The call is finished by e_cal_client_get_view_finish()
4891  * from the @callback.
4892  *
4893  * Since: 3.2
4894  **/
4895 void
4896 e_cal_client_get_view (ECalClient *client,
4897                        const gchar *sexp,
4898                        GCancellable *cancellable,
4899                        GAsyncReadyCallback callback,
4900                        gpointer user_data)
4901 {
4902         gchar *gdbus_sexp = NULL;
4903
4904         g_return_if_fail (sexp != NULL);
4905
4906         e_client_proxy_call_string (
4907                 E_CLIENT (client), e_util_ensure_gdbus_string (sexp, &gdbus_sexp), cancellable, callback, user_data, e_cal_client_get_view,
4908                 e_gdbus_cal_call_get_view,
4909                 NULL, NULL, e_gdbus_cal_call_get_view_finish, NULL, NULL);
4910
4911         g_free (gdbus_sexp);
4912 }
4913
4914 static gboolean
4915 complete_get_view (ECalClient *client,
4916                    gboolean res,
4917                    gchar *view_path,
4918                    ECalClientView **view,
4919                    GError **error)
4920 {
4921         g_return_val_if_fail (view != NULL, FALSE);
4922
4923         if (view_path && res && cal_factory) {
4924                 GDBusConnection *connection;
4925                 GError *local_error = NULL;
4926
4927                 connection = g_dbus_proxy_get_connection (
4928                         G_DBUS_PROXY (cal_factory));
4929
4930                 *view = g_initable_new (
4931                         E_TYPE_CAL_CLIENT_VIEW,
4932                         NULL, &local_error,
4933                         "client", client,
4934                         "connection", connection,
4935                         "object-path", view_path,
4936                         NULL);
4937
4938                 if (local_error != NULL) {
4939                         unwrap_dbus_error (local_error, error);
4940                         res = FALSE;
4941                 }
4942         } else {
4943                 *view = NULL;
4944                 res = FALSE;
4945         }
4946
4947         if (!*view && error && !*error)
4948                 g_set_error_literal (error, E_CLIENT_ERROR, E_CLIENT_ERROR_DBUS_ERROR, _("Cannot get connection to view"));
4949
4950         g_free (view_path);
4951
4952         return res;
4953 }
4954
4955 /**
4956  * e_cal_client_get_view_finish:
4957  * @client: an #ECalClient
4958  * @result: a #GAsyncResult
4959  * @view: (out) an #ECalClientView
4960  * @error: (out): a #GError to set an error, if any
4961  *
4962  * Finishes previous call of e_cal_client_get_view().
4963  * If successful, then the @view is set to newly allocated #ECalClientView,
4964  * which should be freed with g_object_unref().
4965  *
4966  * Returns: %TRUE if successful, %FALSE otherwise.
4967  *
4968  * Since: 3.2
4969  **/
4970 gboolean
4971 e_cal_client_get_view_finish (ECalClient *client,
4972                               GAsyncResult *result,
4973                               ECalClientView **view,
4974                               GError **error)
4975 {
4976         gboolean res;
4977         gchar *view_path = NULL;
4978
4979         g_return_val_if_fail (view != NULL, FALSE);
4980
4981         res = e_client_proxy_call_finish_string (E_CLIENT (client), result, &view_path, error, e_cal_client_get_view);
4982
4983         return complete_get_view (client, res, view_path, view, error);
4984 }
4985
4986 /**
4987  * e_cal_client_get_view_sync:
4988  * @client: an #ECalClient
4989  * @sexp: an S-expression representing the query.
4990  * @view: (out) an #ECalClientView
4991  * @cancellable: a #GCancellable; can be %NULL
4992  * @error: (out): a #GError to set an error, if any
4993  *
4994  * Query @client with @sexp, creating an #ECalClientView.
4995  * If successful, then the @view is set to newly allocated #ECalClientView,
4996  * which should be freed with g_object_unref().
4997  *
4998  * Returns: %TRUE if successful, %FALSE otherwise.
4999  *
5000  * Since: 3.2
5001  **/
5002 gboolean
5003 e_cal_client_get_view_sync (ECalClient *client,
5004                             const gchar *sexp,
5005                             ECalClientView **view,
5006                             GCancellable *cancellable,
5007                             GError **error)
5008 {
5009         gboolean res;
5010         gchar *gdbus_sexp = NULL;
5011         gchar *view_path = NULL;
5012
5013         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
5014         g_return_val_if_fail (sexp != NULL, FALSE);
5015         g_return_val_if_fail (view != NULL, FALSE);
5016
5017         if (client->priv->dbus_proxy == NULL) {
5018                 set_proxy_gone_error (error);
5019                 return FALSE;
5020         }
5021
5022         res = e_client_proxy_call_sync_string__string (E_CLIENT (client), e_util_ensure_gdbus_string (sexp, &gdbus_sexp), &view_path, cancellable, error, e_gdbus_cal_call_get_view_sync);
5023
5024         g_free (gdbus_sexp);
5025
5026         return complete_get_view (client, res, view_path, view, error);
5027 }
5028
5029 static gboolean
5030 cal_client_get_timezone_from_cache_finish (ECalClient *client,
5031                                            GAsyncResult *result,
5032                                            icaltimezone **zone,
5033                                            GError **error)
5034 {
5035         GSimpleAsyncResult *simple;
5036         GError *local_error = NULL;
5037
5038         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
5039         g_return_val_if_fail (result != NULL, FALSE);
5040         g_return_val_if_fail (zone != NULL, FALSE);
5041         g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (client), e_timezone_cache_get_timezone), FALSE);
5042
5043         simple = G_SIMPLE_ASYNC_RESULT (result);
5044
5045         if (g_simple_async_result_propagate_error (simple, &local_error)) {
5046                 e_client_unwrap_dbus_error (E_CLIENT (client), local_error, error);
5047                 return FALSE;
5048         }
5049
5050         *zone = g_simple_async_result_get_op_res_gpointer (simple);
5051
5052         return *zone != NULL;
5053 }
5054
5055 /**
5056  * e_cal_client_get_timezone:
5057  * @client: an #ECalClient
5058  * @tzid: ID of the timezone to retrieve
5059  * @cancellable: a #GCancellable; can be %NULL
5060  * @callback: callback to call when a result is ready
5061  * @user_data: user data for the @callback
5062  *
5063  * Retrieves a timezone object from the calendar backend.
5064  * The call is finished by e_cal_client_get_timezone_finish() from
5065  * the @callback.
5066  *
5067  * Since: 3.2
5068  **/
5069 void
5070 e_cal_client_get_timezone (ECalClient *client,
5071                            const gchar *tzid,
5072                            GCancellable *cancellable,
5073                            GAsyncReadyCallback callback,
5074                            gpointer user_data)
5075 {
5076         gchar *gdbus_tzid = NULL;
5077         icaltimezone *zone;
5078
5079         g_return_if_fail (tzid != NULL);
5080
5081         zone = e_timezone_cache_get_timezone (
5082                 E_TIMEZONE_CACHE (client), tzid);
5083         if (zone) {
5084                 e_client_finish_async_without_dbus (E_CLIENT (client), cancellable, callback, user_data, e_timezone_cache_get_timezone, zone, NULL);
5085         } else {
5086                 e_client_proxy_call_string (
5087                         E_CLIENT (client), e_util_ensure_gdbus_string (tzid, &gdbus_tzid), cancellable, callback, user_data, e_cal_client_get_timezone,
5088                         e_gdbus_cal_call_get_timezone,
5089                         NULL, NULL, e_gdbus_cal_call_get_timezone_finish, NULL, NULL);
5090
5091                 g_free (gdbus_tzid);
5092         }
5093 }
5094
5095 static gboolean
5096 complete_get_timezone (ECalClient *client,
5097                        gboolean res,
5098                        gchar *out_string,
5099                        icaltimezone **zone,
5100                        GError **error)
5101 {
5102         g_return_val_if_fail (zone != NULL, FALSE);
5103
5104         *zone = NULL;
5105
5106         if (res && out_string) {
5107                 icalcomponent *icalcomp;
5108
5109                 icalcomp = icalparser_parse_string (out_string);
5110                 if (icalcomp) {
5111                         *zone = icaltimezone_new ();
5112                         if (!icaltimezone_set_component (*zone, icalcomp)) {
5113                                 icaltimezone_free (*zone, 1);
5114                                 icalcomponent_free (icalcomp);
5115                                 res = FALSE;
5116                                 g_propagate_error (error, e_cal_client_error_create (E_CAL_CLIENT_ERROR_INVALID_OBJECT, NULL));
5117                         } else {
5118                                 const gchar *tzid;
5119
5120                                 tzid = icaltimezone_get_tzid (*zone);
5121
5122                                 /* Add the timezone to the cache directly,
5123                                  * otherwise we'd have to free this struct
5124                                  * and fetch the cached copy. */
5125                                 g_mutex_lock (&client->priv->zone_cache_lock);
5126                                 g_hash_table_insert (
5127                                         client->priv->zone_cache,
5128                                         g_strdup (tzid), *zone);
5129                                 g_mutex_unlock (&client->priv->zone_cache_lock);
5130                         }
5131                 } else {
5132                         res = FALSE;
5133                         g_propagate_error (error, e_cal_client_error_create (E_CAL_CLIENT_ERROR_INVALID_OBJECT, NULL));
5134                 }
5135         } else {
5136                 res = FALSE;
5137         }
5138
5139         g_free (out_string);
5140
5141         return res;
5142 }
5143
5144 /**
5145  * e_cal_client_get_timezone_finish:
5146  * @client: an #ECalClient
5147  * @result: a #GAsyncResult
5148  * @zone: (out): Return value for the timezone
5149  * @error: (out): a #GError to set an error, if any
5150  *
5151  * Finishes previous call of e_cal_client_get_timezone() and
5152  * sets @zone to a retrieved timezone object from the calendar backend.
5153  * This object is owned by the @client, thus do not free it.
5154  *
5155  * Returns: %TRUE if successful, %FALSE otherwise.
5156  *
5157  * Since: 3.2
5158  **/
5159 gboolean
5160 e_cal_client_get_timezone_finish (ECalClient *client,
5161                                   GAsyncResult *result,
5162                                   icaltimezone **zone,
5163                                   GError **error)
5164 {
5165         gboolean res;
5166         gchar *out_string = NULL;
5167
5168         g_return_val_if_fail (zone != NULL, FALSE);
5169
5170         if (g_simple_async_result_get_source_tag (G_SIMPLE_ASYNC_RESULT (result)) == e_timezone_cache_get_timezone) {
5171                 res = cal_client_get_timezone_from_cache_finish (client, result, zone, error);
5172         } else {
5173                 res = e_client_proxy_call_finish_string (E_CLIENT (client), result, &out_string, error, e_cal_client_get_timezone);
5174                 res = complete_get_timezone (client, res, out_string, zone, error);
5175         }
5176
5177         return res;
5178 }
5179
5180 /**
5181  * e_cal_client_get_timezone_sync:
5182  * @client: an #ECalClient
5183  * @tzid: ID of the timezone to retrieve
5184  * @zone: (out): Return value for the timezone
5185  * @cancellable: a #GCancellable; can be %NULL
5186  * @error: (out): a #GError to set an error, if any
5187  *
5188  * Retrieves a timezone object from the calendar backend.
5189  * This object is owned by the @client, thus do not free it.
5190  *
5191  * Returns: %TRUE if successful, %FALSE otherwise.
5192  *
5193  * Since: 3.2
5194  **/
5195 gboolean
5196 e_cal_client_get_timezone_sync (ECalClient *client,
5197                                 const gchar *tzid,
5198                                 icaltimezone **zone,
5199                                 GCancellable *cancellable,
5200                                 GError **error)
5201 {
5202         gboolean res;
5203         gchar *gdbus_tzid = NULL, *out_string = NULL;
5204
5205         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
5206         g_return_val_if_fail (tzid != NULL, FALSE);
5207         g_return_val_if_fail (zone != NULL, FALSE);
5208
5209         if (client->priv->dbus_proxy == NULL) {
5210                 set_proxy_gone_error (error);
5211                 return FALSE;
5212         }
5213
5214         *zone = e_timezone_cache_get_timezone (
5215                 E_TIMEZONE_CACHE (client), tzid);
5216         if (*zone)
5217                 return TRUE;
5218
5219         res = e_client_proxy_call_sync_string__string (E_CLIENT (client), e_util_ensure_gdbus_string (tzid, &gdbus_tzid), &out_string, cancellable, error, e_gdbus_cal_call_get_timezone_sync);
5220
5221         g_free (gdbus_tzid);
5222
5223         return complete_get_timezone (client, res, out_string, zone, error);
5224 }
5225
5226 /**
5227  * e_cal_client_add_timezone:
5228  * @client: an #ECalClient
5229  * @zone: The timezone to add
5230  * @cancellable: a #GCancellable; can be %NULL
5231  * @callback: callback to call when a result is ready
5232  * @user_data: user data for the @callback
5233  *
5234  * Add a VTIMEZONE object to the given calendar client.
5235  * The call is finished by e_cal_client_add_timezone_finish() from
5236  * the @callback.
5237  *
5238  * Since: 3.2
5239  **/
5240 void
5241 e_cal_client_add_timezone (ECalClient *client,
5242                            /* const */ icaltimezone *zone,
5243                            GCancellable *cancellable,
5244                            GAsyncReadyCallback callback,
5245                            gpointer user_data)
5246 {
5247         icalcomponent *icalcomp;
5248         gchar *zone_str, *gdbus_zone = NULL;
5249
5250         g_return_if_fail (zone != NULL);
5251
5252         if (zone == icaltimezone_get_utc_timezone ())
5253                 return;
5254
5255         icalcomp = icaltimezone_get_component (zone);
5256         g_return_if_fail (icalcomp != NULL);
5257
5258         zone_str = icalcomponent_as_ical_string_r (icalcomp);
5259
5260         e_client_proxy_call_string (
5261                 E_CLIENT (client), e_util_ensure_gdbus_string (zone_str, &gdbus_zone), cancellable, callback, user_data, e_cal_client_add_timezone,
5262                 e_gdbus_cal_call_add_timezone,
5263                 e_gdbus_cal_call_add_timezone_finish, NULL, NULL, NULL, NULL);
5264
5265         g_free (zone_str);
5266         g_free (gdbus_zone);
5267 }
5268
5269 /**
5270  * e_cal_client_add_timezone_finish:
5271  * @client: an #ECalClient
5272  * @result: a #GAsyncResult
5273  * @error: (out): a #GError to set an error, if any
5274  *
5275  * Finishes previous call of e_cal_client_add_timezone().
5276  *
5277  * Returns: %TRUE if successful, %FALSE otherwise.
5278  *
5279  * Since: 3.2
5280  **/
5281 gboolean
5282 e_cal_client_add_timezone_finish (ECalClient *client,
5283                                   GAsyncResult *result,
5284                                   GError **error)
5285 {
5286         return e_client_proxy_call_finish_void (E_CLIENT (client), result, error, e_cal_client_add_timezone);
5287 }
5288
5289 /**
5290  * e_cal_client_add_timezone_sync:
5291  * @client: an #ECalClient
5292  * @zone: The timezone to add
5293  * @cancellable: a #GCancellable; can be %NULL
5294  * @error: (out): a #GError to set an error, if any
5295  *
5296  * Add a VTIMEZONE object to the given calendar client.
5297  *
5298  * Returns: %TRUE if successful, %FALSE otherwise.
5299  *
5300  * Since: 3.2
5301  **/
5302 gboolean
5303 e_cal_client_add_timezone_sync (ECalClient *client,
5304                                 /* const */ icaltimezone *zone,
5305                                 GCancellable *cancellable,
5306                                 GError **error)
5307 {
5308         gboolean res;
5309         icalcomponent *icalcomp;
5310         gchar *zone_str, *gdbus_zone = NULL;
5311
5312         g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
5313         g_return_val_if_fail (zone != NULL, FALSE);
5314
5315         if (zone == icaltimezone_get_utc_timezone ())
5316                 return TRUE;
5317
5318         icalcomp = icaltimezone_get_component (zone);
5319         if (!icalcomp) {
5320                 g_propagate_error (error, e_client_error_create (E_CLIENT_ERROR_INVALID_ARG, NULL));
5321                 return FALSE;
5322         }
5323
5324         if (client->priv->dbus_proxy == NULL) {
5325                 set_proxy_gone_error (error);
5326                 return FALSE;
5327         }
5328
5329         zone_str = icalcomponent_as_ical_string_r (icalcomp);
5330
5331         res = e_client_proxy_call_sync_string__void (E_CLIENT (client), e_util_ensure_gdbus_string (zone_str, &gdbus_zone), cancellable, error, e_gdbus_cal_call_add_timezone_sync);
5332
5333         g_free (zone_str);
5334         g_free (gdbus_zone);
5335
5336         return res;
5337 }
5338