1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* Evolution calendar factory
4 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
5 * Copyright (C) 2009 Intel Corporation
8 * Federico Mena-Quintero <federico@ximian.com>
9 * JP Rosevear <jpr@ximian.com>
10 * Ross Burton <ross@linux.intel.com>
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.
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.
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.
31 #include <glib/gi18n.h>
32 #include <glib-object.h>
33 #include <dbus/dbus-glib.h>
34 #include <dbus/dbus-glib-lowlevel.h>
35 #include <dbus/dbus-glib-bindings.h>
37 #include "libedataserver/e-url.h"
38 #include "libedataserver/e-source.h"
39 #include "libedataserver/e-source-list.h"
40 #include "libebackend/e-data-server-module.h"
41 #include <libebackend/e-offline-listener.h>
42 #include "libecal/e-cal.h"
43 #include "e-cal-backend.h"
44 #include "e-cal-backend-factory.h"
45 #include "e-data-cal.h"
46 #include "e-data-cal-factory.h"
47 #include "e-cal-backend-loader-factory.h"
51 static void impl_CalFactory_getCal (EDataCalFactory *factory, const gchar *IN_uri, EDataCalObjType type, DBusGMethodInvocation *context);
52 #include "e-data-cal-factory-glue.h"
54 static GMainLoop *loop;
55 static EDataCalFactory *factory;
56 extern DBusGConnection *connection;
58 /* Convenience macro to test and set a GError/return on failure */
59 #define g_set_error_val_if_fail(test, returnval, error, domain, code) G_STMT_START{ \
60 if G_LIKELY (test) {} else { \
61 g_set_error (error, domain, code, #test); \
62 g_warning(#test " failed"); \
67 G_DEFINE_TYPE(EDataCalFactory, e_data_cal_factory, G_TYPE_OBJECT);
69 #define E_DATA_CAL_FACTORY_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), E_TYPE_DATA_CAL_FACTORY, EDataCalFactoryPrivate))
71 struct _EDataCalFactoryPrivate {
72 /* Hash table from URI method strings to GType * for backend class types */
76 /* mutex to access backends hash table */
77 GMutex *backends_mutex;
79 GHashTable *calendars;
81 GHashTable *connections;
85 /* this is for notifications of source changes */
86 ESourceList *lists[E_CAL_SOURCE_TYPE_LAST];
88 /* backends divided by their type */
89 GSList *backends_by_type[E_CAL_SOURCE_TYPE_LAST];
94 /* Create the EDataCalFactory error quark */
96 e_data_cal_factory_error_quark (void)
98 static GQuark quark = 0;
100 quark = g_quark_from_static_string ("e_data_cal_factory_error");
104 static icalcomponent_kind
105 calobjtype_to_icalkind (const EDataCalObjType type)
109 return ICAL_VEVENT_COMPONENT;
111 return ICAL_VTODO_COMPONENT;
113 return ICAL_VJOURNAL_COMPONENT;
115 return ICAL_NO_COMPONENT;
118 return ICAL_NO_COMPONENT;
122 calobjtype_to_string (const EDataCalObjType type)
135 return "UNKNOWN COMPONENT";
138 static ECalSourceType
139 icalkind_to_ecalsourcetype (const icalcomponent_kind kind)
142 case ICAL_VEVENT_COMPONENT:
143 return E_CAL_SOURCE_TYPE_EVENT;
144 case ICAL_VTODO_COMPONENT:
145 return E_CAL_SOURCE_TYPE_TODO;
146 case ICAL_VJOURNAL_COMPONENT:
147 return E_CAL_SOURCE_TYPE_JOURNAL;
152 return E_CAL_SOURCE_TYPE_LAST;
156 update_source_in_backend (ECalBackend *backend, ESource *updated_source)
160 g_return_if_fail (backend != NULL);
161 g_return_if_fail (updated_source != NULL);
163 xml = xmlNewNode (NULL, (const xmlChar *)"dummy");
164 e_source_dump_to_xml_node (updated_source, xml);
165 e_source_update_from_xml_node (e_cal_backend_get_source (backend), xml->children, NULL);
170 source_list_changed_cb (ESourceList *list, EDataCalFactory *factory)
172 EDataCalFactoryPrivate *priv;
175 g_return_if_fail (list != NULL);
176 g_return_if_fail (factory != NULL);
177 g_return_if_fail (E_IS_DATA_CAL_FACTORY (factory));
179 priv = factory->priv;
181 g_mutex_lock (priv->backends_mutex);
183 for (i = 0; i < E_CAL_SOURCE_TYPE_LAST; i++) {
184 if (list == priv->lists[i]) {
187 for (l = priv->backends_by_type [i]; l; l = l->next) {
188 ECalBackend *backend = l->data;
189 ESource *source, *list_source;
191 source = e_cal_backend_get_source (backend);
192 list_source = e_source_list_peek_source_by_uid (priv->lists[i], e_source_peek_uid (source));
195 update_source_in_backend (backend, list_source);
203 g_mutex_unlock (priv->backends_mutex);
206 static ECalBackendFactory *
207 get_backend_factory (GHashTable *methods, const gchar *method, icalcomponent_kind kind)
210 ECalBackendFactory *factory;
212 kinds = g_hash_table_lookup (methods, method);
217 factory = g_hash_table_lookup (kinds, GINT_TO_POINTER (kind));
223 construct_cal_factory_path (void)
225 static volatile gint counter = 1;
227 return g_strdup_printf (
228 "/org/gnome/evolution/dataserver/calendar/%d/%u",
230 g_atomic_int_exchange_and_add (&counter, 1));
234 my_remove (gchar *key, GObject *dead)
236 EDataCalFactoryPrivate *priv = factory->priv;
238 gpointer hkey, hvalue;
240 d (g_debug ("%s (%p) is dead", key, dead));
242 g_hash_table_remove (priv->calendars, key);
244 g_hash_table_iter_init (&iter, priv->connections);
245 while (g_hash_table_iter_next (&iter, &hkey, &hvalue)) {
246 GList *calendars = hvalue;
248 if (g_list_find (calendars, dead)) {
249 calendars = g_list_remove (calendars, dead);
251 g_hash_table_insert (priv->connections, g_strdup (hkey), calendars);
253 g_hash_table_remove (priv->connections, hkey);
262 /* If there are no open calendars, start a timer to quit */
263 if (priv->exit_timeout == 0 && g_hash_table_size (priv->calendars) == 0) {
264 priv->exit_timeout = g_timeout_add (10000, (GSourceFunc)g_main_loop_quit, loop);
268 struct find_backend_data
270 const gchar *str_uri;
271 ECalBackend *backend;
272 icalcomponent_kind kind;
276 find_backend_cb (gpointer key, gpointer value, gpointer data)
278 struct find_backend_data *fbd = data;
280 if (fbd && fbd->str_uri && !fbd->backend) {
281 ECalBackend *backend = value;
284 str_uri = e_source_get_uri (e_cal_backend_get_source (backend));
286 if (str_uri && g_str_equal (str_uri, fbd->str_uri)) {
287 const gchar *uid_kind = key, *pos;
289 pos = strrchr (uid_kind, ':');
290 if (pos && atoi (pos + 1) == fbd->kind)
291 fbd->backend = backend;
299 impl_CalFactory_getCal (EDataCalFactory *factory,
300 const gchar *source_xml,
301 EDataCalObjType type,
302 DBusGMethodInvocation *context)
305 EDataCalFactoryPrivate *priv = factory->priv;
306 ECalBackendFactory *backend_factory;
307 ECalBackend *backend;
311 gchar *uid_type_string;
312 gchar *path = NULL, *sender;
314 GError *error = NULL;
316 /* Remove a pending exit */
317 if (priv->exit_timeout) {
318 g_source_remove (priv->exit_timeout);
319 priv->exit_timeout = 0;
322 source = e_source_new_from_standalone_xml (source_xml);
324 dbus_g_method_return_error (context, g_error_new (E_DATA_CAL_ERROR, NoSuchCal, _("Invalid source")));
328 /* Get the URI so we can extract the protocol */
329 str_uri = e_source_get_uri (source);
331 g_object_unref (source);
333 dbus_g_method_return_error (context, g_error_new (E_DATA_CAL_ERROR, NoSuchCal, _("Invalid source")));
338 uri = e_uri_new (str_uri);
340 dbus_g_method_return_error (context, g_error_new (E_DATA_CAL_ERROR, NoSuchCal, _("Invalid URI")));
344 uid_type_string = g_strdup_printf ("%s:%d", e_source_peek_uid (source), (gint)calobjtype_to_icalkind (type));
346 /* Find the associated backend factory (if any) */
347 backend_factory = get_backend_factory (priv->methods, uri->protocol, calobjtype_to_icalkind (type));
348 if (!backend_factory) {
349 error = g_error_new (
350 E_DATA_CAL_ERROR, NoSuchCal,
351 _("No backend factory for '%s' of '%s'"),
352 uri->protocol, calobjtype_to_string (type));
357 g_mutex_lock (priv->backends_mutex);
359 /* Look for an existing backend */
360 backend = g_hash_table_lookup (factory->priv->backends, uid_type_string);
363 /* find backend by URL, if opened, thus functions like e_cal_system_new_* will not
364 create new backends for the same url */
365 struct find_backend_data fbd;
367 fbd.str_uri = str_uri;
368 fbd.kind = calobjtype_to_icalkind (type);
371 g_hash_table_foreach (priv->backends, find_backend_cb, &fbd);
374 backend = fbd.backend;
375 g_object_unref (source);
376 source = g_object_ref (e_cal_backend_get_source (backend));
383 /* There was no existing backend, create a new one */
384 if (E_IS_CAL_BACKEND_LOADER_FACTORY (backend_factory)) {
385 backend = E_CAL_BACKEND_LOADER_FACTORY_GET_CLASS (backend_factory)->new_backend_with_protocol ((ECalBackendLoaderFactory *)backend_factory,
386 source, uri->protocol);
388 backend = e_cal_backend_factory_new_backend (backend_factory, source);
391 error = g_error_new (E_DATA_CAL_ERROR, NoSuchCal, _("Could not instantiate backend"));
395 st = icalkind_to_ecalsourcetype (e_cal_backend_get_kind (backend));
396 if (st != E_CAL_SOURCE_TYPE_LAST) {
397 if (!priv->lists[st] && e_cal_get_sources (&(priv->lists[st]), st, NULL)) {
398 g_signal_connect (priv->lists[st], "changed", G_CALLBACK (source_list_changed_cb), factory);
402 priv->backends_by_type[st] = g_slist_prepend (priv->backends_by_type[st], backend);
405 /* Track the backend */
406 g_hash_table_insert (priv->backends, g_strdup (uid_type_string), backend);
408 e_cal_backend_set_mode (backend, priv->mode);
409 } else if (!e_source_equal (source, e_cal_backend_get_source (backend))) {
410 /* source changed, update it in a backend */
411 update_source_in_backend (backend, source);
414 calendar = e_data_cal_new (backend, source);
415 e_cal_backend_add_client (backend, calendar);
417 path = construct_cal_factory_path ();
418 dbus_g_connection_register_g_object (connection, path, G_OBJECT (calendar));
419 g_object_weak_ref (G_OBJECT (calendar), (GWeakNotify)my_remove, path);
421 g_hash_table_insert (priv->calendars, g_strdup (path), calendar);
423 sender = dbus_g_method_get_sender (context);
424 list = g_hash_table_lookup (priv->connections, sender);
425 list = g_list_prepend (list, calendar);
426 g_hash_table_insert (priv->connections, sender, list);
429 /* The reason why the lock is held for such a long time is that there is
430 a subtle race where e_cal_backend_add_client() can be called just
431 before e_cal_backend_finalize() is called from the
432 backend_last_client_gone_cb(), for details see bug 506457. */
433 g_mutex_unlock (priv->backends_mutex);
437 g_free (uid_type_string);
438 g_object_unref (source);
441 dbus_g_method_return_error (context, error);
443 dbus_g_method_return (context, path);
447 remove_data_cal_cb (gpointer data_cl, gpointer user_data)
451 data_cal = E_DATA_CAL (data_cl);
452 g_return_if_fail (data_cal != NULL);
454 e_cal_backend_remove_client (e_data_cal_get_backend (data_cal), data_cal);
456 g_object_unref (data_cal);
460 name_owner_changed (DBusGProxy *proxy,
462 const gchar *prev_owner,
463 const gchar *new_owner,
464 EDataCalFactory *factory)
466 if (strcmp (new_owner, "") == 0 && strcmp (name, prev_owner) == 0) {
469 while (g_hash_table_lookup_extended (factory->priv->connections, prev_owner, (gpointer)&key, (gpointer)&list)) {
470 GList *copy = g_list_copy (list);
472 /* this should trigger the book's weak ref notify
473 * function, which will remove it from the list before
474 * it's freed, and will remove the connection from
475 * priv->connections once they're all gone */
476 g_list_foreach (copy, remove_data_cal_cb, NULL);
482 /* Class initialization function for the calendar factory */
484 e_data_cal_factory_class_init (EDataCalFactoryClass *klass)
486 g_type_class_add_private (klass, sizeof (EDataCalFactoryPrivate));
487 dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (klass), &dbus_glib_e_data_cal_factory_object_info);
492 e_data_cal_factory_init (EDataCalFactory *factory)
494 factory->priv = E_DATA_CAL_FACTORY_GET_PRIVATE (factory);
496 factory->priv->methods = g_hash_table_new_full (g_str_hash, g_str_equal,
497 (GDestroyNotify) g_free, (GDestroyNotify) g_hash_table_destroy);
499 factory->priv->backends_mutex = g_mutex_new ();
500 factory->priv->backends = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
502 factory->priv->calendars = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
503 factory->priv->connections = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
505 e_data_server_module_init ();
506 e_data_cal_factory_register_backends (factory);
510 set_backend_online_status (gpointer key, gpointer value, gpointer data)
512 ECalBackend *backend = E_CAL_BACKEND (value);
514 e_cal_backend_set_mode (backend, GPOINTER_TO_INT (data));
518 * e_data_cal_factory_set_backend_mode:
519 * @factory: A calendar factory.
520 * @mode: Online mode to set.
522 * Sets the online mode for all backends created by the given factory.
525 e_data_cal_factory_set_backend_mode (EDataCalFactory *factory, gint mode)
527 EDataCalFactoryPrivate *priv = factory->priv;
530 g_mutex_lock (priv->backends_mutex);
531 g_hash_table_foreach (priv->backends, set_backend_online_status, GINT_TO_POINTER (priv->mode));
532 g_mutex_unlock (priv->backends_mutex);
536 * e_data_cal_factory_register_backend:
537 * @factory: A calendar factory.
538 * @backend_factory: The object responsible for creating backends.
540 * Registers an #ECalBackend subclass that will be used to handle URIs
541 * with a particular method. When the factory is asked to open a
542 * particular URI, it will look in its list of registered methods and
543 * create a backend of the appropriate type.
546 e_data_cal_factory_register_backend (EDataCalFactory *factory, ECalBackendFactory *backend_factory)
548 EDataCalFactoryPrivate *priv;
552 icalcomponent_kind kind;
553 GSList *methods = NULL, *l;
555 g_return_if_fail (factory && E_IS_DATA_CAL_FACTORY (factory));
556 g_return_if_fail (backend_factory && E_IS_CAL_BACKEND_FACTORY (backend_factory));
558 priv = factory->priv;
560 if (E_IS_CAL_BACKEND_LOADER_FACTORY (backend_factory)) {
561 GSList *list = E_CAL_BACKEND_LOADER_FACTORY_GET_CLASS (backend_factory)->get_protocol_list ((ECalBackendLoaderFactory *) backend_factory);
562 methods = g_slist_copy (list);
563 } else if (E_CAL_BACKEND_FACTORY_GET_CLASS (backend_factory)->get_protocol) {
564 method = E_CAL_BACKEND_FACTORY_GET_CLASS (backend_factory)->get_protocol (backend_factory);
565 methods = g_slist_append (methods, (gpointer) method);
567 g_assert_not_reached ();
571 kind = E_CAL_BACKEND_FACTORY_GET_CLASS (backend_factory)->get_kind (backend_factory);
573 for (l= methods; l != NULL; l = g_slist_next (l)) {
578 method_str = g_ascii_strdown (method, -1);
580 kinds = g_hash_table_lookup (priv->methods, method_str);
582 type = GPOINTER_TO_INT (g_hash_table_lookup (kinds, GINT_TO_POINTER (kind)));
584 g_warning (G_STRLOC ": method `%s' already registered", method_str);
586 g_slist_free (methods);
592 kinds = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
593 g_hash_table_insert (priv->methods, method_str, kinds);
596 g_hash_table_insert (kinds, GINT_TO_POINTER (kind), backend_factory);
598 g_slist_free (methods);
602 * e_data_cal_factory_register_backends:
603 * @cal_factory: A calendar factory.
605 * Register all backends for the given factory.
608 e_data_cal_factory_register_backends (EDataCalFactory *cal_factory)
610 GList *factories, *f;
612 factories = e_data_server_get_extensions_for_type (E_TYPE_CAL_BACKEND_FACTORY);
614 for (f = factories; f; f = f->next) {
615 ECalBackendFactory *backend_factory = f->data;
617 e_data_cal_factory_register_backend (cal_factory, g_object_ref (backend_factory));
620 e_data_server_extension_list_free (factories);
621 e_data_server_module_remove_unused ();
625 * e_data_cal_factory_get_n_backends
626 * @factory: A calendar factory.
628 * Get the number of backends currently active in the given factory.
630 * Returns: the number of backends.
633 e_data_cal_factory_get_n_backends (EDataCalFactory *factory)
635 EDataCalFactoryPrivate *priv;
638 g_return_val_if_fail (E_IS_DATA_CAL_FACTORY (factory), 0);
640 priv = factory->priv;
641 g_mutex_lock (priv->backends_mutex);
642 sz = g_hash_table_size (priv->backends);
643 g_mutex_unlock (priv->backends_mutex);
648 /* Frees a uri/backend pair from the backends hash table */
650 dump_backend (gpointer key, gpointer value, gpointer data)
653 ECalBackend *backend;
658 g_message (" %s: %p", uri, (gpointer) backend);
662 * e_data_cal_factory_dump_active_backends:
663 * @factory: A calendar factory.
665 * Dumps to standard output a list of all active backends for the given
669 e_data_cal_factory_dump_active_backends (EDataCalFactory *factory)
671 EDataCalFactoryPrivate *priv;
673 g_message ("Active PCS backends");
675 priv = factory->priv;
676 g_mutex_lock (priv->backends_mutex);
677 g_hash_table_foreach (priv->backends, dump_backend, NULL);
678 g_mutex_unlock (priv->backends_mutex);
681 /* Convenience function to print an error and exit */
682 G_GNUC_NORETURN static void
683 die (const gchar *prefix, GError *error)
685 g_error("%s: %s", prefix, error->message);
686 g_error_free (error);
691 offline_state_changed_cb (EOfflineListener *eol, EDataCalFactory *factory)
693 EOfflineListenerState state = e_offline_listener_get_state (eol);
695 g_return_if_fail (state == EOL_STATE_ONLINE || state == EOL_STATE_OFFLINE);
697 e_data_cal_factory_set_backend_mode (factory, state == EOL_STATE_ONLINE ? GNOME_Evolution_Calendar_MODE_REMOTE : GNOME_Evolution_Calendar_MODE_LOCAL);
700 #define E_DATA_CAL_FACTORY_SERVICE_NAME "org.gnome.evolution.dataserver.Calendar"
703 main (gint argc, gchar **argv)
705 GError *error = NULL;
706 DBusGProxy *bus_proxy;
707 guint32 request_name_ret;
708 EOfflineListener *eol;
710 setlocale (LC_ALL, "");
711 bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
712 bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
715 g_set_prgname (E_PRGNAME);
716 if (!g_thread_supported ()) g_thread_init (NULL);
717 dbus_g_thread_init ();
719 loop = g_main_loop_new (NULL, FALSE);
721 /* Obtain a connection to the session bus */
722 connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
723 if (connection == NULL)
724 die ("Failed to open connection to bus", error);
726 bus_proxy = dbus_g_proxy_new_for_name (connection,
729 DBUS_INTERFACE_DBUS);
731 factory = g_object_new (E_TYPE_DATA_CAL_FACTORY, NULL);
732 dbus_g_connection_register_g_object (connection,
733 "/org/gnome/evolution/dataserver/calendar/CalFactory",
736 dbus_g_proxy_add_signal (bus_proxy, "NameOwnerChanged",
737 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
738 dbus_g_proxy_connect_signal (bus_proxy, "NameOwnerChanged", G_CALLBACK (name_owner_changed), factory, NULL);
740 if (!org_freedesktop_DBus_request_name (bus_proxy, E_DATA_CAL_FACTORY_SERVICE_NAME,
741 0, &request_name_ret, &error))
742 die ("Failed to get name", error);
744 if (request_name_ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
745 g_error ("Got result code %u from requesting name", request_name_ret);
749 eol = e_offline_listener_new ();
750 offline_state_changed_cb (eol, factory);
751 g_signal_connect (eol, "changed", G_CALLBACK (offline_state_changed_cb), factory);
753 printf ("Server is up and running...\n");
755 g_main_loop_run (loop);
757 dbus_g_connection_unref (connection);
759 g_object_unref (eol);