summaryrefslogtreecommitdiffstats
path: root/src/calendar-server
diff options
context:
space:
mode:
Diffstat (limited to 'src/calendar-server')
-rw-r--r--src/calendar-server/README1
-rw-r--r--src/calendar-server/calendar-debug.h50
-rw-r--r--src/calendar-server/calendar-sources.c506
-rw-r--r--src/calendar-server/calendar-sources.h64
-rw-r--r--src/calendar-server/evolution-calendar.desktop.in8
-rw-r--r--src/calendar-server/gnome-shell-calendar-server.c1131
-rw-r--r--src/calendar-server/meson.build37
-rw-r--r--src/calendar-server/org.gnome.Shell.CalendarServer.service.in3
8 files changed, 1800 insertions, 0 deletions
diff --git a/src/calendar-server/README b/src/calendar-server/README
new file mode 100644
index 0000000..ad9b5e3
--- /dev/null
+++ b/src/calendar-server/README
@@ -0,0 +1 @@
+Please keep in sync with gnome-panel.
diff --git a/src/calendar-server/calendar-debug.h b/src/calendar-server/calendar-debug.h
new file mode 100644
index 0000000..39befd7
--- /dev/null
+++ b/src/calendar-server/calendar-debug.h
@@ -0,0 +1,50 @@
+/*
+ * 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>
+ */
+
+#ifndef __CALENDAR_DEBUG_H__
+#define __CALENDAR_DEBUG_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#ifdef CALENDAR_ENABLE_DEBUG
+
+#include <stdio.h>
+
+#ifdef G_HAVE_ISO_VARARGS
+# define dprintf(...) fprintf (stderr, __VA_ARGS__);
+#elif defined(G_HAVE_GNUC_VARARGS)
+# define dprintf(args...) fprintf (stderr, args);
+#endif
+
+#else /* if !defined (CALENDAR_DEBUG) */
+
+#ifdef G_HAVE_ISO_VARARGS
+# define dprintf(...)
+#elif defined(G_HAVE_GNUC_VARARGS)
+# define dprintf(args...)
+#endif
+
+#endif /* CALENDAR_ENABLE_DEBUG */
+
+G_END_DECLS
+
+#endif /* __CALENDAR_DEBUG_H__ */
diff --git a/src/calendar-server/calendar-sources.c b/src/calendar-server/calendar-sources.c
new file mode 100644
index 0000000..9c25f4e
--- /dev/null
+++ b/src/calendar-server/calendar-sources.c
@@ -0,0 +1,506 @@
+/*
+ * 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
+#define EDS_DISABLE_DEPRECATED
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+#include <libecal/libecal.h>
+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 (&registry);
+
+ 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:
+ ;
+}
diff --git a/src/calendar-server/calendar-sources.h b/src/calendar-server/calendar-sources.h
new file mode 100644
index 0000000..1ffc8ad
--- /dev/null
+++ b/src/calendar-server/calendar-sources.h
@@ -0,0 +1,64 @@
+/*
+ * 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>
+ */
+
+#ifndef __CALENDAR_SOURCES_H__
+#define __CALENDAR_SOURCES_H__
+
+#include <glib-object.h>
+
+#define EDS_DISABLE_DEPRECATED
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+#include <libedataserver/libedataserver.h>
+#include <libecal/libecal.h>
+G_GNUC_END_IGNORE_DEPRECATIONS
+
+G_BEGIN_DECLS
+
+#define CALENDAR_TYPE_SOURCES (calendar_sources_get_type ())
+G_DECLARE_FINAL_TYPE (CalendarSources, calendar_sources,
+ CALENDAR, SOURCES, GObject)
+
+CalendarSources *calendar_sources_get (void);
+ESourceRegistry *calendar_sources_get_registry (CalendarSources *sources);
+GSList *calendar_sources_ref_clients (CalendarSources *sources);
+gboolean calendar_sources_has_clients (CalendarSources *sources);
+
+void calendar_sources_connect_client (CalendarSources *sources,
+ ESource *source,
+ ECalClientSourceType source_type,
+ guint32 wait_for_connected_seconds,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+EClient *calendar_sources_connect_client_finish
+ (CalendarSources *sources,
+ GAsyncResult *result,
+ GError **error);
+
+/* Set the environment variable CALENDAR_SERVER_DEBUG to show debug */
+void print_debug (const gchar *str,
+ ...) G_GNUC_PRINTF (1, 2);
+
+G_END_DECLS
+
+#endif /* __CALENDAR_SOURCES_H__ */
diff --git a/src/calendar-server/evolution-calendar.desktop.in b/src/calendar-server/evolution-calendar.desktop.in
new file mode 100644
index 0000000..1e34997
--- /dev/null
+++ b/src/calendar-server/evolution-calendar.desktop.in
@@ -0,0 +1,8 @@
+[Desktop Entry]
+Name=Evolution Calendar
+Exec=evolution -c calendar
+# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
+Icon=evolution
+NoDisplay=true
+Type=Application
+StartupNotify=true
diff --git a/src/calendar-server/gnome-shell-calendar-server.c b/src/calendar-server/gnome-shell-calendar-server.c
new file mode 100644
index 0000000..4cd28d1
--- /dev/null
+++ b/src/calendar-server/gnome-shell-calendar-server.c
@@ -0,0 +1,1131 @@
+/*
+ * Copyright (C) 2011 Red Hat, 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/>.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ *
+ * Based on code from gnome-panel's clock-applet, file calendar-client.c, with 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 <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <gio/gio.h>
+
+#define HANDLE_LIBICAL_MEMORY
+#define EDS_DISABLE_DEPRECATED
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+#include <libecal/libecal.h>
+G_GNUC_END_IGNORE_DEPRECATIONS
+
+#include "calendar-sources.h"
+
+#define BUS_NAME "org.gnome.Shell.CalendarServer"
+
+static const gchar introspection_xml[] =
+ "<node>"
+ " <interface name='org.gnome.Shell.CalendarServer'>"
+ " <method name='SetTimeRange'>"
+ " <arg type='x' name='since' direction='in'/>"
+ " <arg type='x' name='until' direction='in'/>"
+ " <arg type='b' name='force_reload' direction='in'/>"
+ " </method>"
+ " <signal name='EventsAddedOrUpdated'>"
+ " <arg type='a(ssxxa{sv})' name='events' direction='out'/>"
+ " </signal>"
+ " <signal name='EventsRemoved'>"
+ " <arg type='as' name='ids' direction='out'/>"
+ " </signal>"
+ " <signal name='ClientDisappeared'>"
+ " <arg type='s' name='source_uid' direction='out'/>"
+ " </signal>"
+ " <property name='Since' type='x' access='read'/>"
+ " <property name='Until' type='x' access='read'/>"
+ " <property name='HasCalendars' type='b' access='read'/>"
+ " </interface>"
+ "</node>";
+static GDBusNodeInfo *introspection_data = NULL;
+
+struct _App;
+typedef struct _App App;
+
+static gboolean opt_replace = FALSE;
+static GOptionEntry opt_entries[] = {
+ {"replace", 0, 0, G_OPTION_ARG_NONE, &opt_replace, "Replace existing daemon", NULL},
+ {NULL }
+};
+static App *_global_app = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* While the UID is usually enough to identify an event,
+ * only the triple of (source,UID,RID) is fully unambiguous;
+ * neither may contain '\n', so we can safely use it to
+ * create a unique ID from the triple
+ */
+static gchar *
+create_event_id (const gchar *source_uid,
+ const gchar *comp_uid,
+ const gchar *comp_rid)
+{
+ return g_strconcat (
+ source_uid ? source_uid : "",
+ "\n",
+ comp_uid ? comp_uid : "",
+ "\n",
+ comp_rid ? comp_rid : "",
+ NULL);
+}
+
+typedef struct
+{
+ ECalClient *client;
+ GSList **pappointments; /* CalendarAppointment * */
+} CollectAppointmentsData;
+
+typedef struct
+{
+ gchar *id;
+ gchar *summary;
+ time_t start_time;
+ time_t end_time;
+} CalendarAppointment;
+
+static gboolean
+get_time_from_property (ECalClient *cal,
+ ICalComponent *icomp,
+ ICalPropertyKind prop_kind,
+ ICalTime * (* get_prop_func) (ICalProperty *prop),
+ ICalTimezone *default_zone,
+ ICalTime **out_itt,
+ ICalTimezone **out_timezone)
+{
+ ICalProperty *prop;
+ ICalTime *itt;
+ ICalTimezone *timezone = NULL;
+
+ prop = i_cal_component_get_first_property (icomp, prop_kind);
+ if (!prop)
+ return FALSE;
+
+ itt = get_prop_func (prop);
+
+ if (i_cal_time_is_utc (itt))
+ timezone = i_cal_timezone_get_utc_timezone ();
+ else
+ {
+ ICalParameter *param;
+
+ param = i_cal_property_get_first_parameter (prop, I_CAL_TZID_PARAMETER);
+ if (param && !e_cal_client_get_timezone_sync (cal, i_cal_parameter_get_tzid (param), &timezone, NULL, NULL))
+ print_debug ("Failed to get timezone '%s'\n", i_cal_parameter_get_tzid (param));
+
+ g_clear_object (&param);
+ }
+
+ if (timezone == NULL)
+ timezone = default_zone;
+
+ i_cal_time_set_timezone (itt, timezone);
+
+ g_clear_object (&prop);
+
+ *out_itt = itt;
+ *out_timezone = timezone;
+
+ return TRUE;
+}
+
+static inline time_t
+get_ical_start_time (ECalClient *cal,
+ ICalComponent *icomp,
+ ICalTimezone *default_zone)
+{
+ ICalTime *itt;
+ ICalTimezone *timezone;
+ time_t retval;
+
+ if (!get_time_from_property (cal,
+ icomp,
+ I_CAL_DTSTART_PROPERTY,
+ i_cal_property_get_dtstart,
+ default_zone,
+ &itt,
+ &timezone))
+ {
+ return 0;
+ }
+
+ retval = i_cal_time_as_timet_with_zone (itt, timezone);
+
+ g_clear_object (&itt);
+
+ return retval;
+}
+
+static inline time_t
+get_ical_end_time (ECalClient *cal,
+ ICalComponent *icomp,
+ ICalTimezone *default_zone)
+{
+ ICalTime *itt;
+ ICalTimezone *timezone;
+ time_t retval;
+
+ if (!get_time_from_property (cal,
+ icomp,
+ I_CAL_DTEND_PROPERTY,
+ i_cal_property_get_dtend,
+ default_zone,
+ &itt,
+ &timezone))
+ {
+ if (!get_time_from_property (cal,
+ icomp,
+ I_CAL_DTSTART_PROPERTY,
+ i_cal_property_get_dtstart,
+ default_zone,
+ &itt,
+ &timezone))
+ {
+ return 0;
+ }
+
+ if (i_cal_time_is_date (itt))
+ i_cal_time_adjust (itt, 1, 0, 0, 0);
+ }
+
+ retval = i_cal_time_as_timet_with_zone (itt, timezone);
+
+ g_clear_object (&itt);
+
+ return retval;
+}
+
+static CalendarAppointment *
+calendar_appointment_new (ECalClient *cal,
+ ECalComponent *comp)
+{
+ CalendarAppointment *appt;
+ ICalTimezone *default_zone;
+ ICalComponent *ical;
+ ECalComponentId *id;
+
+ default_zone = e_cal_client_get_default_timezone (cal);
+ ical = e_cal_component_get_icalcomponent (comp);
+ id = e_cal_component_get_id (comp);
+
+ appt = g_new0 (CalendarAppointment, 1);
+
+ appt->id = create_event_id (e_source_get_uid (e_client_get_source (E_CLIENT (cal))),
+ id ? e_cal_component_id_get_uid (id) : NULL,
+ id ? e_cal_component_id_get_rid (id) : NULL);
+ appt->summary = g_strdup (i_cal_component_get_summary (ical));
+ appt->start_time = get_ical_start_time (cal, ical, default_zone);
+ appt->end_time = get_ical_end_time (cal, ical, default_zone);
+
+ e_cal_component_id_free (id);
+
+ return appt;
+}
+
+static void
+calendar_appointment_free (gpointer ptr)
+{
+ CalendarAppointment *appt = ptr;
+
+ if (appt)
+ {
+ g_free (appt->id);
+ g_free (appt->summary);
+ g_free (appt);
+ }
+}
+
+static time_t
+timet_from_ical_time (ICalTime *time,
+ ICalTimezone *default_zone)
+{
+ ICalTimezone *timezone = NULL;
+
+ timezone = i_cal_time_get_timezone (time);
+ if (timezone == NULL)
+ timezone = default_zone;
+ return i_cal_time_as_timet_with_zone (time, timezone);
+}
+
+static gboolean
+generate_instances_cb (ICalComponent *icomp,
+ ICalTime *instance_start,
+ ICalTime *instance_end,
+ gpointer user_data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ CollectAppointmentsData *data = user_data;
+ CalendarAppointment *appointment;
+ ECalComponent *comp;
+ ICalTimezone *default_zone;
+
+ default_zone = e_cal_client_get_default_timezone (data->client);
+ comp = e_cal_component_new_from_icalcomponent (i_cal_component_clone (icomp));
+
+ appointment = calendar_appointment_new (data->client, comp);
+ appointment->start_time = timet_from_ical_time (instance_start, default_zone);
+ appointment->end_time = timet_from_ical_time (instance_end, default_zone);
+
+ *(data->pappointments) = g_slist_prepend (*(data->pappointments), appointment);
+
+ g_clear_object (&comp);
+
+ return TRUE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+struct _App
+{
+ GDBusConnection *connection;
+
+ time_t since;
+ time_t until;
+
+ ICalTimezone *zone;
+
+ CalendarSources *sources;
+ gulong client_appeared_signal_id;
+ gulong client_disappeared_signal_id;
+
+ gchar *timezone_location;
+
+ GSList *notify_appointments; /* CalendarAppointment *, for EventsAdded */
+ GSList *notify_ids; /* gchar *, for EventsRemoved */
+
+ GSList *live_views;
+};
+
+static void
+app_update_timezone (App *app)
+{
+ g_autofree char *location = NULL;
+
+ location = e_cal_system_timezone_get_location ();
+ if (g_strcmp0 (location, app->timezone_location) != 0)
+ {
+ if (location == NULL)
+ app->zone = i_cal_timezone_get_utc_timezone ();
+ else
+ app->zone = i_cal_timezone_get_builtin_timezone (location);
+ g_free (app->timezone_location);
+ app->timezone_location = g_steal_pointer (&location);
+ print_debug ("Using timezone %s", app->timezone_location);
+ }
+}
+
+static void
+app_notify_events_added (App *app)
+{
+ GVariantBuilder builder, extras_builder;
+ GSList *events, *link;
+
+ events = g_slist_reverse (app->notify_appointments);
+ app->notify_appointments = NULL;
+
+ print_debug ("Emitting EventsAddedOrUpdated with %d events", g_slist_length (events));
+
+ if (!events)
+ return;
+
+ /* The a{sv} is used as an escape hatch in case we want to provide more
+ * information in the future without breaking ABI
+ */
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ssxxa{sv})"));
+ for (link = events; link; link = g_slist_next (link))
+ {
+ CalendarAppointment *appt = link->data;
+ time_t start_time = appt->start_time;
+ time_t end_time = appt->end_time;
+
+ if ((start_time >= app->since &&
+ start_time < app->until) ||
+ (start_time <= app->since &&
+ (end_time - 1) > app->since))
+ {
+ g_variant_builder_init (&extras_builder, G_VARIANT_TYPE ("a{sv}"));
+ g_variant_builder_add (&builder,
+ "(ssxxa{sv})",
+ appt->id,
+ appt->summary != NULL ? appt->summary : "",
+ (gint64) start_time,
+ (gint64) end_time,
+ &extras_builder);
+ }
+ }
+
+ g_dbus_connection_emit_signal (app->connection,
+ NULL, /* destination_bus_name */
+ "/org/gnome/Shell/CalendarServer",
+ "org.gnome.Shell.CalendarServer",
+ "EventsAddedOrUpdated",
+ g_variant_new ("(a(ssxxa{sv}))", &builder),
+ NULL);
+
+ g_variant_builder_clear (&builder);
+
+ g_slist_free_full (events, calendar_appointment_free);
+}
+
+static void
+app_notify_events_removed (App *app)
+{
+ GVariantBuilder builder;
+ GSList *ids, *link;
+
+ ids = app->notify_ids;
+ app->notify_ids = NULL;
+
+ print_debug ("Emitting EventsRemoved with %d ids", g_slist_length (ids));
+
+ if (!ids)
+ return;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
+ for (link = ids; link; link = g_slist_next (link))
+ {
+ const gchar *id = link->data;
+
+ g_variant_builder_add (&builder, "s", id);
+ }
+
+ g_dbus_connection_emit_signal (app->connection,
+ NULL, /* destination_bus_name */
+ "/org/gnome/Shell/CalendarServer",
+ "org.gnome.Shell.CalendarServer",
+ "EventsRemoved",
+ g_variant_new ("(as)", &builder),
+ NULL);
+ g_variant_builder_clear (&builder);
+
+ g_slist_free_full (ids, g_free);
+
+ return;
+}
+
+static void
+app_process_added_modified_objects (App *app,
+ ECalClientView *view,
+ GSList *objects) /* ICalComponent * */
+{
+ ECalClient *cal_client;
+ g_autoptr(GHashTable) covered_uids = NULL;
+ GSList *link;
+ gboolean expand_recurrences;
+
+ cal_client = e_cal_client_view_ref_client (view);
+ covered_uids = g_hash_table_new (g_str_hash, g_str_equal);
+ expand_recurrences = e_cal_client_get_source_type (cal_client) == E_CAL_CLIENT_SOURCE_TYPE_EVENTS;
+
+ for (link = objects; link; link = g_slist_next (link))
+ {
+ ECalComponent *comp;
+ ICalComponent *icomp = link->data;
+ const gchar *uid;
+ gboolean fallback = FALSE;
+
+ if (!icomp)
+ continue;
+
+ uid = i_cal_component_get_uid (icomp);
+ if (!uid || g_hash_table_contains (covered_uids, uid))
+ continue;
+
+ g_hash_table_add (covered_uids, (gpointer) uid);
+
+ if (expand_recurrences &&
+ !e_cal_util_component_is_instance (icomp) &&
+ e_cal_util_component_has_recurrences (icomp))
+ {
+ CollectAppointmentsData data;
+
+ data.client = cal_client;
+ data.pappointments = &app->notify_appointments;
+
+ e_cal_client_generate_instances_for_object_sync (cal_client, icomp, app->since, app->until, NULL,
+ generate_instances_cb, &data);
+ }
+ else if (expand_recurrences &&
+ e_cal_util_component_is_instance (icomp))
+ {
+ ICalComponent *main_comp = NULL;
+
+ /* Always pass whole series of the recurring events, because
+ * the calendar removes events with the same UID first. */
+ if (e_cal_client_get_object_sync (cal_client, uid, NULL, &main_comp, NULL, NULL))
+ {
+ CollectAppointmentsData data;
+
+ data.client = cal_client;
+ data.pappointments = &app->notify_appointments;
+
+ e_cal_client_generate_instances_for_object_sync (cal_client, main_comp, app->since, app->until, NULL,
+ generate_instances_cb, &data);
+
+ g_clear_object (&main_comp);
+ }
+ else
+ {
+ fallback = TRUE;
+ }
+ }
+ else
+ {
+ fallback = TRUE;
+ }
+
+ if (fallback)
+ {
+ comp = e_cal_component_new_from_icalcomponent (i_cal_component_clone (icomp));
+ if (!comp)
+ continue;
+
+ app->notify_appointments = g_slist_prepend (app->notify_appointments,
+ calendar_appointment_new (cal_client, comp));
+ g_object_unref (comp);
+ }
+ }
+
+ g_clear_object (&cal_client);
+
+ if (app->notify_appointments)
+ app_notify_events_added (app);
+}
+
+static void
+on_objects_added (ECalClientView *view,
+ GSList *objects,
+ gpointer user_data)
+{
+ App *app = user_data;
+ ECalClient *client;
+
+ client = e_cal_client_view_ref_client (view);
+ print_debug ("%s (%d) for calendar '%s'", G_STRFUNC, g_slist_length (objects), e_source_get_uid (e_client_get_source (E_CLIENT (client))));
+ g_clear_object (&client);
+
+ app_process_added_modified_objects (app, view, objects);
+}
+
+static void
+on_objects_modified (ECalClientView *view,
+ GSList *objects,
+ gpointer user_data)
+{
+ App *app = user_data;
+ ECalClient *client;
+
+ client = e_cal_client_view_ref_client (view);
+ print_debug ("%s (%d) for calendar '%s'", G_STRFUNC, g_slist_length (objects), e_source_get_uid (e_client_get_source (E_CLIENT (client))));
+ g_clear_object (&client);
+
+ app_process_added_modified_objects (app, view, objects);
+}
+
+static void
+on_objects_removed (ECalClientView *view,
+ GSList *uids,
+ gpointer user_data)
+{
+ App *app = user_data;
+ ECalClient *client;
+ GSList *link;
+ const gchar *source_uid;
+
+ client = e_cal_client_view_ref_client (view);
+ source_uid = e_source_get_uid (e_client_get_source (E_CLIENT (client)));
+
+ print_debug ("%s (%d) for calendar '%s'", G_STRFUNC, g_slist_length (uids), source_uid);
+
+ for (link = uids; link; link = g_slist_next (link))
+ {
+ ECalComponentId *id = link->data;
+
+ if (!id)
+ continue;
+
+ app->notify_ids = g_slist_prepend (app->notify_ids,
+ create_event_id (source_uid,
+ e_cal_component_id_get_uid (id),
+ e_cal_component_id_get_rid (id)));
+ }
+
+ g_clear_object (&client);
+
+ if (app->notify_ids)
+ app_notify_events_removed (app);
+}
+
+static gboolean
+app_has_calendars (App *app)
+{
+ return app->live_views != NULL;
+}
+
+static ECalClientView *
+app_start_view (App *app,
+ ECalClient *cal_client)
+{
+ g_autofree char *since_iso8601 = NULL;
+ g_autofree char *until_iso8601 = NULL;
+ g_autofree char *query = NULL;
+ const gchar *tz_location;
+ ECalClientView *view = NULL;
+ g_autoptr (GError) error = NULL;
+
+ if (app->since <= 0 || app->since >= app->until)
+ return NULL;
+
+ if (!app->since || !app->until)
+ {
+ print_debug ("Skipping load of events, no time interval set yet");
+ return NULL;
+ }
+
+ /* timezone could have changed */
+ app_update_timezone (app);
+
+ since_iso8601 = isodate_from_time_t (app->since);
+ until_iso8601 = isodate_from_time_t (app->until);
+ tz_location = i_cal_timezone_get_location (app->zone);
+
+ print_debug ("Loading events since %s until %s for calendar '%s'",
+ since_iso8601,
+ until_iso8601,
+ e_source_get_uid (e_client_get_source (E_CLIENT (cal_client))));
+
+ query = g_strdup_printf ("occur-in-time-range? (make-time \"%s\") "
+ "(make-time \"%s\") \"%s\"",
+ since_iso8601,
+ until_iso8601,
+ tz_location);
+
+ e_cal_client_set_default_timezone (cal_client, app->zone);
+
+ if (!e_cal_client_get_view_sync (cal_client, query, &view, NULL /* cancellable */, &error))
+ {
+ g_warning ("Error setting up live-query '%s' on calendar: %s\n", query, error ? error->message : "Unknown error");
+ view = NULL;
+ }
+ else
+ {
+ g_signal_connect (view,
+ "objects-added",
+ G_CALLBACK (on_objects_added),
+ app);
+ g_signal_connect (view,
+ "objects-modified",
+ G_CALLBACK (on_objects_modified),
+ app);
+ g_signal_connect (view,
+ "objects-removed",
+ G_CALLBACK (on_objects_removed),
+ app);
+ e_cal_client_view_start (view, NULL);
+ }
+
+ return view;
+}
+
+static void
+app_stop_view (App *app,
+ ECalClientView *view)
+{
+ e_cal_client_view_stop (view, NULL);
+
+ g_signal_handlers_disconnect_by_func (view, on_objects_added, app);
+ g_signal_handlers_disconnect_by_func (view, on_objects_modified, app);
+ g_signal_handlers_disconnect_by_func (view, on_objects_removed, app);
+}
+
+static void
+app_notify_has_calendars (App *app)
+{
+ GVariantBuilder dict_builder;
+
+ g_variant_builder_init (&dict_builder, G_VARIANT_TYPE ("a{sv}"));
+ g_variant_builder_add (&dict_builder, "{sv}", "HasCalendars",
+ g_variant_new_boolean (app_has_calendars (app)));
+
+ g_dbus_connection_emit_signal (app->connection,
+ NULL,
+ "/org/gnome/Shell/CalendarServer",
+ "org.freedesktop.DBus.Properties",
+ "PropertiesChanged",
+ g_variant_new ("(sa{sv}as)",
+ "org.gnome.Shell.CalendarServer",
+ &dict_builder,
+ NULL),
+ NULL);
+ g_variant_builder_clear (&dict_builder);
+}
+
+static void
+app_update_views (App *app)
+{
+ GSList *link, *clients;
+ gboolean had_views, has_views;
+
+ had_views = app->live_views != NULL;
+
+ for (link = app->live_views; link; link = g_slist_next (link))
+ {
+ app_stop_view (app, link->data);
+ }
+
+ g_slist_free_full (app->live_views, g_object_unref);
+ app->live_views = NULL;
+
+ clients = calendar_sources_ref_clients (app->sources);
+
+ for (link = clients; link; link = g_slist_next (link))
+ {
+ ECalClient *cal_client = link->data;
+ ECalClientView *view;
+
+ if (!cal_client)
+ continue;
+
+ view = app_start_view (app, cal_client);
+ if (view)
+ app->live_views = g_slist_prepend (app->live_views, view);
+ }
+
+ has_views = app->live_views != NULL;
+
+ if (has_views != had_views)
+ app_notify_has_calendars (app);
+
+ g_slist_free_full (clients, g_object_unref);
+}
+
+static void
+on_client_appeared_cb (CalendarSources *sources,
+ ECalClient *client,
+ gpointer user_data)
+{
+ App *app = user_data;
+ ECalClientView *view;
+ GSList *link;
+ const gchar *source_uid;
+
+ source_uid = e_source_get_uid (e_client_get_source (E_CLIENT (client)));
+
+ print_debug ("Client appeared '%s'", source_uid);
+
+ for (link = app->live_views; link; link = g_slist_next (link))
+ {
+ ECalClientView *view = link->data;
+ ECalClient *cal_client;
+ ESource *source;
+
+ cal_client = e_cal_client_view_ref_client (view);
+ source = e_client_get_source (E_CLIENT (cal_client));
+
+ if (g_strcmp0 (source_uid, e_source_get_uid (source)) == 0)
+ {
+ g_clear_object (&cal_client);
+ return;
+ }
+
+ g_clear_object (&cal_client);
+ }
+
+ view = app_start_view (app, client);
+
+ if (view)
+ {
+ app->live_views = g_slist_prepend (app->live_views, view);
+
+ /* It's the first view, notify that it has calendars now */
+ if (!g_slist_next (app->live_views))
+ app_notify_has_calendars (app);
+ }
+}
+
+static void
+on_client_disappeared_cb (CalendarSources *sources,
+ const gchar *source_uid,
+ gpointer user_data)
+{
+ App *app = user_data;
+ GSList *link;
+
+ print_debug ("Client disappeared '%s'", source_uid);
+
+ for (link = app->live_views; link; link = g_slist_next (link))
+ {
+ ECalClientView *view = link->data;
+ ECalClient *cal_client;
+ ESource *source;
+
+ cal_client = e_cal_client_view_ref_client (view);
+ source = e_client_get_source (E_CLIENT (cal_client));
+
+ if (g_strcmp0 (source_uid, e_source_get_uid (source)) == 0)
+ {
+ g_clear_object (&cal_client);
+ app_stop_view (app, view);
+ app->live_views = g_slist_remove (app->live_views, view);
+ g_object_unref (view);
+
+ print_debug ("Emitting ClientDisappeared for '%s'", source_uid);
+
+ g_dbus_connection_emit_signal (app->connection,
+ NULL, /* destination_bus_name */
+ "/org/gnome/Shell/CalendarServer",
+ "org.gnome.Shell.CalendarServer",
+ "ClientDisappeared",
+ g_variant_new ("(s)", source_uid),
+ NULL);
+
+ /* It was the last view, notify that it doesn't have calendars now */
+ if (!app->live_views)
+ app_notify_has_calendars (app);
+
+ break;
+ }
+
+ g_clear_object (&cal_client);
+ }
+}
+
+static App *
+app_new (GDBusConnection *connection)
+{
+ App *app;
+
+ app = g_new0 (App, 1);
+ app->connection = g_object_ref (connection);
+ app->sources = calendar_sources_get ();
+ app->client_appeared_signal_id = g_signal_connect (app->sources,
+ "client-appeared",
+ G_CALLBACK (on_client_appeared_cb),
+ app);
+ app->client_disappeared_signal_id = g_signal_connect (app->sources,
+ "client-disappeared",
+ G_CALLBACK (on_client_disappeared_cb),
+ app);
+
+ app_update_timezone (app);
+
+ return app;
+}
+
+static void
+app_free (App *app)
+{
+ GSList *ll;
+
+ for (ll = app->live_views; ll != NULL; ll = g_slist_next (ll))
+ {
+ ECalClientView *view = E_CAL_CLIENT_VIEW (ll->data);
+
+ app_stop_view (app, view);
+ }
+
+ g_signal_handler_disconnect (app->sources,
+ app->client_appeared_signal_id);
+ g_signal_handler_disconnect (app->sources,
+ app->client_disappeared_signal_id);
+
+ g_free (app->timezone_location);
+
+ g_slist_free_full (app->live_views, g_object_unref);
+ g_slist_free_full (app->notify_appointments, calendar_appointment_free);
+ g_slist_free_full (app->notify_ids, g_free);
+
+ g_object_unref (app->connection);
+ g_object_unref (app->sources);
+
+ g_free (app);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+handle_method_call (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ App *app = user_data;
+
+ if (g_strcmp0 (method_name, "SetTimeRange") == 0)
+ {
+ gint64 since;
+ gint64 until;
+ gboolean force_reload = FALSE;
+ gboolean window_changed = FALSE;
+
+ g_variant_get (parameters,
+ "(xxb)",
+ &since,
+ &until,
+ &force_reload);
+
+ if (until < since)
+ {
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.gnome.Shell.CalendarServer.Error.Failed",
+ "until cannot be before since");
+ goto out;
+ }
+
+ print_debug ("Handling SetTimeRange (since=%" G_GINT64_FORMAT ", until=%" G_GINT64_FORMAT ", force_reload=%s)",
+ since,
+ until,
+ force_reload ? "true" : "false");
+
+ if (app->until != until || app->since != since)
+ {
+ GVariantBuilder *builder;
+ GVariantBuilder *invalidated_builder;
+
+ app->until = until;
+ app->since = since;
+ window_changed = TRUE;
+
+ builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
+ invalidated_builder = g_variant_builder_new (G_VARIANT_TYPE ("as"));
+ g_variant_builder_add (builder, "{sv}",
+ "Until", g_variant_new_int64 (app->until));
+ g_variant_builder_add (builder, "{sv}",
+ "Since", g_variant_new_int64 (app->since));
+ g_dbus_connection_emit_signal (app->connection,
+ NULL, /* destination_bus_name */
+ "/org/gnome/Shell/CalendarServer",
+ "org.freedesktop.DBus.Properties",
+ "PropertiesChanged",
+ g_variant_new ("(sa{sv}as)",
+ "org.gnome.Shell.CalendarServer",
+ builder,
+ invalidated_builder),
+ NULL); /* GError** */
+
+ g_variant_builder_unref (builder);
+ g_variant_builder_unref (invalidated_builder);
+ }
+
+ g_dbus_method_invocation_return_value (invocation, NULL);
+
+ if (window_changed || force_reload)
+ app_update_views (app);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ out:
+ ;
+}
+
+static GVariant *
+handle_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ App *app = user_data;
+ GVariant *ret;
+
+ ret = NULL;
+ if (g_strcmp0 (property_name, "Since") == 0)
+ {
+ ret = g_variant_new_int64 (app->since);
+ }
+ else if (g_strcmp0 (property_name, "Until") == 0)
+ {
+ ret = g_variant_new_int64 (app->until);
+ }
+ else if (g_strcmp0 (property_name, "HasCalendars") == 0)
+ {
+ ret = g_variant_new_boolean (app_has_calendars (app));
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+ return ret;
+}
+
+static const GDBusInterfaceVTable interface_vtable =
+{
+ handle_method_call,
+ handle_get_property,
+ NULL /* handle_set_property */
+};
+
+static void
+on_bus_acquired (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ GMainLoop *main_loop = user_data;
+ guint registration_id;
+ g_autoptr (GError) error = NULL;
+
+ _global_app = app_new (connection);
+
+ registration_id = g_dbus_connection_register_object (connection,
+ "/org/gnome/Shell/CalendarServer",
+ introspection_data->interfaces[0],
+ &interface_vtable,
+ _global_app,
+ NULL, /* user_data_free_func */
+ &error);
+ if (registration_id == 0)
+ {
+ g_printerr ("Error exporting object: %s (%s %d)\n",
+ error->message,
+ g_quark_to_string (error->domain),
+ error->code);
+ g_main_loop_quit (main_loop);
+ return;
+ }
+
+ print_debug ("Connected to the session bus");
+}
+
+static void
+on_name_lost (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ GMainLoop *main_loop = user_data;
+
+ g_print ("gnome-shell-calendar-server[%d]: Lost (or failed to acquire) the name " BUS_NAME " - exiting\n",
+ (gint) getpid ());
+ g_main_loop_quit (main_loop);
+}
+
+static void
+on_name_acquired (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ print_debug ("Acquired the name " BUS_NAME);
+}
+
+static gboolean
+stdin_channel_io_func (GIOChannel *source,
+ GIOCondition condition,
+ gpointer data)
+{
+ GMainLoop *main_loop = data;
+
+ if (condition & G_IO_HUP)
+ {
+ g_debug ("gnome-shell-calendar-server[%d]: Got HUP on stdin - exiting\n",
+ (gint) getpid ());
+ g_main_loop_quit (main_loop);
+ }
+ else
+ {
+ g_warning ("Unhandled condition %d on GIOChannel for stdin", condition);
+ }
+ return FALSE; /* remove source */
+}
+
+int
+main (int argc,
+ char **argv)
+{
+ g_autoptr (GError) error = NULL;
+ GOptionContext *opt_context;
+ GMainLoop *main_loop;
+ gint ret;
+ guint name_owner_id;
+ GIOChannel *stdin_channel;
+
+ ret = 1;
+ opt_context = NULL;
+ name_owner_id = 0;
+ stdin_channel = NULL;
+
+ introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+ g_assert (introspection_data != NULL);
+
+ opt_context = g_option_context_new ("gnome-shell calendar server");
+ g_option_context_add_main_entries (opt_context, opt_entries, NULL);
+ if (!g_option_context_parse (opt_context, &argc, &argv, &error))
+ {
+ g_printerr ("Error parsing options: %s\n", error->message);
+ goto out;
+ }
+
+ main_loop = g_main_loop_new (NULL, FALSE);
+
+ stdin_channel = g_io_channel_unix_new (STDIN_FILENO);
+ g_io_add_watch_full (stdin_channel,
+ G_PRIORITY_DEFAULT,
+ G_IO_HUP,
+ stdin_channel_io_func,
+ g_main_loop_ref (main_loop),
+ (GDestroyNotify) g_main_loop_unref);
+
+ name_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
+ BUS_NAME,
+ G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
+ (opt_replace ? G_BUS_NAME_OWNER_FLAGS_REPLACE : 0),
+ on_bus_acquired,
+ on_name_acquired,
+ on_name_lost,
+ g_main_loop_ref (main_loop),
+ (GDestroyNotify) g_main_loop_unref);
+
+ g_main_loop_run (main_loop);
+
+ g_main_loop_unref (main_loop);
+
+ ret = 0;
+
+ out:
+ if (stdin_channel != NULL)
+ g_io_channel_unref (stdin_channel);
+ if (_global_app != NULL)
+ app_free (_global_app);
+ if (name_owner_id != 0)
+ g_bus_unown_name (name_owner_id);
+ if (opt_context != NULL)
+ g_option_context_free (opt_context);
+
+ return ret;
+}
diff --git a/src/calendar-server/meson.build b/src/calendar-server/meson.build
new file mode 100644
index 0000000..8b4ef41
--- /dev/null
+++ b/src/calendar-server/meson.build
@@ -0,0 +1,37 @@
+calendar_sources = [
+ 'gnome-shell-calendar-server.c',
+ 'calendar-debug.h',
+ 'calendar-sources.c',
+ 'calendar-sources.h'
+]
+
+calendar_server = executable('gnome-shell-calendar-server', calendar_sources,
+ dependencies: [ecal_dep, eds_dep, gio_dep],
+ include_directories: include_directories('..', '../..'),
+ c_args: [
+ '-DPREFIX="@0@"'.format(prefix),
+ '-DLIBDIR="@0@"'.format(libdir),
+ '-DDATADIR="@0@"'.format(datadir),
+ '-DG_LOG_DOMAIN="ShellCalendarServer"'
+ ],
+ install_dir: libexecdir,
+ install: true
+)
+
+service_file = 'org.gnome.Shell.CalendarServer.service'
+
+configure_file(
+ input: service_file + '.in',
+ output: service_file,
+ configuration: service_data,
+ install_dir: servicedir
+)
+
+i18n.merge_file(
+ input: 'evolution-calendar.desktop.in',
+ output: 'evolution-calendar.desktop',
+ po_dir: po_dir,
+ install: true,
+ install_dir: desktopdir,
+ type: 'desktop'
+)
diff --git a/src/calendar-server/org.gnome.Shell.CalendarServer.service.in b/src/calendar-server/org.gnome.Shell.CalendarServer.service.in
new file mode 100644
index 0000000..5addce6
--- /dev/null
+++ b/src/calendar-server/org.gnome.Shell.CalendarServer.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.gnome.Shell.CalendarServer
+Exec=@libexecdir@/gnome-shell-calendar-server