gnome-shell/src/calendar-server/calendar-sources.c
2015-10-15 21:04:07 +02:00

569 lines
18 KiB
C

/*
* 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 <http://www.gnu.org/licenses/>.
*
* Authors:
* Mark McLoughlin <mark@skynet.ie>
* William Jon McCann <mccann@jhu.edu>
* Martin Grimme <martin@pycage.de>
* Christian Kellner <gicmo@xatom.net>
*/
#include <config.h>
#include "calendar-sources.h"
#include <libintl.h>
#include <string.h>
#define HANDLE_LIBICAL_MEMORY
#include <libecal/libecal.h>
#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;
}