Adapt libedata-cal to the new ESource API.
[platform/upstream/evolution-data-server.git] / calendar / libedata-cal / e-data-cal-factory.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* Evolution calendar factory
3  *
4  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
5  * Copyright (C) 2009 Intel Corporation
6  *
7  * Authors:
8  *   Federico Mena-Quintero <federico@ximian.com>
9  *   JP Rosevear <jpr@ximian.com>
10  *   Ross Burton <ross@linux.intel.com>
11  *
12  * This program is free software; you can redistribute it and/or
13  * modify it under the terms of version 2 of the GNU Lesser General Public
14  * License as published by the Free Software Foundation.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24  */
25
26 #include <config.h>
27 #include <locale.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <glib/gi18n.h>
32
33 #include <libedataserver/e-source-calendar.h>
34
35 #include "e-cal-backend.h"
36 #include "e-cal-backend-factory.h"
37 #include "e-data-cal.h"
38 #include "e-data-cal-factory.h"
39
40 #include "e-gdbus-cal-factory.h"
41
42 #include <libical/ical.h>
43
44 #define d(x)
45
46 #define E_DATA_CAL_FACTORY_GET_PRIVATE(obj) \
47         (G_TYPE_INSTANCE_GET_PRIVATE \
48         ((obj), E_TYPE_DATA_CAL_FACTORY, EDataCalFactoryPrivate))
49
50 struct _EDataCalFactoryPrivate {
51         ESourceRegistry *registry;
52         EGdbusCalFactory *gdbus_object;
53
54         GMutex *calendars_lock;
55         /* A hash of object paths for calendar URIs to EDataCals */
56         GHashTable *calendars;
57
58         GMutex *connections_lock;
59         /* This is a hash of client addresses to GList* of EDataCals */
60         GHashTable *connections;
61 };
62
63 enum {
64         PROP_0,
65         PROP_REGISTRY
66 };
67
68 /* Forward Declarations */
69 static void     e_data_cal_factory_initable_init
70                                                 (GInitableIface *interface);
71
72 G_DEFINE_TYPE_WITH_CODE (
73         EDataCalFactory,
74         e_data_cal_factory,
75         E_TYPE_DATA_FACTORY,
76         G_IMPLEMENT_INTERFACE (
77                 G_TYPE_INITABLE,
78                 e_data_cal_factory_initable_init))
79
80 static EBackend *
81 data_cal_factory_ref_backend (EDataFactory *factory,
82                               ESource *source,
83                               EDataCalObjType type,
84                               GError **error)
85 {
86         EBackend *backend;
87         ESourceBackend *extension;
88         const gchar *extension_name;
89         const gchar *type_string;
90         gchar *backend_name;
91         gchar *hash_key;
92
93         switch (type) {
94                 case Event:
95                         extension_name = E_SOURCE_EXTENSION_CALENDAR;
96                         type_string = "VEVENT";
97                         break;
98                 case Todo:
99                         extension_name = E_SOURCE_EXTENSION_TASK_LIST;
100                         type_string = "VTODO";
101                         break;
102                 case Journal:
103                         extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
104                         type_string = "VJOURNAL";
105                         break;
106                 default:
107                         g_return_val_if_reached (NULL);
108         }
109
110         extension = e_source_get_extension (source, extension_name);
111         backend_name = e_source_backend_dup_backend_name (extension);
112
113         if (backend_name == NULL || *backend_name == '\0') {
114                 g_set_error (
115                         error, E_DATA_CAL_ERROR, NoSuchCal,
116                         _("No backend name in source '%s'"),
117                         e_source_get_display_name (source));
118                 g_free (backend_name);
119                 return NULL;
120         }
121
122         hash_key = g_strdup_printf ("%s:%s", backend_name, type_string);
123         backend = e_data_factory_ref_backend (factory, hash_key, source);
124         g_free (hash_key);
125
126         if (backend == NULL)
127                 g_set_error (
128                         error, E_DATA_CAL_ERROR, NoSuchCal,
129                         _("Invalid backend name '%s' in source '%s'"),
130                         backend_name, e_source_get_display_name (source));
131
132         g_free (backend_name);
133
134         return backend;
135 }
136
137 static gchar *
138 construct_cal_factory_path (void)
139 {
140         static volatile gint counter = 1;
141
142         g_atomic_int_inc (&counter);
143
144         return g_strdup_printf (
145                 "/org/gnome/evolution/dataserver/Calendar/%d/%u",
146                 getpid (), counter);
147 }
148
149 static gboolean
150 remove_dead_calendar_cb (gpointer path,
151                          gpointer calendar,
152                          gpointer dead_calendar)
153 {
154         return calendar == dead_calendar;
155 }
156
157 static void
158 calendar_freed_cb (EDataCalFactory *factory,
159                    GObject *dead)
160 {
161         EDataCalFactoryPrivate *priv = factory->priv;
162         GHashTableIter iter;
163         gpointer hkey, hvalue;
164
165         d (g_debug ("in factory %p (%p) is dead", factory, dead));
166
167         g_mutex_lock (priv->calendars_lock);
168         g_mutex_lock (priv->connections_lock);
169
170         g_hash_table_foreach_remove (
171                 priv->calendars, remove_dead_calendar_cb, dead);
172
173         g_hash_table_iter_init (&iter, priv->connections);
174         while (g_hash_table_iter_next (&iter, &hkey, &hvalue)) {
175                 GList *calendars = hvalue;
176
177                 if (g_list_find (calendars, dead)) {
178                         calendars = g_list_remove (calendars, dead);
179                         if (calendars != NULL)
180                                 g_hash_table_insert (
181                                         priv->connections,
182                                         g_strdup (hkey), calendars);
183                         else
184                                 g_hash_table_remove (priv->connections, hkey);
185
186                         break;
187                 }
188         }
189
190         g_mutex_unlock (priv->connections_lock);
191         g_mutex_unlock (priv->calendars_lock);
192
193         e_dbus_server_release (E_DBUS_SERVER (factory));
194 }
195
196 static gboolean
197 impl_CalFactory_get_cal (EGdbusCalFactory *object,
198                          GDBusMethodInvocation *invocation,
199                          const gchar * const *in_source_type,
200                          EDataCalFactory *factory)
201 {
202         EDataCal *calendar;
203         EBackend *backend;
204         EDataCalFactoryPrivate *priv = factory->priv;
205         GDBusConnection *connection;
206         ESourceRegistry *registry;
207         ESource *source;
208         gchar *path = NULL;
209         const gchar *sender;
210         GList *list;
211         GError *error = NULL;
212         gchar *uid = NULL;
213         guint type = 0;
214
215         sender = g_dbus_method_invocation_get_sender (invocation);
216         connection = g_dbus_method_invocation_get_connection (invocation);
217
218         registry = e_data_cal_factory_get_registry (factory);
219
220         if (!e_gdbus_cal_factory_decode_get_cal (in_source_type, &uid, &type)) {
221                 error = g_error_new (
222                         E_DATA_CAL_ERROR, NoSuchCal, _("Invalid call"));
223                 g_dbus_method_invocation_return_gerror (invocation, error);
224                 g_error_free (error);
225
226                 return TRUE;
227         }
228
229         if (uid == NULL || *uid == '\0') {
230                 error = g_error_new_literal (
231                         E_DATA_CAL_ERROR, NoSuchCal,
232                         _("Missing source UID"));
233                 g_dbus_method_invocation_return_gerror (invocation, error);
234                 g_error_free (error);
235                 g_free (uid);
236
237                 return TRUE;
238         }
239
240         source = e_source_registry_ref_source (registry, uid);
241
242         if (source == NULL) {
243                 error = g_error_new (
244                         E_DATA_CAL_ERROR, NoSuchCal,
245                         _("No such source for UID '%s'"), uid);
246                 g_dbus_method_invocation_return_gerror (invocation, error);
247                 g_error_free (error);
248                 g_free (uid);
249
250                 return TRUE;
251         }
252
253         backend = data_cal_factory_ref_backend (
254                 E_DATA_FACTORY (factory), source, type, &error);
255
256         g_object_unref (source);
257
258         if (error != NULL) {
259                 g_dbus_method_invocation_return_gerror (invocation, error);
260                 g_error_free (error);
261
262                 return TRUE;
263         }
264
265         g_return_val_if_fail (E_IS_BACKEND (backend), FALSE);
266
267         g_mutex_lock (priv->calendars_lock);
268
269         e_dbus_server_hold (E_DBUS_SERVER (factory));
270
271         path = construct_cal_factory_path ();
272         calendar = e_data_cal_new (E_CAL_BACKEND (backend));
273         g_hash_table_insert (priv->calendars, g_strdup (path), calendar);
274         e_cal_backend_add_client (E_CAL_BACKEND (backend), calendar);
275         e_data_cal_register_gdbus_object (calendar, connection, path, &error);
276         g_object_weak_ref (
277                 G_OBJECT (calendar), (GWeakNotify)
278                 calendar_freed_cb, factory);
279
280         g_object_unref (backend);
281
282         /* Update the hash of open connections. */
283         g_mutex_lock (priv->connections_lock);
284         list = g_hash_table_lookup (priv->connections, sender);
285         list = g_list_prepend (list, calendar);
286         g_hash_table_insert (priv->connections, g_strdup (sender), list);
287         g_mutex_unlock (priv->connections_lock);
288
289         g_mutex_unlock (priv->calendars_lock);
290
291         g_free (uid);
292
293         e_gdbus_cal_factory_complete_get_cal (
294                 object, invocation, path, error);
295
296         if (error)
297                 g_error_free (error);
298
299         g_free (path);
300
301         return TRUE;
302 }
303
304 static void
305 remove_data_cal_cb (EDataCal *data_cal)
306 {
307         ECalBackend *backend;
308
309         g_return_if_fail (data_cal != NULL);
310
311         backend = e_data_cal_get_backend (data_cal);
312         e_cal_backend_remove_client (backend, data_cal);
313
314         g_object_unref (data_cal);
315 }
316
317 static void
318 data_cal_factory_get_property (GObject *object,
319                                guint property_id,
320                                GValue *value,
321                                GParamSpec *pspec)
322 {
323         switch (property_id) {
324                 case PROP_REGISTRY:
325                         g_value_set_object (
326                                 value,
327                                 e_data_cal_factory_get_registry (
328                                 E_DATA_CAL_FACTORY (object)));
329                         return;
330         }
331
332         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
333 }
334
335 static void
336 data_cal_factory_dispose (GObject *object)
337 {
338         EDataCalFactoryPrivate *priv;
339
340         priv = E_DATA_CAL_FACTORY_GET_PRIVATE (object);
341
342         if (priv->registry != NULL) {
343                 g_object_unref (priv->registry);
344                 priv->registry = NULL;
345         }
346
347         if (priv->gdbus_object != NULL) {
348                 g_object_unref (priv->gdbus_object);
349                 priv->gdbus_object = NULL;
350         }
351
352         /* Chain up to parent's dispose() method. */
353         G_OBJECT_CLASS (e_data_cal_factory_parent_class)->dispose (object);
354 }
355
356 static void
357 data_cal_factory_finalize (GObject *object)
358 {
359         EDataCalFactoryPrivate *priv;
360
361         priv = E_DATA_CAL_FACTORY_GET_PRIVATE (object);
362
363         g_hash_table_destroy (priv->calendars);
364         g_hash_table_destroy (priv->connections);
365
366         g_mutex_free (priv->calendars_lock);
367         g_mutex_free (priv->connections_lock);
368
369         /* Chain up to parent's finalize() method. */
370         G_OBJECT_CLASS (e_data_cal_factory_parent_class)->finalize (object);
371 }
372
373 static void
374 data_cal_factory_bus_acquired (EDBusServer *server,
375                                GDBusConnection *connection)
376 {
377         EDataCalFactoryPrivate *priv;
378         guint registration_id;
379         GError *error = NULL;
380
381         priv = E_DATA_CAL_FACTORY_GET_PRIVATE (server);
382
383         registration_id = e_gdbus_cal_factory_register_object (
384                 priv->gdbus_object,
385                 connection,
386                 "/org/gnome/evolution/dataserver/CalendarFactory",
387                 &error);
388
389         if (error != NULL) {
390                 g_error (
391                         "Failed to register a CalendarFactory object: %s",
392                         error->message);
393                 g_assert_not_reached ();
394         }
395
396         g_assert (registration_id > 0);
397
398         /* Chain up to parent's bus_acquired() method. */
399         E_DBUS_SERVER_CLASS (e_data_cal_factory_parent_class)->
400                 bus_acquired (server, connection);
401 }
402
403 static void
404 data_cal_factory_bus_name_lost (EDBusServer *server,
405                                 GDBusConnection *connection)
406 {
407         EDataCalFactoryPrivate *priv;
408         GList *list = NULL;
409         gchar *key;
410
411         priv = E_DATA_CAL_FACTORY_GET_PRIVATE (server);
412
413         g_mutex_lock (priv->connections_lock);
414
415         while (g_hash_table_lookup_extended (
416                 priv->connections,
417                 CALENDAR_DBUS_SERVICE_NAME,
418                 (gpointer) &key, (gpointer) &list)) {
419                 GList *copy;
420
421                 /* this should trigger the calendar's weak ref notify
422                  * function, which will remove it from the list before
423                  * it's freed, and will remove the connection from
424                  * priv->connections once they're all gone */
425                 copy = g_list_copy (list);
426                 g_list_foreach (copy, (GFunc) remove_data_cal_cb, NULL);
427                 g_list_free (copy);
428         }
429
430         g_mutex_unlock (priv->connections_lock);
431
432         /* Chain up to parent's bus_name_lost() method. */
433         E_DBUS_SERVER_CLASS (e_data_cal_factory_parent_class)->
434                 bus_name_lost (server, connection);
435 }
436
437 static void
438 data_cal_factory_quit_server (EDBusServer *server,
439                               EDBusServerExitCode exit_code)
440 {
441         /* This factory does not support reloading, so stop the signal
442          * emission and return without chaining up to prevent quitting. */
443         if (exit_code == E_DBUS_SERVER_EXIT_RELOAD) {
444                 g_signal_stop_emission_by_name (server, "quit-server");
445                 return;
446         }
447
448         /* Chain up to parent's quit_server() method. */
449         E_DBUS_SERVER_CLASS (e_data_cal_factory_parent_class)->
450                 quit_server (server, exit_code);
451 }
452
453 static gboolean
454 data_cal_factory_initable_init (GInitable *initable,
455                                 GCancellable *cancellable,
456                                 GError **error)
457 {
458         EDataCalFactoryPrivate *priv;
459
460         priv = E_DATA_CAL_FACTORY_GET_PRIVATE (initable);
461
462         priv->registry = e_source_registry_new_sync (cancellable, error);
463
464         return (priv->registry != NULL);
465 }
466
467 static void
468 e_data_cal_factory_class_init (EDataCalFactoryClass *class)
469 {
470         GObjectClass *object_class;
471         EDBusServerClass *dbus_server_class;
472         EDataFactoryClass *data_factory_class;
473
474         g_type_class_add_private (class, sizeof (EDataCalFactoryPrivate));
475
476         object_class = G_OBJECT_CLASS (class);
477         object_class->get_property = data_cal_factory_get_property;
478         object_class->dispose = data_cal_factory_dispose;
479         object_class->finalize = data_cal_factory_finalize;
480
481         dbus_server_class = E_DBUS_SERVER_CLASS (class);
482         dbus_server_class->bus_name = CALENDAR_DBUS_SERVICE_NAME;
483         dbus_server_class->module_directory = BACKENDDIR;
484         dbus_server_class->bus_acquired = data_cal_factory_bus_acquired;
485         dbus_server_class->bus_name_lost = data_cal_factory_bus_name_lost;
486         dbus_server_class->quit_server = data_cal_factory_quit_server;
487
488         data_factory_class = E_DATA_FACTORY_CLASS (class);
489         data_factory_class->backend_factory_type = E_TYPE_CAL_BACKEND_FACTORY;
490
491         g_object_class_install_property (
492                 object_class,
493                 PROP_REGISTRY,
494                 g_param_spec_object (
495                         "registry",
496                         "Registry",
497                         "Data source registry",
498                         E_TYPE_SOURCE_REGISTRY,
499                         G_PARAM_READABLE |
500                         G_PARAM_STATIC_STRINGS));
501 }
502
503 static void
504 e_data_cal_factory_initable_init (GInitableIface *interface)
505 {
506         interface->init = data_cal_factory_initable_init;
507 }
508
509 static void
510 e_data_cal_factory_init (EDataCalFactory *factory)
511 {
512         factory->priv = E_DATA_CAL_FACTORY_GET_PRIVATE (factory);
513
514         factory->priv->gdbus_object = e_gdbus_cal_factory_stub_new ();
515         g_signal_connect (
516                 factory->priv->gdbus_object, "handle-get-cal",
517                 G_CALLBACK (impl_CalFactory_get_cal), factory);
518
519         factory->priv->calendars_lock = g_mutex_new ();
520         factory->priv->calendars = g_hash_table_new_full (
521                 g_str_hash, g_str_equal,
522                 (GDestroyNotify) g_free,
523                 (GDestroyNotify) NULL);
524
525         factory->priv->connections_lock = g_mutex_new ();
526         factory->priv->connections = g_hash_table_new_full (
527                 g_str_hash, g_str_equal,
528                 (GDestroyNotify) g_free,
529                 (GDestroyNotify) NULL);
530 }
531
532 EDBusServer *
533 e_data_cal_factory_new (GCancellable *cancellable,
534                         GError **error)
535 {
536         icalarray *builtin_timezones;
537         gint ii;
538
539 #ifdef HAVE_ICAL_UNKNOWN_TOKEN_HANDLING
540         ical_set_unknown_token_handling_setting (ICAL_DISCARD_TOKEN);
541 #endif
542
543         /* XXX Pre-load all built-in timezones in libical.
544          *
545          *     Built-in time zones in libical 0.43 are loaded on demand,
546          *     but not in a thread-safe manner, resulting in a race when
547          *     multiple threads call icaltimezone_load_builtin_timezone()
548          *     on the same time zone.  Until built-in time zone loading
549          *     in libical is made thread-safe, work around the issue by
550          *     loading all built-in time zones now, so libical's internal
551          *     time zone array will be fully populated before any threads
552          *     are spawned.
553          */
554         builtin_timezones = icaltimezone_get_builtin_timezones ();
555         for (ii = 0; ii < builtin_timezones->num_elements; ii++) {
556                 icaltimezone *zone;
557
558                 zone = icalarray_element_at (builtin_timezones, ii);
559
560                 /* We don't care about the component right now,
561                  * we just need some function that will trigger
562                  * icaltimezone_load_builtin_timezone(). */
563                 icaltimezone_get_component (zone);
564         }
565
566         return g_initable_new (
567                 E_TYPE_DATA_CAL_FACTORY,
568                 cancellable, error, NULL);
569 }
570
571 ESourceRegistry *
572 e_data_cal_factory_get_registry (EDataCalFactory *factory)
573 {
574         g_return_val_if_fail (E_IS_DATA_CAL_FACTORY (factory), NULL);
575
576         return factory->priv->registry;
577 }