/* * 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 #define EDS_DISABLE_DEPRECATED G_GNUC_BEGIN_IGNORE_DEPRECATIONS #include G_GNUC_END_IGNORE_DEPRECATIONS #undef CALENDAR_ENABLE_DEBUG #include "calendar-debug.h" typedef struct _ClientData ClientData; typedef struct _CalendarSourceData CalendarSourceData; struct _ClientData { ECalClient *client; gulong backend_died_id; }; typedef struct _CalendarSourcesPrivate CalendarSourcesPrivate; struct _CalendarSources { GObject parent; ESourceRegistryWatcher *registry_watcher; gulong filter_id; gulong appeared_id; gulong disappeared_id; GMutex clients_lock; GHashTable *clients; /* ESource -> ClientData */ }; G_DEFINE_TYPE (CalendarSources, calendar_sources, G_TYPE_OBJECT) enum { CLIENT_APPEARED, CLIENT_DISAPPEARED, LAST_SIGNAL }; static guint signals [LAST_SIGNAL] = { 0, }; static void calendar_sources_client_connected_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { CalendarSources *sources = CALENDAR_SOURCES (source_object); ESource *source = user_data; EClient *client; g_autoptr (GError) error = NULL; /* The calendar_sources_connect_client_sync() already stored the 'client' * into the sources->clients */ client = calendar_sources_connect_client_finish (sources, result, &error); if (error) { g_warning ("Could not load source '%s': %s", e_source_get_uid (source), error->message); } else { g_signal_emit (sources, signals[CLIENT_APPEARED], 0, client, NULL); } g_clear_object (&client); g_clear_object (&source); } static gboolean registry_watcher_filter_cb (ESourceRegistryWatcher *watcher, ESource *source, CalendarSources *sources) { return e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR) && e_source_selectable_get_selected (e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR)); } static void registry_watcher_source_appeared_cb (ESourceRegistryWatcher *watcher, ESource *source, CalendarSources *sources) { ECalClientSourceType source_type; if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR)) source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS; else if (e_source_has_extension (source, E_SOURCE_EXTENSION_MEMO_LIST)) source_type = E_CAL_CLIENT_SOURCE_TYPE_MEMOS; else if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST)) source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS; else g_return_if_reached (); calendar_sources_connect_client (sources, source, source_type, 30, NULL, calendar_sources_client_connected_cb, g_object_ref (source)); } static void registry_watcher_source_disappeared_cb (ESourceRegistryWatcher *watcher, ESource *source, CalendarSources *sources) { gboolean emit; g_mutex_lock (&sources->clients_lock); emit = g_hash_table_remove (sources->clients, source); g_mutex_unlock (&sources->clients_lock); if (emit) g_signal_emit (sources, signals[CLIENT_DISAPPEARED], 0, e_source_get_uid (source), NULL); } static void client_data_free (ClientData *data) { g_signal_handler_disconnect (data->client, data->backend_died_id); g_object_unref (data->client); g_free (data); } static void calendar_sources_constructed (GObject *object) { CalendarSources *sources = CALENDAR_SOURCES (object); ESourceRegistry *registry = NULL; GError *error = NULL; G_OBJECT_CLASS (calendar_sources_parent_class)->constructed (object); 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_return_if_fail (registry != NULL); sources->registry_watcher = e_source_registry_watcher_new (registry, NULL); g_clear_object (®istry); sources->clients = g_hash_table_new_full ((GHashFunc) e_source_hash, (GEqualFunc) e_source_equal, (GDestroyNotify) g_object_unref, (GDestroyNotify) client_data_free); sources->filter_id = g_signal_connect (sources->registry_watcher, "filter", G_CALLBACK (registry_watcher_filter_cb), sources); sources->appeared_id = g_signal_connect (sources->registry_watcher, "appeared", G_CALLBACK (registry_watcher_source_appeared_cb), sources); sources->disappeared_id = g_signal_connect (sources->registry_watcher, "disappeared", G_CALLBACK (registry_watcher_source_disappeared_cb), sources); e_source_registry_watcher_reclaim (sources->registry_watcher); } static void calendar_sources_finalize (GObject *object) { CalendarSources *sources = CALENDAR_SOURCES (object); g_clear_pointer (&sources->clients, g_hash_table_destroy); if (sources->registry_watcher) { g_signal_handler_disconnect (sources->registry_watcher, sources->filter_id); g_signal_handler_disconnect (sources->registry_watcher, sources->appeared_id); g_signal_handler_disconnect (sources->registry_watcher, sources->disappeared_id); g_clear_object (&sources->registry_watcher); } g_mutex_clear (&sources->clients_lock); G_OBJECT_CLASS (calendar_sources_parent_class)->finalize (object); } static void calendar_sources_class_init (CalendarSourcesClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; gobject_class->constructed = calendar_sources_constructed; gobject_class->finalize = calendar_sources_finalize; signals [CLIENT_APPEARED] = g_signal_new ("client-appeared", G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, E_TYPE_CAL_CLIENT); signals [CLIENT_DISAPPEARED] = g_signal_new ("client-disappeared", G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING); /* ESource::uid of the disappeared client */ } static void calendar_sources_init (CalendarSources *sources) { g_mutex_init (&sources->clients_lock); } CalendarSources * calendar_sources_get (void) { static CalendarSources *calendar_sources_singleton = NULL; 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; } ESourceRegistry * calendar_sources_get_registry (CalendarSources *sources) { return e_source_registry_watcher_get_registry (sources->registry_watcher); } static void gather_event_clients_cb (gpointer key, gpointer value, gpointer user_data) { GSList **plist = user_data; ClientData *cd = value; if (cd) *plist = g_slist_prepend (*plist, g_object_ref (cd->client)); } GSList * calendar_sources_ref_clients (CalendarSources *sources) { GSList *list = NULL; g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL); g_mutex_lock (&sources->clients_lock); g_hash_table_foreach (sources->clients, gather_event_clients_cb, &list); g_mutex_unlock (&sources->clients_lock); return list; } gboolean calendar_sources_has_clients (CalendarSources *sources) { GHashTableIter iter; gpointer value; gboolean has = FALSE; g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), FALSE); g_mutex_lock (&sources->clients_lock); g_hash_table_iter_init (&iter, sources->clients); while (!has && g_hash_table_iter_next (&iter, NULL, &value)) { ClientData *cd = value; has = cd != NULL; } g_mutex_unlock (&sources->clients_lock); return has; } static void backend_died_cb (EClient *client, CalendarSources *sources) { 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_mutex_lock (&sources->clients_lock); g_hash_table_remove (sources->clients, source); g_mutex_unlock (&sources->clients_lock); } static EClient * calendar_sources_connect_client_sync (CalendarSources *sources, ESource *source, ECalClientSourceType source_type, guint32 wait_for_connected_seconds, GCancellable *cancellable, GError **error) { EClient *client = NULL; ClientData *client_data; g_mutex_lock (&sources->clients_lock); client_data = g_hash_table_lookup (sources->clients, source); if (client_data) client = E_CLIENT (g_object_ref (client_data->client)); g_mutex_unlock (&sources->clients_lock); if (client) return client; client = e_cal_client_connect_sync (source, source_type, wait_for_connected_seconds, cancellable, error); if (!client) return NULL; g_mutex_lock (&sources->clients_lock); client_data = g_hash_table_lookup (sources->clients, source); if (client_data) { g_clear_object (&client); client = E_CLIENT (g_object_ref (client_data->client)); } else { client_data = g_new0 (ClientData, 1); client_data->client = E_CAL_CLIENT (g_object_ref (client)); client_data->backend_died_id = g_signal_connect (client, "backend-died", G_CALLBACK (backend_died_cb), sources); g_hash_table_insert (sources->clients, g_object_ref (source), client_data); } g_mutex_unlock (&sources->clients_lock); return client; } typedef struct _AsyncContext { ESource *source; ECalClientSourceType source_type; guint32 wait_for_connected_seconds; } AsyncContext; static void async_context_free (gpointer ptr) { AsyncContext *ctx = ptr; if (ctx) { g_clear_object (&ctx->source); g_free (ctx); } } static void calendar_sources_connect_client_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { CalendarSources *sources = source_object; AsyncContext *ctx = task_data; EClient *client; GError *local_error = NULL; client = calendar_sources_connect_client_sync (sources, ctx->source, ctx->source_type, ctx->wait_for_connected_seconds, cancellable, &local_error); if (!client) { if (local_error) g_task_return_error (task, local_error); else g_task_return_pointer (task, NULL, NULL); } else { g_task_return_pointer (task, client, g_object_unref); } } void calendar_sources_connect_client (CalendarSources *sources, ESource *source, ECalClientSourceType source_type, guint32 wait_for_connected_seconds, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { AsyncContext *ctx; g_autoptr (GTask) task = NULL; ctx = g_new0 (AsyncContext, 1); ctx->source = g_object_ref (source); ctx->source_type = source_type; ctx->wait_for_connected_seconds = wait_for_connected_seconds; task = g_task_new (sources, cancellable, callback, user_data); g_task_set_source_tag (task, calendar_sources_connect_client); g_task_set_task_data (task, ctx, async_context_free); g_task_run_in_thread (task, calendar_sources_connect_client_thread); } EClient * calendar_sources_connect_client_finish (CalendarSources *sources, GAsyncResult *result, GError **error) { g_return_val_if_fail (g_task_is_valid (result, sources), NULL); g_return_val_if_fail (g_async_result_is_tagged (result, calendar_sources_connect_client), NULL); return g_task_propagate_pointer (G_TASK (result), error); } void print_debug (const gchar *format, ...) { g_autofree char *s = NULL; g_autofree char *timestamp = NULL; va_list ap; g_autoptr (GDateTime) now = NULL; static size_t once_init_value = 0; static gboolean show_debug = FALSE; static guint pid = 0; if (g_once_init_enter (&once_init_value)) { show_debug = (g_getenv ("CALENDAR_SERVER_DEBUG") != NULL); pid = getpid (); g_once_init_leave (&once_init_value, 1); } if (!show_debug) goto out; now = g_date_time_new_now_local (); timestamp = g_date_time_format (now, "%H:%M:%S"); va_start (ap, format); s = g_strdup_vprintf (format, ap); va_end (ap); g_print ("gnome-shell-calendar-server[%d]: %s.%03d: %s\n", pid, timestamp, g_date_time_get_microsecond (now), s); out: ; }