/* * Copyright (C) 2004 Free Software Foundation, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * Authors: * Mark McLoughlin * William Jon McCann * Martin Grimme * Christian Kellner */ #include #include "calendar-sources.h" #include #include #define HANDLE_LIBICAL_MEMORY #include #undef CALENDAR_ENABLE_DEBUG #include "calendar-debug.h" #ifndef _ #define _(x) gettext(x) #endif #ifndef N_ #define N_(x) x #endif typedef struct _ClientData ClientData; typedef struct _CalendarSourceData CalendarSourceData; struct _ClientData { ECalClient *client; gulong backend_died_id; }; struct _CalendarSourceData { ECalClientSourceType source_type; CalendarSources *sources; guint changed_signal; /* ESource -> EClient */ GHashTable *clients; guint timeout_id; guint loaded : 1; }; typedef struct _CalendarSourcesPrivate CalendarSourcesPrivate; struct _CalendarSources { GObject parent; CalendarSourcesPrivate *priv; }; struct _CalendarSourcesPrivate { ESourceRegistry *registry; gulong source_added_id; gulong source_changed_id; gulong source_removed_id; CalendarSourceData appointment_sources; CalendarSourceData task_sources; }; G_DEFINE_TYPE_WITH_PRIVATE (CalendarSources, calendar_sources, G_TYPE_OBJECT) static void calendar_sources_finalize (GObject *object); static void backend_died_cb (EClient *client, CalendarSourceData *source_data); static void calendar_sources_registry_source_changed_cb (ESourceRegistry *registry, ESource *source, CalendarSources *sources); static void calendar_sources_registry_source_removed_cb (ESourceRegistry *registry, ESource *source, CalendarSources *sources); enum { APPOINTMENT_SOURCES_CHANGED, TASK_SOURCES_CHANGED, LAST_SIGNAL }; static guint signals [LAST_SIGNAL] = { 0, }; static GObjectClass *parent_class = NULL; static CalendarSources *calendar_sources_singleton = NULL; static void client_data_free (ClientData *data) { g_signal_handler_disconnect (data->client, data->backend_died_id); g_object_unref (data->client); g_slice_free (ClientData, data); } static void calendar_sources_class_init (CalendarSourcesClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; parent_class = g_type_class_peek_parent (klass); gobject_class->finalize = calendar_sources_finalize; signals [APPOINTMENT_SOURCES_CHANGED] = g_signal_new ("appointment-sources-changed", G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); signals [TASK_SOURCES_CHANGED] = g_signal_new ("task-sources-changed", G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } static void calendar_sources_init (CalendarSources *sources) { GError *error = NULL; GDBusConnection *session_bus; GVariant *result; sources->priv = calendar_sources_get_instance_private (sources); /* WORKAROUND: the hardcoded timeout for e_source_registry_new_sync() (and other library calls that eventually call g_dbus_proxy_new[_sync]()) is 25 seconds. This has been shown to be too small for evolution-source-registry in certain cases (slow disk, concurrent IO, many configured sources), so we first ensure that the service starts with a manual call and a higher timeout. HACK: every time the DBus API is bumped in e-d-s we need to update this! */ session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); if (session_bus == NULL) { g_error ("Failed to connect to the session bus: %s", error->message); } result = g_dbus_connection_call_sync (session_bus, "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "StartServiceByName", g_variant_new ("(su)", "org.gnome.evolution.dataserver.Sources5", 0), NULL, G_DBUS_CALL_FLAGS_NONE, 60 * 1000, NULL, &error); if (result != NULL) { g_variant_unref (result); sources->priv->registry = e_source_registry_new_sync (NULL, &error); } if (error != NULL) { /* Any error is fatal, but we don't want to crash gnome-shell-calendar-server because of e-d-s problems. So just exit here. */ g_warning ("Failed to start evolution-source-registry: %s", error->message); exit(EXIT_FAILURE); } g_object_unref (session_bus); sources->priv->source_added_id = g_signal_connect (sources->priv->registry, "source-added", G_CALLBACK (calendar_sources_registry_source_changed_cb), sources); sources->priv->source_changed_id = g_signal_connect (sources->priv->registry, "source-changed", G_CALLBACK (calendar_sources_registry_source_changed_cb), sources); sources->priv->source_removed_id = g_signal_connect (sources->priv->registry, "source-removed", G_CALLBACK (calendar_sources_registry_source_removed_cb), sources); sources->priv->appointment_sources.source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS; sources->priv->appointment_sources.sources = sources; sources->priv->appointment_sources.changed_signal = signals [APPOINTMENT_SOURCES_CHANGED]; sources->priv->appointment_sources.clients = g_hash_table_new_full ((GHashFunc) e_source_hash, (GEqualFunc) e_source_equal, (GDestroyNotify) g_object_unref, (GDestroyNotify) client_data_free); sources->priv->appointment_sources.timeout_id = 0; sources->priv->task_sources.source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS; sources->priv->task_sources.sources = sources; sources->priv->task_sources.changed_signal = signals [TASK_SOURCES_CHANGED]; sources->priv->task_sources.clients = g_hash_table_new_full ((GHashFunc) e_source_hash, (GEqualFunc) e_source_equal, (GDestroyNotify) g_object_unref, (GDestroyNotify) client_data_free); sources->priv->task_sources.timeout_id = 0; } static void calendar_sources_finalize_source_data (CalendarSources *sources, CalendarSourceData *source_data) { if (source_data->loaded) { g_hash_table_destroy (source_data->clients); source_data->clients = NULL; if (source_data->timeout_id != 0) { g_source_remove (source_data->timeout_id); source_data->timeout_id = 0; } source_data->loaded = FALSE; } } static void calendar_sources_finalize (GObject *object) { CalendarSources *sources = CALENDAR_SOURCES (object); if (sources->priv->registry) { g_signal_handler_disconnect (sources->priv->registry, sources->priv->source_added_id); g_signal_handler_disconnect (sources->priv->registry, sources->priv->source_changed_id); g_signal_handler_disconnect (sources->priv->registry, sources->priv->source_removed_id); g_object_unref (sources->priv->registry); } sources->priv->registry = NULL; calendar_sources_finalize_source_data (sources, &sources->priv->appointment_sources); calendar_sources_finalize_source_data (sources, &sources->priv->task_sources); if (G_OBJECT_CLASS (parent_class)->finalize) G_OBJECT_CLASS (parent_class)->finalize (object); } CalendarSources * calendar_sources_get (void) { gpointer singleton_location = &calendar_sources_singleton; if (calendar_sources_singleton) return g_object_ref (calendar_sources_singleton); calendar_sources_singleton = g_object_new (CALENDAR_TYPE_SOURCES, NULL); g_object_add_weak_pointer (G_OBJECT (calendar_sources_singleton), singleton_location); return calendar_sources_singleton; } /* The clients are just created here but not loaded */ static void create_client_for_source (ESource *source, ECalClientSourceType source_type, CalendarSourceData *source_data) { ClientData *data; ECalClient *client; GError *error = NULL; client = g_hash_table_lookup (source_data->clients, source); g_return_if_fail (client == NULL); client = e_cal_client_new (source, source_type, &error); if (!client) { g_warning ("Could not load source '%s': %s", e_source_get_uid (source), error->message); g_clear_error(&error); return; } data = g_slice_new0 (ClientData); data->client = client; /* takes ownership */ data->backend_died_id = g_signal_connect (client, "backend-died", G_CALLBACK (backend_died_cb), source_data); g_hash_table_insert (source_data->clients, g_object_ref (source), data); } static inline void debug_dump_ecal_list (GHashTable *clients) { #ifdef CALENDAR_ENABLE_DEBUG GList *list, *link; dprintf ("Loaded clients:\n"); list = g_hash_table_get_keys (clients); for (link = list; link != NULL; link = g_list_next (link)) { ESource *source = E_SOURCE (link->data); dprintf (" %s %s\n", e_source_get_uid (source), e_source_get_display_name (source)); } g_list_free (list); #endif } static void calendar_sources_load_esource_list (ESourceRegistry *registry, CalendarSourceData *source_data); static gboolean backend_restart (gpointer data) { CalendarSourceData *source_data = data; ESourceRegistry *registry; registry = source_data->sources->priv->registry; calendar_sources_load_esource_list (registry, source_data); g_signal_emit (source_data->sources, source_data->changed_signal, 0); source_data->timeout_id = 0; return FALSE; } static void backend_died_cb (EClient *client, CalendarSourceData *source_data) { ESource *source; const char *display_name; source = e_client_get_source (client); display_name = e_source_get_display_name (source); g_warning ("The calendar backend for '%s' has crashed.", display_name); g_hash_table_remove (source_data->clients, source); if (source_data->timeout_id != 0) { g_source_remove (source_data->timeout_id); source_data->timeout_id = 0; } source_data->timeout_id = g_timeout_add_seconds (2, backend_restart, source_data); g_source_set_name_by_id (source_data->timeout_id, "[gnome-shell] backend_restart"); } static void calendar_sources_load_esource_list (ESourceRegistry *registry, CalendarSourceData *source_data) { GList *list, *link; const gchar *extension_name; switch (source_data->source_type) { case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: extension_name = E_SOURCE_EXTENSION_CALENDAR; break; case E_CAL_CLIENT_SOURCE_TYPE_TASKS: extension_name = E_SOURCE_EXTENSION_TASK_LIST; break; default: g_return_if_reached (); } list = e_source_registry_list_sources (registry, extension_name); for (link = list; link != NULL; link = g_list_next (link)) { ESource *source = E_SOURCE (link->data); ESourceSelectable *extension; gboolean show_source; extension = e_source_get_extension (source, extension_name); show_source = e_source_get_enabled (source) && e_source_selectable_get_selected (extension); if (show_source) create_client_for_source (source, source_data->source_type, source_data); } debug_dump_ecal_list (source_data->clients); g_list_free_full (list, g_object_unref); } static void calendar_sources_registry_source_changed_cb (ESourceRegistry *registry, ESource *source, CalendarSources *sources) { if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR)) { CalendarSourceData *source_data; ESourceSelectable *extension; gboolean have_client; gboolean show_source; source_data = &sources->priv->appointment_sources; extension = e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR); have_client = (g_hash_table_lookup (source_data->clients, source) != NULL); show_source = e_source_get_enabled (source) && e_source_selectable_get_selected (extension); if (!show_source && have_client) { g_hash_table_remove (source_data->clients, source); g_signal_emit (sources, source_data->changed_signal, 0); } if (show_source && !have_client) { create_client_for_source (source, source_data->source_type, source_data); g_signal_emit (sources, source_data->changed_signal, 0); } } if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST)) { CalendarSourceData *source_data; ESourceSelectable *extension; gboolean have_client; gboolean show_source; source_data = &sources->priv->task_sources; extension = e_source_get_extension (source, E_SOURCE_EXTENSION_TASK_LIST); have_client = (g_hash_table_lookup (source_data->clients, source) != NULL); show_source = e_source_get_enabled (source) && e_source_selectable_get_selected (extension); if (!show_source && have_client) { g_hash_table_remove (source_data->clients, source); g_signal_emit (sources, source_data->changed_signal, 0); } if (show_source && !have_client) { create_client_for_source (source, source_data->source_type, source_data); g_signal_emit (sources, source_data->changed_signal, 0); } } } static void calendar_sources_registry_source_removed_cb (ESourceRegistry *registry, ESource *source, CalendarSources *sources) { if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR)) { CalendarSourceData *source_data; source_data = &sources->priv->appointment_sources; g_hash_table_remove (source_data->clients, source); g_signal_emit (sources, source_data->changed_signal, 0); } if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST)) { CalendarSourceData *source_data; source_data = &sources->priv->task_sources; g_hash_table_remove (source_data->clients, source); g_signal_emit (sources, source_data->changed_signal, 0); } } static void ensure_appointment_sources (CalendarSources *sources) { if (!sources->priv->appointment_sources.loaded) { calendar_sources_load_esource_list (sources->priv->registry, &sources->priv->appointment_sources); sources->priv->appointment_sources.loaded = TRUE; } } GList * calendar_sources_get_appointment_clients (CalendarSources *sources) { GList *list, *link; g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL); ensure_appointment_sources (sources); list = g_hash_table_get_values (sources->priv->appointment_sources.clients); for (link = list; link != NULL; link = g_list_next (link)) link->data = ((ClientData *) link->data)->client; return list; } static void ensure_task_sources (CalendarSources *sources) { if (!sources->priv->task_sources.loaded) { calendar_sources_load_esource_list (sources->priv->registry, &sources->priv->task_sources); sources->priv->task_sources.loaded = TRUE; } } GList * calendar_sources_get_task_clients (CalendarSources *sources) { GList *list, *link; g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL); ensure_task_sources (sources); list = g_hash_table_get_values (sources->priv->task_sources.clients); for (link = list; link != NULL; link = g_list_next (link)) link->data = ((ClientData *) link->data)->client; return list; } gboolean calendar_sources_has_sources (CalendarSources *sources) { g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), FALSE); ensure_appointment_sources (sources); ensure_task_sources (sources); return g_hash_table_size (sources->priv->appointment_sources.clients) > 0 || g_hash_table_size (sources->priv->task_sources.clients) > 0; }