569 lines
18 KiB
C
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;
|
|
}
|