File gnome-shell-Improve-performance-by-properly-using-ECalClientView.patch of Package gnome-shell.19696

commit 135d178d088800992e3526fafb659788bb42ba34
Author: Robert Mader <robert.mader@posteo.de>
Date:   Thu Nov 21 23:00:53 2019 +0100

    Subject: [PATCH 1/6] cleanup: Use g_clear_signal_handler() where possible
    
    `g_clear_signal_handler()` is usually cleaner and saver than
    `g_signal_handler_disconnect()`. We use it new code, lets also
    adopt the existing one.
    
    See also https://gitlab.gnome.org/GNOME/mutter/merge_requests/868
    and https://gitlab.gnome.org/GNOME/mutter/merge_requests/940
    
    https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/842

diff --git a/src/calendar-server/calendar-sources.c b/src/calendar-server/calendar-sources.c
index ed76c015c..7e4c7f52e 100644
--- a/src/calendar-server/calendar-sources.c
+++ b/src/calendar-server/calendar-sources.c
@@ -112,7 +112,7 @@ static CalendarSources *calendar_sources_singleton = NULL;
 static void
 client_data_free (ClientData *data)
 {
-  g_signal_handler_disconnect (data->client, data->backend_died_id);
+  g_clear_signal_handler (&data->backend_died_id, data->client);
   g_object_unref (data->client);
   g_slice_free (ClientData, data);
 }
@@ -259,12 +259,12 @@ calendar_sources_finalize (GObject *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_clear_signal_handler (&sources->priv->source_added_id,
+                              sources->priv->registry);
+      g_clear_signal_handler (&sources->priv->source_changed_id,
+                              sources->priv->registry);
+      g_clear_signal_handler (&sources->priv->source_removed_id,
+                              sources->priv->registry);
       g_object_unref (sources->priv->registry);
     }
   sources->priv->registry = NULL;
diff --git a/src/calendar-server/gnome-shell-calendar-server.c b/src/calendar-server/gnome-shell-calendar-server.c
index 9c88a2f9e..d04f0019c 100644
--- a/src/calendar-server/gnome-shell-calendar-server.c
+++ b/src/calendar-server/gnome-shell-calendar-server.c
@@ -789,8 +789,7 @@ app_free (App *app)
   g_hash_table_unref (app->appointments);
 
   g_object_unref (app->connection);
-  g_signal_handler_disconnect (app->sources,
-                               app->sources_signal_id);
+  g_clear_signal_handler (&app->sources_signal_id, app->sources);
   g_object_unref (app->sources);
 
   if (app->changed_timeout_id != 0)
commit c48330a9861dcb62f962539e83b7479642e06698
Author: Robert Mader <robert.mader@posteo.de>
Date:   Fri Nov 22 01:42:14 2019 +0100

    Subject: [PATCH 2/6] cleanup: Use g_clear_handle_id() for g_source_remove()
    
    It makes sure we do not forget to zero the id and lets us avoid
    zero checks before. We use it for all new code, lets clean up the
    existing code base.
    
    https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/845

diff --git a/src/calendar-server/calendar-sources.c b/src/calendar-server/calendar-sources.c
index 7e4c7f52e..bdc145421 100644
--- a/src/calendar-server/calendar-sources.c
+++ b/src/calendar-server/calendar-sources.c
@@ -242,11 +242,7 @@ calendar_sources_finalize_source_data (CalendarSources    *sources,
       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;
-        }
+      g_clear_handle_id (&source_data->timeout_id, g_source_remove);
 
       source_data->loaded = FALSE;
     }
@@ -374,11 +370,7 @@ backend_died_cb (EClient *client, CalendarSourceData *source_data)
   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;
-    }
+  g_clear_handle_id (&source_data->timeout_id, g_source_remove);
 
   source_data->timeout_id = g_timeout_add_seconds (2, backend_restart,
 		  				   source_data);
diff --git a/src/calendar-server/gnome-shell-calendar-server.c b/src/calendar-server/gnome-shell-calendar-server.c
index d04f0019c..25cb694db 100644
--- a/src/calendar-server/gnome-shell-calendar-server.c
+++ b/src/calendar-server/gnome-shell-calendar-server.c
@@ -792,8 +792,7 @@ app_free (App *app)
   g_clear_signal_handler (&app->sources_signal_id, app->sources);
   g_object_unref (app->sources);
 
-  if (app->changed_timeout_id != 0)
-    g_source_remove (app->changed_timeout_id);
+  g_clear_handle_id (&app->changed_timeout_id, g_source_remove);
 
   g_free (app);
 }
From 20648e9207045c98f1f450cf929d2f257f09b179 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Wed, 22 Apr 2020 16:04:05 +0200
Subject: [PATCH 3/6] calendar-server: Replace tabs with spaces

... according to the coding style.

https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/1875
---
 src/calendar-server/calendar-sources.c        | 46 +++++++++----------
 .../gnome-shell-calendar-server.c             |  8 ++--
 2 files changed, 27 insertions(+), 27 deletions(-)

diff --git a/src/calendar-server/calendar-sources.c b/src/calendar-server/calendar-sources.c
index a0da1cedf..145304a92 100644
--- a/src/calendar-server/calendar-sources.c
+++ b/src/calendar-server/calendar-sources.c
@@ -120,25 +120,25 @@ calendar_sources_class_init (CalendarSourcesClass *klass)
 
   signals [APPOINTMENT_SOURCES_CHANGED] =
     g_signal_new ("appointment-sources-changed",
-		  G_TYPE_FROM_CLASS (gobject_class),
-		  G_SIGNAL_RUN_LAST,
-		  0,
-		  NULL,
-		  NULL,
+                  G_TYPE_FROM_CLASS (gobject_class),
+                  G_SIGNAL_RUN_LAST,
+                  0,
                   NULL,
-		  G_TYPE_NONE,
-		  0);
+                  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,
+                  G_TYPE_FROM_CLASS (gobject_class),
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL,
+                  NULL,
                   NULL,
-		  G_TYPE_NONE,
-		  0);
+                  G_TYPE_NONE,
+                  0);
 }
 
 static void
@@ -227,7 +227,7 @@ calendar_sources_init (CalendarSources *sources)
 
 static void
 calendar_sources_finalize_source_data (CalendarSources    *sources,
-				       CalendarSourceData *source_data)
+                                       CalendarSourceData *source_data)
 {
   if (source_data->loaded)
     {
@@ -274,7 +274,7 @@ calendar_sources_get (void)
 
   calendar_sources_singleton = g_object_new (CALENDAR_TYPE_SOURCES, NULL);
   g_object_add_weak_pointer (G_OBJECT (calendar_sources_singleton),
-			     singleton_location);
+                             singleton_location);
 
   return calendar_sources_singleton;
 }
@@ -282,8 +282,8 @@ calendar_sources_get (void)
 /* The clients are just created here but not loaded */
 static void
 create_client_for_source (ESource              *source,
-		          ECalClientSourceType  source_type,
-		          CalendarSourceData   *source_data)
+                          ECalClientSourceType  source_type,
+                          CalendarSourceData   *source_data)
 {
   ClientData *data;
   EClient *client;
@@ -296,8 +296,8 @@ create_client_for_source (ESource              *source,
   if (!client)
     {
       g_warning ("Could not load source '%s': %s",
-		 e_source_get_uid (source),
-		 error->message);
+                 e_source_get_uid (source),
+                 error->message);
       g_clear_error(&error);
       return;
     }
@@ -325,8 +325,8 @@ debug_dump_ecal_list (GHashTable *clients)
       ESource *source = E_SOURCE (link->data);
 
       dprintf ("  %s %s\n",
-	       e_source_get_uid (source),
-	       e_source_get_display_name (source));
+               e_source_get_uid (source),
+               e_source_get_display_name (source));
     }
   g_list_free (list);
 #endif
@@ -365,7 +365,7 @@ backend_died_cb (EClient *client, CalendarSourceData *source_data)
   g_clear_handle_id (&source_data->timeout_id, g_source_remove);
 
   source_data->timeout_id = g_timeout_add_seconds (2, backend_restart,
-		  				   source_data);
+                                                   source_data);
   g_source_set_name_by_id (source_data->timeout_id, "[gnome-shell] backend_restart");
 }
 
diff --git a/src/calendar-server/gnome-shell-calendar-server.c b/src/calendar-server/gnome-shell-calendar-server.c
index 2da0f84ba..23398bb03 100644
--- a/src/calendar-server/gnome-shell-calendar-server.c
+++ b/src/calendar-server/gnome-shell-calendar-server.c
@@ -691,10 +691,10 @@ app_load_events (App *app)
 
       error = NULL;
       if (!e_cal_client_get_view_sync (cal,
-				       query,
-				       &view,
-				       NULL, /* cancellable */
-				       &error))
+                                       query,
+                                       &view,
+                                       NULL, /* cancellable */
+                                       &error))
         {
           g_warning ("Error setting up live-query on calendar: %s\n", error->message);
           g_error_free (error);
-- 
2.31.1

From 8f9da6f80190641d760bb48f08a9a9122e6a3766 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Wed, 22 Apr 2020 17:10:31 +0200
Subject: [PATCH 4/6] calendar-server: Add missing spaces

... according to coding style.

https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/1875
---
 src/calendar-server/calendar-sources.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/calendar-server/calendar-sources.c b/src/calendar-server/calendar-sources.c
index 145304a92..0737fbd93 100644
--- a/src/calendar-server/calendar-sources.c
+++ b/src/calendar-server/calendar-sources.c
@@ -188,7 +188,7 @@ calendar_sources_init (CalendarSources *sources)
          because of e-d-s problems. So just exit here.
       */
       g_warning ("Failed to start evolution-source-registry: %s", error->message);
-      exit(EXIT_FAILURE);
+      exit (EXIT_FAILURE);
     }
 
   g_object_unref (session_bus);
@@ -298,7 +298,7 @@ create_client_for_source (ESource              *source,
       g_warning ("Could not load source '%s': %s",
                  e_source_get_uid (source),
                  error->message);
-      g_clear_error(&error);
+      g_clear_error (&error);
       return;
     }
 
-- 
2.31.1

From 30d902f898004c8646c406b5755c1b82df2b5ec4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Wed, 22 Apr 2020 17:01:31 +0200
Subject: [PATCH 5/6] calendar-server: Drop separate private struct

CalendarSources is a final type, so the regular instance struct is
already non-public. No need for a separate private struct and priv
pointer ...

https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/1875
---
 src/calendar-server/calendar-sources.c | 126 ++++++++++++-------------
 1 file changed, 60 insertions(+), 66 deletions(-)

diff --git a/src/calendar-server/calendar-sources.c b/src/calendar-server/calendar-sources.c
index 0737fbd93..3281500ec 100644
--- a/src/calendar-server/calendar-sources.c
+++ b/src/calendar-server/calendar-sources.c
@@ -64,11 +64,7 @@ typedef struct _CalendarSourcesPrivate CalendarSourcesPrivate;
 struct _CalendarSources
 {
   GObject                 parent;
-  CalendarSourcesPrivate *priv;
-};
 
-struct _CalendarSourcesPrivate
-{
   ESourceRegistry    *registry;
   gulong              source_added_id;
   gulong              source_changed_id;
@@ -78,7 +74,7 @@ struct _CalendarSourcesPrivate
   CalendarSourceData  task_sources;
 };
 
-G_DEFINE_TYPE_WITH_PRIVATE (CalendarSources, calendar_sources, G_TYPE_OBJECT)
+G_DEFINE_TYPE (CalendarSources, calendar_sources, G_TYPE_OBJECT)
 
 static void calendar_sources_finalize   (GObject             *object);
 
@@ -148,8 +144,6 @@ calendar_sources_init (CalendarSources *sources)
   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
@@ -179,7 +173,7 @@ calendar_sources_init (CalendarSources *sources)
   if (result != NULL)
     {
       g_variant_unref (result);
-      sources->priv->registry = e_source_registry_new_sync (NULL, &error);
+      sources->registry = e_source_registry_new_sync (NULL, &error);
     }
 
   if (error != NULL)
@@ -193,36 +187,36 @@ calendar_sources_init (CalendarSources *sources)
 
   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;
+  sources->source_added_id = g_signal_connect (sources->registry,
+                                               "source-added",
+                                               G_CALLBACK (calendar_sources_registry_source_changed_cb),
+                                               sources);
+  sources->source_changed_id = g_signal_connect (sources->registry,
+                                                 "source-changed",
+                                                 G_CALLBACK (calendar_sources_registry_source_changed_cb),
+                                                 sources);
+  sources->source_removed_id = g_signal_connect (sources->registry,
+                                                 "source-removed",
+                                                 G_CALLBACK (calendar_sources_registry_source_removed_cb),
+                                                 sources);
+
+  sources->appointment_sources.source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS;
+  sources->appointment_sources.sources = sources;
+  sources->appointment_sources.changed_signal = signals [APPOINTMENT_SOURCES_CHANGED];
+  sources->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->appointment_sources.timeout_id = 0;
+
+  sources->task_sources.source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS;
+  sources->task_sources.sources = sources;
+  sources->task_sources.changed_signal = signals [TASK_SOURCES_CHANGED];
+  sources->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->task_sources.timeout_id = 0;
 }
 
 static void
@@ -245,20 +239,20 @@ calendar_sources_finalize (GObject *object)
 {
   CalendarSources *sources = CALENDAR_SOURCES (object);
 
-  if (sources->priv->registry)
+  if (sources->registry)
     {
-      g_clear_signal_handler (&sources->priv->source_added_id,
-                              sources->priv->registry);
-      g_clear_signal_handler (&sources->priv->source_changed_id,
-                              sources->priv->registry);
-      g_clear_signal_handler (&sources->priv->source_removed_id,
-                              sources->priv->registry);
-      g_object_unref (sources->priv->registry);
+      g_clear_signal_handler (&sources->source_added_id,
+                              sources->registry);
+      g_clear_signal_handler (&sources->source_changed_id,
+                              sources->registry);
+      g_clear_signal_handler (&sources->source_removed_id,
+                              sources->registry);
+      g_object_unref (sources->registry);
     }
-  sources->priv->registry = NULL;
+  sources->registry = NULL;
 
-  calendar_sources_finalize_source_data (sources, &sources->priv->appointment_sources);
-  calendar_sources_finalize_source_data (sources, &sources->priv->task_sources);
+  calendar_sources_finalize_source_data (sources, &sources->appointment_sources);
+  calendar_sources_finalize_source_data (sources, &sources->task_sources);
 
   if (G_OBJECT_CLASS (parent_class)->finalize)
     G_OBJECT_CLASS (parent_class)->finalize (object);
@@ -342,7 +336,7 @@ backend_restart (gpointer data)
   CalendarSourceData *source_data = data;
   ESourceRegistry *registry;
 
-  registry = source_data->sources->priv->registry;
+  registry = source_data->sources->registry;
   calendar_sources_load_esource_list (registry, source_data);
   g_signal_emit (source_data->sources, source_data->changed_signal, 0);
 
@@ -420,7 +414,7 @@ calendar_sources_registry_source_changed_cb (ESourceRegistry *registry,
       gboolean have_client;
       gboolean show_source;
 
-      source_data = &sources->priv->appointment_sources;
+      source_data = &sources->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);
@@ -444,7 +438,7 @@ calendar_sources_registry_source_changed_cb (ESourceRegistry *registry,
       gboolean have_client;
       gboolean show_source;
 
-      source_data = &sources->priv->task_sources;
+      source_data = &sources->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);
@@ -471,7 +465,7 @@ calendar_sources_registry_source_removed_cb (ESourceRegistry *registry,
     {
       CalendarSourceData *source_data;
 
-      source_data = &sources->priv->appointment_sources;
+      source_data = &sources->appointment_sources;
       g_hash_table_remove (source_data->clients, source);
       g_signal_emit (sources, source_data->changed_signal, 0);
     }
@@ -480,7 +474,7 @@ calendar_sources_registry_source_removed_cb (ESourceRegistry *registry,
     {
       CalendarSourceData *source_data;
 
-      source_data = &sources->priv->task_sources;
+      source_data = &sources->task_sources;
       g_hash_table_remove (source_data->clients, source);
       g_signal_emit (sources, source_data->changed_signal, 0);
     }
@@ -489,11 +483,11 @@ calendar_sources_registry_source_removed_cb (ESourceRegistry *registry,
 static void
 ensure_appointment_sources (CalendarSources *sources)
 {
-  if (!sources->priv->appointment_sources.loaded)
+  if (!sources->appointment_sources.loaded)
     {
-      calendar_sources_load_esource_list (sources->priv->registry,
-                                          &sources->priv->appointment_sources);
-      sources->priv->appointment_sources.loaded = TRUE;
+      calendar_sources_load_esource_list (sources->registry,
+                                          &sources->appointment_sources);
+      sources->appointment_sources.loaded = TRUE;
     }
 }
 
@@ -506,7 +500,7 @@ calendar_sources_get_appointment_clients (CalendarSources *sources)
 
   ensure_appointment_sources (sources);
 
-  list = g_hash_table_get_values (sources->priv->appointment_sources.clients);
+  list = g_hash_table_get_values (sources->appointment_sources.clients);
 
   for (link = list; link != NULL; link = g_list_next (link))
     link->data = ((ClientData *) link->data)->client;
@@ -517,11 +511,11 @@ calendar_sources_get_appointment_clients (CalendarSources *sources)
 static void
 ensure_task_sources (CalendarSources *sources)
 {
-  if (!sources->priv->task_sources.loaded)
+  if (!sources->task_sources.loaded)
     {
-      calendar_sources_load_esource_list (sources->priv->registry,
-                                          &sources->priv->task_sources);
-      sources->priv->task_sources.loaded = TRUE;
+      calendar_sources_load_esource_list (sources->registry,
+                                          &sources->task_sources);
+      sources->task_sources.loaded = TRUE;
     }
 }
 
@@ -534,7 +528,7 @@ calendar_sources_get_task_clients (CalendarSources *sources)
 
   ensure_task_sources (sources);
 
-  list = g_hash_table_get_values (sources->priv->task_sources.clients);
+  list = g_hash_table_get_values (sources->task_sources.clients);
 
   for (link = list; link != NULL; link = g_list_next (link))
     link->data = ((ClientData *) link->data)->client;
@@ -550,6 +544,6 @@ calendar_sources_has_sources (CalendarSources *sources)
   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;
+  return g_hash_table_size (sources->appointment_sources.clients) > 0 ||
+    g_hash_table_size (sources->task_sources.clients) > 0;
 }
-- 
2.31.1

From c00d79bae229d2c6edab14db678afb59501ad46c Mon Sep 17 00:00:00 2001
From: Milan Crha <mcrha@redhat.com>
Date: Thu, 2 Apr 2020 09:24:28 +0200
Subject: [PATCH 6/6] calendar-server: Improve performance by properly using
 ECalClientView

The previous code always restarted whole ECalClientView when it received
any changes in it, which could sometimes lead to constant repeated restarts
of the view.

https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/1875
---
 .../org.gnome.Shell.CalendarServer.xml        |   19 +-
 js/ui/calendar.js                             |  104 +-
 meson.build                                   |    2 +-
 src/calendar-server/calendar-sources.c        |  677 +++++------
 src/calendar-server/calendar-sources.h        |   29 +-
 .../gnome-shell-calendar-server.c             | 1026 ++++++++---------
 6 files changed, 921 insertions(+), 915 deletions(-)

diff --git a/data/dbus-interfaces/org.gnome.Shell.CalendarServer.xml b/data/dbus-interfaces/org.gnome.Shell.CalendarServer.xml
index c198830..51b71ef 100644
--- a/data/dbus-interfaces/org.gnome.Shell.CalendarServer.xml
+++ b/data/dbus-interfaces/org.gnome.Shell.CalendarServer.xml
@@ -1,12 +1,19 @@
 <node>
   <interface name="org.gnome.Shell.CalendarServer">
-    <method name="GetEvents">
-      <arg type="x" direction="in" />
-      <arg type="x" direction="in" />
-      <arg type="b" direction="in" />
-      <arg type="a(sssbxxa{sv})" direction="out" />
+    <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(ssbxxa{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="HasCalendars" type="b" access="read" />
-    <signal name="Changed" />
   </interface>
 </node>
diff --git a/js/ui/calendar.js b/js/ui/calendar.js
index 88e008e..7bbdaaf 100644
--- a/js/ui/calendar.js
+++ b/js/ui/calendar.js
@@ -183,7 +183,12 @@ var DBusEventSource = class DBusEventSource {
                 }
             }
 
-            this._dbusProxy.connectSignal('Changed', this._onChanged.bind(this));
+            this._dbusProxy.connectSignal('EventsAddedOrUpdated',
+                this._onEventsAddedOrUpdated.bind(this));
+            this._dbusProxy.connectSignal('EventsRemoved',
+                this._onEventsRemoved.bind(this));
+            this._dbusProxy.connectSignal('ClientDisappeared',
+                this._onClientDisappeared.bind(this));
 
             this._dbusProxy.connect('notify::g-name-owner', () => {
                 if (this._dbusProxy.g_name_owner)
@@ -216,7 +221,7 @@ var DBusEventSource = class DBusEventSource {
     }
 
     _resetCache() {
-        this._events = [];
+        this._events = new Map();
         this._lastRequestBegin = null;
         this._lastRequestEnd = null;
     }
@@ -232,28 +237,47 @@ var DBusEventSource = class DBusEventSource {
         this.emit('changed');
     }
 
-    _onChanged() {
-        this._loadEvents(false);
-    }
+    _onEventsAddedOrUpdated(dbusProxy, nameOwner, argArray) {
+        const [appointments = []] = argArray;
+        let changed = false;
 
-    _onEventsReceived(results, _error) {
-        let newEvents = [];
-        let appointments = results[0] || [];
         for (let n = 0; n < appointments.length; n++) {
-            let a = appointments[n];
-            let date = new Date(a[4] * 1000);
-            let end = new Date(a[5] * 1000);
-            let id = a[0];
-            let summary = a[1];
-            let allDay = a[3];
+            const [id, summary, allDay, startTime, endTime] = appointments[n];
+            const date = new Date(startTime * 1000);
+            const end = new Date(endTime * 1000);
             let event = new CalendarEvent(id, date, end, summary, allDay);
-            newEvents.push(event);
+            this._events.set(event.id, event);
+
+            changed = true;
         }
-        newEvents.sort((ev1, ev2) => ev1.date.getTime() - ev2.date.getTime());
 
-        this._events = newEvents;
-        this.isLoading = false;
-        this.emit('changed');
+        if (changed)
+            this.emit('changed');
+    }
+
+    _onEventsRemoved(dbusProxy, nameOwner, argArray) {
+        const [ids = []] = argArray;
+
+        let changed = false;
+        for (const id of ids)
+            changed |= this._events.delete(id);
+
+        if (changed)
+            this.emit('changed');
+    }
+
+    _onClientDisappeared(dbusProxy, nameOwner, argArray) {
+        let [sourceUid = ''] = argArray;
+        sourceUid += '\n';
+
+        let changed = false;
+        for (const id of this._events.keys()) {
+            if (id.startsWith(sourceUid))
+                changed |= this._events.delete(id);
+        }
+
+        if (changed)
+            this.emit('changed');
     }
 
     _loadEvents(forceReload) {
@@ -262,34 +286,38 @@ var DBusEventSource = class DBusEventSource {
             return;
 
         if (this._curRequestBegin && this._curRequestEnd) {
-            this._dbusProxy.GetEventsRemote(this._curRequestBegin.getTime() / 1000,
-                                            this._curRequestEnd.getTime() / 1000,
-                                            forceReload,
-                                            this._onEventsReceived.bind(this),
-                                            Gio.DBusCallFlags.NONE);
+            if (forceReload) {
+                this._events.clear();
+                this.emit('changed');
+            }
+            this._dbusProxy.SetTimeRangeRemote(
+                this._curRequestBegin.getTime() / 1000,
+                this._curRequestEnd.getTime() / 1000,
+                forceReload,
+                Gio.DBusCallFlags.NONE);
         }
     }
 
     requestRange(begin, end) {
         if (!(_datesEqual(begin, this._lastRequestBegin) && _datesEqual(end, this._lastRequestEnd))) {
-            this.isLoading = true;
             this._lastRequestBegin = begin;
             this._lastRequestEnd = end;
             this._curRequestBegin = begin;
             this._curRequestEnd = end;
-            this._loadEvents(false);
+            this._loadEvents(true);
+        }
+    }
+
+    *_getFilteredEvents(begin, end) {
+        for (const event of this._events.values()) {
+            if (_dateIntervalsOverlap(event.date, event.end, begin, end))
+                yield event;
         }
     }
 
     getEvents(begin, end) {
-        let result = [];
-        for (let n = 0; n < this._events.length; n++) {
-            let event = this._events[n];
+        let result = [...this._getFilteredEvents(begin, end)];
 
-            if (_dateIntervalsOverlap (event.date, event.end, begin, end)) {
-                result.push(event);
-            }
-        }
         result.sort((event1, event2) => {
             // sort events by end time on ending day
             let d1 = event1.date < begin && event1.end <= end ? event1.end : event1.date;
@@ -303,12 +331,8 @@ var DBusEventSource = class DBusEventSource {
         let dayBegin = _getBeginningOfDay(day);
         let dayEnd = _getEndOfDay(day);
 
-        let events = this.getEvents(dayBegin, dayEnd);
-
-        if (events.length == 0)
-            return false;
-
-        return true;
+        const { done } = this._getFilteredEvents(dayBegin, dayEnd).next();
+        return !done;
     }
 };
 Signals.addSignalMethods(DBusEventSource.prototype);
@@ -821,7 +845,7 @@ var EventsSection = class EventsSection extends MessageList.MessageListSection {
     }
 
     _reloadEvents() {
-        if (this._eventSource.isLoading)
+        if (this._eventSource.isLoading || this._reloading)
             return;
 
         this._reloading = true;
diff --git a/meson.build b/meson.build
index 8663b1d..712a3f5 100644
--- a/meson.build
+++ b/meson.build
@@ -20,7 +20,7 @@ libmutter_pc = 'libmutter-' + mutter_api_version
 
 croco_req = '>= 0.6.8'
 ecal_req = '>= 3.33.1'
-eds_req = '>= 3.17.2'
+eds_req = '>= 3.33.1'
 gcr_req = '>= 3.7.5'
 gio_req = '>= 2.56.0'
 gi_req = '>= 1.49.1'
diff --git a/src/calendar-server/calendar-sources.c b/src/calendar-server/calendar-sources.c
index 6c0510b..70ca382 100644
--- a/src/calendar-server/calendar-sources.c
+++ b/src/calendar-server/calendar-sources.c
@@ -53,137 +53,122 @@ struct _ClientData
   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;
 
-  ESourceRegistry    *registry;
-  gulong              source_added_id;
-  gulong              source_changed_id;
-  gulong              source_removed_id;
+  ESourceRegistryWatcher *registry_watcher;
+  gulong                  filter_id;
+  gulong                  appeared_id;
+  gulong                  disappeared_id;
 
-  CalendarSourceData  appointment_sources;
-  CalendarSourceData  task_sources;
+  GMutex                  clients_lock;
+  GHashTable             *clients; /* ESource -> ClientData */
 };
 
 G_DEFINE_TYPE (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,
+  CLIENT_APPEARED,
+  CLIENT_DISAPPEARED,
   LAST_SIGNAL
 };
 static guint signals [LAST_SIGNAL] = { 0, };
 
-static GObjectClass    *parent_class = NULL;
-static CalendarSources *calendar_sources_singleton = NULL;
+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
-client_data_free (ClientData *data)
+registry_watcher_source_appeared_cb (ESourceRegistryWatcher *watcher,
+                                     ESource *source,
+                                     CalendarSources *sources)
 {
-  g_clear_signal_handler (&data->backend_died_id, data->client);
-  g_object_unref (data->client);
-  g_slice_free (ClientData, data);
+  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
-calendar_sources_class_init (CalendarSourcesClass *klass)
+registry_watcher_source_disappeared_cb (ESourceRegistryWatcher *watcher,
+                                        ESource *source,
+                                        CalendarSources *sources)
 {
-  GObjectClass *gobject_class = (GObjectClass *) klass;
+  gboolean emit;
 
-  parent_class = g_type_class_peek_parent (klass);
+  g_mutex_lock (&sources->clients_lock);
 
-  gobject_class->finalize = calendar_sources_finalize;
+  emit = g_hash_table_remove (sources->clients, source);
 
-  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);
+  g_mutex_unlock (&sources->clients_lock);
 
-  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);
+  if (emit)
+    g_signal_emit (sources, signals[CLIENT_DISAPPEARED], 0, e_source_get_uid (source), NULL);
 }
 
 static void
-calendar_sources_init (CalendarSources *sources)
+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_constructed (GObject *object)
+{
+  CalendarSources *sources = CALENDAR_SOURCES (object);
+  ESourceRegistry *registry = NULL;
   GError *error = NULL;
-  GDBusConnection *session_bus;
-  GVariant *result;
-
-  /* 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->registry = e_source_registry_new_sync (NULL, &error);
-    }
+  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
@@ -193,82 +178,98 @@ calendar_sources_init (CalendarSources *sources)
       exit (EXIT_FAILURE);
     }
 
-  g_object_unref (session_bus);
-
-  sources->source_added_id = g_signal_connect (sources->registry,
-                                               "source-added",
-                                               G_CALLBACK (calendar_sources_registry_source_changed_cb),
-                                               sources);
-  sources->source_changed_id = g_signal_connect (sources->registry,
-                                                 "source-changed",
-                                                 G_CALLBACK (calendar_sources_registry_source_changed_cb),
-                                                 sources);
-  sources->source_removed_id = g_signal_connect (sources->registry,
-                                                 "source-removed",
-                                                 G_CALLBACK (calendar_sources_registry_source_removed_cb),
-                                                 sources);
-
-  sources->appointment_sources.source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS;
-  sources->appointment_sources.sources = sources;
-  sources->appointment_sources.changed_signal = signals [APPOINTMENT_SOURCES_CHANGED];
-  sources->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->appointment_sources.timeout_id = 0;
-
-  sources->task_sources.source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS;
-  sources->task_sources.sources = sources;
-  sources->task_sources.changed_signal = signals [TASK_SOURCES_CHANGED];
-  sources->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->task_sources.timeout_id = 0;
+  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_source_data (CalendarSources    *sources,
-                                       CalendarSourceData *source_data)
+calendar_sources_finalize (GObject *object)
 {
-  if (source_data->loaded)
-    {
-      g_hash_table_destroy (source_data->clients);
-      source_data->clients = NULL;
+  CalendarSources *sources = CALENDAR_SOURCES (object);
 
-      g_clear_handle_id (&source_data->timeout_id, g_source_remove);
+  g_clear_pointer (&sources->clients, g_hash_table_destroy);
 
-      source_data->loaded = FALSE;
+  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_finalize (GObject *object)
+calendar_sources_class_init (CalendarSourcesClass *klass)
 {
-  CalendarSources *sources = CALENDAR_SOURCES (object);
+  GObjectClass *gobject_class = (GObjectClass *) klass;
 
-  if (sources->registry)
-    {
-      g_clear_signal_handler (&sources->source_added_id,
-                              sources->registry);
-      g_clear_signal_handler (&sources->source_changed_id,
-                              sources->registry);
-      g_clear_signal_handler (&sources->source_removed_id,
-                              sources->registry);
-      g_object_unref (sources->registry);
-    }
-  sources->registry = NULL;
+  gobject_class->constructed = calendar_sources_constructed;
+  gobject_class->finalize = calendar_sources_finalize;
 
-  calendar_sources_finalize_source_data (sources, &sources->appointment_sources);
-  calendar_sources_finalize_source_data (sources, &sources->task_sources);
+  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);
 
-  if (G_OBJECT_CLASS (parent_class)->finalize)
-    G_OBJECT_CLASS (parent_class)->finalize (object);
+  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)
@@ -281,80 +282,65 @@ calendar_sources_get (void)
   return calendar_sources_singleton;
 }
 
-/* The clients are just created here but not loaded */
+ESourceRegistry *
+calendar_sources_get_registry (CalendarSources *sources)
+{
+  return e_source_registry_watcher_get_registry (sources->registry_watcher);
+}
+
 static void
-create_client_for_source (ESource              *source,
-                          ECalClientSourceType  source_type,
-                          CalendarSourceData   *source_data)
+gather_event_clients_cb (gpointer key,
+                         gpointer value,
+                         gpointer user_data)
 {
-  ClientData *data;
-  EClient *client;
-  GError *error = NULL;
+  GSList **plist = user_data;
+  ClientData *cd = value;
 
-  client = g_hash_table_lookup (source_data->clients, source);
-  g_return_if_fail (client == NULL);
+  if (cd)
+    *plist = g_slist_prepend (*plist, g_object_ref (cd->client));
+}
 
-  client = e_cal_client_connect_sync (source, source_type, -1, NULL, &error);
-  if (!client)
-    {
-      g_warning ("Could not load source '%s': %s",
-                 e_source_get_uid (source),
-                 error->message);
-      g_clear_error (&error);
-      return;
-    }
+GSList *
+calendar_sources_ref_clients (CalendarSources *sources)
+{
+  GSList *list = NULL;
+
+  g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL);
 
-  data = g_slice_new0 (ClientData);
-  data->client = E_CAL_CLIENT (client);  /* takes ownership */
-  data->backend_died_id = g_signal_connect (client,
-                                            "backend-died",
-                                            G_CALLBACK (backend_died_cb),
-                                            source_data);
+  g_mutex_lock (&sources->clients_lock);
+  g_hash_table_foreach (sources->clients, gather_event_clients_cb, &list);
+  g_mutex_unlock (&sources->clients_lock);
 
-  g_hash_table_insert (source_data->clients, g_object_ref (source), data);
+  return list;
 }
 
-static inline void
-debug_dump_ecal_list (GHashTable *clients)
+gboolean
+calendar_sources_has_clients (CalendarSources *sources)
 {
-#ifdef CALENDAR_ENABLE_DEBUG
-  GList *list, *link;
+  GHashTableIter iter;
+  gpointer value;
+  gboolean has = FALSE;
 
-  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);
+  g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), FALSE);
 
-      dprintf ("  %s %s\n",
-               e_source_get_uid (source),
-               e_source_get_display_name (source));
-    }
-  g_list_free (list);
-#endif
-}
+  g_mutex_lock (&sources->clients_lock);
 
-static void
-calendar_sources_load_esource_list (ESourceRegistry *registry,
-                                    CalendarSourceData *source_data);
+  g_hash_table_iter_init (&iter, sources->clients);
+  while (!has && g_hash_table_iter_next (&iter, NULL, &value))
+   {
+     ClientData *cd = value;
 
-static gboolean
-backend_restart (gpointer data)
-{
-  CalendarSourceData *source_data = data;
-  ESourceRegistry *registry;
+     has = cd != NULL;
+   }
 
-  registry = source_data->sources->registry;
-  calendar_sources_load_esource_list (registry, source_data);
-  g_signal_emit (source_data->sources, source_data->changed_signal, 0);
+  g_mutex_unlock (&sources->clients_lock);
 
-  source_data->timeout_id = 0;
-    
-  return FALSE;
+  return has;
 }
 
 static void
-backend_died_cb (EClient *client, CalendarSourceData *source_data)
+backend_died_cb (EClient *client,
+                 CalendarSources *sources)
 {
   ESource *source;
   const char *display_name;
@@ -362,196 +348,167 @@ backend_died_cb (EClient *client, CalendarSourceData *source_data)
   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);
-
-  g_clear_handle_id (&source_data->timeout_id, g_source_remove);
-
-  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");
+  g_mutex_lock (&sources->clients_lock);
+  g_hash_table_remove (sources->clients, source);
+  g_mutex_unlock (&sources->clients_lock);
 }
 
-static void
-calendar_sources_load_esource_list (ESourceRegistry *registry,
-                                    CalendarSourceData *source_data)
+static EClient *
+calendar_sources_connect_client_sync (CalendarSources *sources,
+                                      ESource *source,
+                                      ECalClientSourceType source_type,
+                                      guint32 wait_for_connected_seconds,
+                                      GCancellable *cancellable,
+                                      GError **error)
 {
-  GList   *list, *link;
-  const gchar *extension_name;
+  EClient *client = NULL;
+  ClientData *client_data;
 
-  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 ();
-    }
+  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);
 
-  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;
+  if (client)
+    return client;
 
-      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);
-}
+  client = e_cal_client_connect_sync (source, source_type, wait_for_connected_seconds, cancellable, error);
+  if (!client)
+    return NULL;
 
-static void
-calendar_sources_registry_source_changed_cb (ESourceRegistry *registry,
-                                             ESource         *source,
-                                             CalendarSources *sources)
-{
-  if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR))
+  g_mutex_lock (&sources->clients_lock);
+  client_data = g_hash_table_lookup (sources->clients, source);
+  if (client_data)
     {
-      CalendarSourceData *source_data;
-      ESourceSelectable *extension;
-      gboolean have_client;
-      gboolean show_source;
-
-      source_data = &sources->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);
-        }
+      g_clear_object (&client);
+      client = E_CLIENT (g_object_ref (client_data->client));
     }
-
-  if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST))
+   else
     {
-      CalendarSourceData *source_data;
-      ESourceSelectable *extension;
-      gboolean have_client;
-      gboolean show_source;
-
-      source_data = &sources->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);
-        }
+      client_data = g_slice_new0 (ClientData);
+      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
-calendar_sources_registry_source_removed_cb (ESourceRegistry *registry,
-                                             ESource         *source,
-                                             CalendarSources *sources)
+async_context_free (gpointer ptr)
 {
-  if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR))
-    {
-      CalendarSourceData *source_data;
+  AsyncContext *ctx = ptr;
 
-      source_data = &sources->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))
+  if (ctx)
     {
-      CalendarSourceData *source_data;
-
-      source_data = &sources->task_sources;
-      g_hash_table_remove (source_data->clients, source);
-      g_signal_emit (sources, source_data->changed_signal, 0);
+      g_clear_object (&ctx->source);
+      g_slice_free (AsyncContext, ctx);
     }
 }
 
 static void
-ensure_appointment_sources (CalendarSources *sources)
+calendar_sources_connect_client_thread (GTask *task,
+                                        gpointer source_object,
+                                        gpointer task_data,
+                                        GCancellable *cancellable)
 {
-  if (!sources->appointment_sources.loaded)
+  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)
     {
-      calendar_sources_load_esource_list (sources->registry,
-                                          &sources->appointment_sources);
-      sources->appointment_sources.loaded = TRUE;
+      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);
     }
 }
 
-GList *
-calendar_sources_get_appointment_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)
 {
-  GList *list, *link;
+  AsyncContext *ctx;
+  g_autoptr (GTask) task = NULL;
 
-  g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL);
+  ctx = g_slice_new0 (AsyncContext);
+  ctx->source = g_object_ref (source);
+  ctx->source_type = source_type;
+  ctx->wait_for_connected_seconds = wait_for_connected_seconds;
 
-  ensure_appointment_sources (sources);
+  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);
 
-  list = g_hash_table_get_values (sources->appointment_sources.clients);
+  g_task_run_in_thread (task, calendar_sources_connect_client_thread);
+}
 
-  for (link = list; link != NULL; link = g_list_next (link))
-    link->data = ((ClientData *) link->data)->client;
+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 list;
+  return g_task_propagate_pointer (G_TASK (result), error);
 }
 
-static void
-ensure_task_sources (CalendarSources *sources)
+
+void
+print_debug (const gchar *format,
+             ...)
 {
-  if (!sources->task_sources.loaded)
+  g_autofree char *s = NULL;
+  g_autofree char *timestamp = NULL;
+  va_list ap;
+  g_autoptr (GDateTime) now = NULL;
+  static volatile gsize once_init_value = 0;
+  static gboolean show_debug = FALSE;
+  static guint pid = 0;
+
+  if (g_once_init_enter (&once_init_value))
     {
-      calendar_sources_load_esource_list (sources->registry,
-                                          &sources->task_sources);
-      sources->task_sources.loaded = TRUE;
+      show_debug = (g_getenv ("CALENDAR_SERVER_DEBUG") != NULL);
+      pid = getpid ();
+      g_once_init_leave (&once_init_value, 1);
     }
-}
-
-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->task_sources.clients);
-
-  for (link = list; link != NULL; link = g_list_next (link))
-    link->data = ((ClientData *) link->data)->client;
-
-  return list;
-}
+  if (!show_debug)
+    goto out;
 
-gboolean
-calendar_sources_has_sources (CalendarSources *sources)
-{
-  g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), FALSE);
+  now = g_date_time_new_now_local ();
+  timestamp = g_date_time_format (now, "%H:%M:%S");
 
-  ensure_appointment_sources (sources);
-  ensure_task_sources (sources);
+  va_start (ap, format);
+  s = g_strdup_vprintf (format, ap);
+  va_end (ap);
 
-  return g_hash_table_size (sources->appointment_sources.clients) > 0 ||
-    g_hash_table_size (sources->task_sources.clients) > 0;
+  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
index d11850f..1ffc8ad 100644
--- a/src/calendar-server/calendar-sources.h
+++ b/src/calendar-server/calendar-sources.h
@@ -26,17 +26,38 @@
 
 #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);
-GList           *calendar_sources_get_appointment_clients (CalendarSources *sources);
-GList           *calendar_sources_get_task_clients        (CalendarSources *sources);
+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);
 
-gboolean         calendar_sources_has_sources             (CalendarSources *sources);
+/* Set the environment variable CALENDAR_SERVER_DEBUG to show debug */
+void            print_debug                          (const gchar *str,
+                                                      ...) G_GNUC_PRINTF (1, 2);
 
 G_END_DECLS
 
diff --git a/src/calendar-server/gnome-shell-calendar-server.c b/src/calendar-server/gnome-shell-calendar-server.c
index 23398bb..8ee8d3f 100644
--- a/src/calendar-server/gnome-shell-calendar-server.c
+++ b/src/calendar-server/gnome-shell-calendar-server.c
@@ -41,21 +41,25 @@ G_GNUC_END_IGNORE_DEPRECATIONS
 
 #include "calendar-sources.h"
 
-/* Set the environment variable CALENDAR_SERVER_DEBUG to show debug */
-static void print_debug (const gchar *str, ...);
-
 #define BUS_NAME "org.gnome.Shell.CalendarServer"
 
 static const gchar introspection_xml[] =
   "<node>"
   "  <interface name='org.gnome.Shell.CalendarServer'>"
-  "    <method name='GetEvents'>"
+  "    <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'/>"
-  "      <arg type='a(sssbxxa{sv})' name='events' direction='out'/>"
   "    </method>"
-  "    <signal name='Changed'/>"
+  "    <signal name='EventsAddedOrUpdated'>"
+  "      <arg type='a(ssbxxa{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'/>"
@@ -75,35 +79,40 @@ 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
 {
-  char *rid;
-  time_t start_time;
-  time_t end_time;
-} CalendarOccurrence;
+  ECalClient *client;
+  GSList **pappointments; /* CalendarAppointment * */
+} CollectAppointmentsData;
 
 typedef struct
 {
-  char   *uid;
-  char   *source_id;
-  char   *backend_name;
-  char   *summary;
-  char   *description;
-  char   *color_string;
+  gchar  *id;
+  gchar  *summary;
   time_t  start_time;
   time_t  end_time;
   guint   is_all_day : 1;
-
-  /* Only used internally */
-  GSList *occurrences;
 } CalendarAppointment;
 
-typedef struct
-{
-  ECalClient *client;
-  GHashTable *appointments;
-} CollectAppointmentsData;
-
 static time_t
 get_time_from_property (ECalClient            *cal,
                         ICalComponent         *icomp,
@@ -142,46 +151,6 @@ get_time_from_property (ECalClient            *cal,
   return retval;
 }
 
-static char *
-get_ical_uid (ICalComponent *icomp)
-{
-  return g_strdup (i_cal_component_get_uid (icomp));
-}
-
-static char *
-get_ical_summary (ICalComponent *icomp)
-{
-  ICalProperty *prop;
-  char         *retval;
-
-  prop = i_cal_component_get_first_property (icomp, I_CAL_SUMMARY_PROPERTY);
-  if (!prop)
-    return NULL;
-
-  retval = g_strdup (i_cal_property_get_summary (prop));
-
-  g_object_unref (prop);
-
-  return retval;
-}
-
-static char *
-get_ical_description (ICalComponent *icomp)
-{
-  ICalProperty *prop;
-  char         *retval;
-
-  prop = i_cal_component_get_first_property (icomp, I_CAL_DESCRIPTION_PROPERTY);
-  if (!prop)
-    return NULL;
-
-  retval = g_strdup (i_cal_property_get_description (prop));
-
-  g_object_unref (prop);
-
-  return retval;
-}
-
 static inline time_t
 get_ical_start_time (ECalClient    *cal,
                      ICalComponent *icomp,
@@ -275,172 +244,48 @@ get_ical_completed_time (ECalClient    *cal,
                                  default_zone);
 }
 
-static char *
-get_source_color (ECalClient *esource)
-{
-  ESource *source;
-  ECalClientSourceType source_type;
-  ESourceSelectable *extension;
-  const gchar *extension_name;
-
-  g_return_val_if_fail (E_IS_CAL_CLIENT (esource), NULL);
-
-  source = e_client_get_source (E_CLIENT (esource));
-  source_type = e_cal_client_get_source_type (esource);
-
-  switch (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_val_if_reached (NULL);
-    }
-
-  extension = e_source_get_extension (source, extension_name);
-
-  return e_source_selectable_dup_color (extension);
-}
-
-static gchar *
-get_source_backend_name (ECalClient *esource)
+static CalendarAppointment *
+calendar_appointment_new (ECalClient    *cal,
+                          ECalComponent *comp)
 {
-  ESource *source;
-  ECalClientSourceType source_type;
-  ESourceBackend *extension;
-  const gchar *extension_name;
-
-  g_return_val_if_fail (E_IS_CAL_CLIENT (esource), NULL);
+  CalendarAppointment *appt;
+  ICalTimezone *default_zone;
+  ICalComponent *ical;
+  ECalComponentId *id;
 
-  source = e_client_get_source (E_CLIENT (esource));
-  source_type = e_cal_client_get_source_type (esource);
+  default_zone = e_cal_client_get_default_timezone (cal);
+  ical = e_cal_component_get_icalcomponent (comp);
+  id = e_cal_component_get_id (comp);
 
-  switch (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_val_if_reached (NULL);
-    }
+  appt = g_new0 (CalendarAppointment, 1);
 
-  extension = e_source_get_extension (source, extension_name);
+  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);
+  appt->is_all_day  = get_ical_is_all_day (cal,
+                                           ical,
+                                           appt->start_time,
+                                           default_zone);
 
-  return e_source_backend_dup_backend_name (extension);
-}
+  e_cal_component_id_free (id);
 
-static inline int
-null_safe_strcmp (const char *a,
-                  const char *b)
-{
-  return (!a && !b) ? 0 : (a && !b) || (!a && b) ? 1 : strcmp (a, b);
+  return appt;
 }
 
-static inline gboolean
-calendar_appointment_equal (CalendarAppointment *a,
-                            CalendarAppointment *b)
+static void
+calendar_appointment_free (gpointer ptr)
 {
-  GSList *la, *lb;
-
-  if (g_slist_length (a->occurrences) != g_slist_length (b->occurrences))
-      return FALSE;
+  CalendarAppointment *appt = ptr;
 
-  for (la = a->occurrences, lb = b->occurrences; la && lb; la = la->next, lb = lb->next)
+  if (appt)
     {
-      CalendarOccurrence *oa = la->data;
-      CalendarOccurrence *ob = lb->data;
-
-      if (oa->start_time != ob->start_time ||
-          oa->end_time   != ob->end_time ||
-          null_safe_strcmp (oa->rid, ob->rid) != 0)
-        return FALSE;
+      g_free (appt->id);
+      g_free (appt->summary);
+      g_free (appt);
     }
-
-  return
-    null_safe_strcmp (a->uid,          b->uid)          == 0 &&
-    null_safe_strcmp (a->source_id,    b->source_id)    == 0 &&
-    null_safe_strcmp (a->backend_name, b->backend_name) == 0 &&
-    null_safe_strcmp (a->summary,      b->summary)      == 0 &&
-    null_safe_strcmp (a->description,  b->description)  == 0 &&
-    null_safe_strcmp (a->color_string, b->color_string) == 0 &&
-    a->start_time == b->start_time                         &&
-    a->end_time   == b->end_time                           &&
-    a->is_all_day == b->is_all_day;
-}
-
-static void
-calendar_appointment_free (CalendarAppointment *appointment)
-{
-  GSList *l;
-
-  for (l = appointment->occurrences; l; l = l->next)
-    g_free (((CalendarOccurrence *)l->data)->rid);
-  g_slist_free_full (appointment->occurrences, g_free);
-  appointment->occurrences = NULL;
-
-  g_free (appointment->uid);
-  appointment->uid = NULL;
-
-  g_free (appointment->source_id);
-  appointment->source_id = NULL;
-
-  g_free (appointment->backend_name);
-  appointment->backend_name = NULL;
-
-  g_free (appointment->summary);
-  appointment->summary = NULL;
-
-  g_free (appointment->description);
-  appointment->description = NULL;
-
-  g_free (appointment->color_string);
-  appointment->color_string = NULL;
-
-  appointment->start_time = 0;
-  appointment->is_all_day = FALSE;
-}
-
-static void
-calendar_appointment_init (CalendarAppointment  *appointment,
-                           ICalComponent        *icomp,
-                           ECalClient           *cal)
-{
-  ICalTimezone *default_zone;
-  const char *source_id;
-
-  source_id = e_source_get_uid (e_client_get_source (E_CLIENT (cal)));
-  default_zone = e_cal_client_get_default_timezone (cal);
-
-  appointment->uid          = get_ical_uid (icomp);
-  appointment->source_id    = g_strdup (source_id);
-  appointment->backend_name = get_source_backend_name (cal);
-  appointment->summary      = get_ical_summary (icomp);
-  appointment->description  = get_ical_description (icomp);
-  appointment->color_string = get_source_color (cal);
-  appointment->start_time   = get_ical_start_time (cal, icomp, default_zone);
-  appointment->end_time     = get_ical_end_time (cal, icomp, default_zone);
-  appointment->is_all_day   = get_ical_is_all_day (cal,
-                                                   icomp,
-                                                   appointment->start_time,
-                                                   default_zone);
-}
-
-static CalendarAppointment *
-calendar_appointment_new (ICalComponent        *icomp,
-                          ECalClient           *cal)
-{
-  CalendarAppointment *appointment;
-
-  appointment = g_new0 (CalendarAppointment, 1);
-
-  calendar_appointment_init (appointment, icomp, cal);
-  return appointment;
 }
 
 static time_t
@@ -463,34 +308,25 @@ generate_instances_cb (ICalComponent *icomp,
                        GCancellable *cancellable,
                        GError **error)
 {
-  ECalClient *cal = ((CollectAppointmentsData *)user_data)->client;
-  GHashTable *appointments = ((CollectAppointmentsData *)user_data)->appointments;
+  CollectAppointmentsData *data = user_data;
   CalendarAppointment *appointment;
-  CalendarOccurrence *occurrence;
+  ECalComponent *comp;
   ICalTimezone *default_zone;
-  const gchar *uid;
 
-  default_zone = e_cal_client_get_default_timezone (cal);
-  uid = i_cal_component_get_uid (icomp);
-  appointment = g_hash_table_lookup (appointments, uid);
+  default_zone = e_cal_client_get_default_timezone (data->client);
+  comp = e_cal_component_new_from_icalcomponent (i_cal_component_clone (icomp));
 
-  if (appointment == NULL)
-    {
-      appointment = calendar_appointment_new (icomp, cal);
-      g_hash_table_insert (appointments, g_strdup (uid), appointment);
-    }
+  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);
 
-  occurrence             = g_new0 (CalendarOccurrence, 1);
-  occurrence->start_time = timet_from_ical_time (instance_start, default_zone);
-  occurrence->end_time   = timet_from_ical_time (instance_end, default_zone);
-  occurrence->rid        = e_cal_util_component_get_recurid_as_string (icomp);
+  *(data->pappointments) = g_slist_prepend (*(data->pappointments), appointment);
 
-  appointment->occurrences = g_slist_append (appointment->occurrences, occurrence);
+  g_clear_object (&comp);
 
   return TRUE;
 }
 
-
 /* ---------------------------------------------------------------------------------------------------- */
 
 struct _App
@@ -503,24 +339,23 @@ struct _App
   ICalTimezone *zone;
 
   CalendarSources *sources;
-  gulong sources_signal_id;
-
-  /* hash from uid to CalendarAppointment objects */
-  GHashTable *appointments;
+  gulong client_appeared_signal_id;
+  gulong client_disappeared_signal_id;
 
   gchar *timezone_location;
 
-  guint changed_timeout_id;
-
-  gboolean cache_invalid;
+  GSList *notify_appointments; /* CalendarAppointment *, for EventsAdded */
+  GSList *notify_ids; /* gchar *, for EventsRemoved */
+  guint events_added_timeout_id;
+  guint events_removed_timeout_id;
 
-  GList *live_views;
+  GSList *live_views;
 };
 
 static void
 app_update_timezone (App *app)
 {
-  gchar *location;
+  g_autofree char *location = NULL;
 
   location = e_cal_system_timezone_get_location ();
   if (g_strcmp0 (location, app->timezone_location) != 0)
@@ -530,74 +365,203 @@ app_update_timezone (App *app)
       else
         app->zone = i_cal_timezone_get_builtin_timezone (location);
       g_free (app->timezone_location);
-      app->timezone_location = location;
+      app->timezone_location = g_steal_pointer (&location);
       print_debug ("Using timezone %s", app->timezone_location);
     }
-  else
-    {
-      g_free (location);
-    }
 }
 
 static gboolean
-on_app_schedule_changed_cb (gpointer user_data)
+on_app_schedule_events_added_cb (gpointer user_data)
 {
   App *app = user_data;
-  print_debug ("Emitting changed");
+  GVariantBuilder builder, extras_builder;
+  GSList *events, *link;
+
+  if (g_source_is_destroyed (g_main_current_source ()))
+    return FALSE;
+
+  events = g_slist_reverse (app->notify_appointments);
+  app->notify_appointments = NULL;
+  app->events_added_timeout_id = 0;
+
+  print_debug ("Emitting EventsAddedOrUpdated with %d events", g_slist_length (events));
+
+  if (!events)
+    return FALSE;
+
+  /* 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(ssbxxa{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,
+                                 "(ssbxxa{sv})",
+                                 appt->id,
+                                 appt->summary != NULL ? appt->summary : "",
+                                 (gboolean) appt->is_all_day,
+                                 (gint64) start_time,
+                                 (gint64) end_time,
+                                 extras_builder);
+          g_variant_builder_clear (&extras_builder);
+        }
+    }
+
   g_dbus_connection_emit_signal (app->connection,
                                  NULL, /* destination_bus_name */
                                  "/org/gnome/Shell/CalendarServer",
                                  "org.gnome.Shell.CalendarServer",
-                                 "Changed",
-                                 NULL, /* no params */
+                                 "EventsAddedOrUpdated",
+                                 g_variant_new ("(a(ssbxxa{sv}))", &builder),
                                  NULL);
-  app->changed_timeout_id = 0;
+
+  g_variant_builder_clear (&builder);
+
+  g_slist_free_full (events, calendar_appointment_free);
+
   return FALSE;
 }
 
 static void
-app_schedule_changed (App *app)
+app_schedule_events_added (App *app)
 {
-  print_debug ("Scheduling changed");
-  if (app->changed_timeout_id == 0)
+  print_debug ("Scheduling EventsAddedOrUpdated");
+  if (app->events_added_timeout_id == 0)
     {
-      app->changed_timeout_id = g_timeout_add (2000,
-                                               on_app_schedule_changed_cb,
-                                               app);
-      g_source_set_name_by_id (app->changed_timeout_id, "[gnome-shell] on_app_schedule_changed_cb");
+      app->events_added_timeout_id = g_timeout_add_seconds (2,
+                                                            on_app_schedule_events_added_cb,
+                                                            app);
+      g_source_set_name_by_id (app->events_added_timeout_id, "[gnome-shell] on_app_schedule_events_added_cb");
     }
 }
 
+static gboolean
+on_app_schedule_events_removed_cb (gpointer user_data)
+{
+  App *app = user_data;
+  GVariantBuilder builder;
+  GSList *ids, *link;
+
+  if (g_source_is_destroyed (g_main_current_source ()))
+    return FALSE;
+
+  ids = app->notify_ids;
+  app->notify_ids = NULL;
+  app->events_removed_timeout_id = 0;
+
+  print_debug ("Emitting EventsRemoved with %d ids", g_slist_length (ids));
+
+  if (!ids)
+    return FALSE;
+
+  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 FALSE;
+}
+
 static void
-invalidate_cache (App *app)
+app_schedule_events_removed (App *app)
 {
-  app->cache_invalid = TRUE;
+  print_debug ("Scheduling EventsRemoved");
+  if (app->events_removed_timeout_id == 0)
+    {
+      app->events_removed_timeout_id = g_timeout_add_seconds (2,
+                                                              on_app_schedule_events_removed_cb,
+                                                              app);
+      g_source_set_name_by_id (app->events_removed_timeout_id, "[gnome-shell] on_app_schedule_events_removed_cb");
+    }
 }
 
 static void
-on_objects_added (ECalClientView *view,
-                  GSList         *objects,
-                  gpointer        user_data)
+app_process_added_modified_objects (App *app,
+                                    ECalClientView *view,
+                                    GSList *objects) /* ICalComponent * */
 {
-  App *app = user_data;
-  GSList *l;
+  ECalClient *cal_client;
+  GSList *link;
+  gboolean expand_recurrences;
 
-  print_debug ("%s for calendar", G_STRFUNC);
+  cal_client = e_cal_client_view_ref_client (view);
+  expand_recurrences = e_cal_client_get_source_type (cal_client) == E_CAL_CLIENT_SOURCE_TYPE_EVENTS;
 
-  for (l = objects; l != NULL; l = l->next)
+  for (link = objects; link; link = g_slist_next (link))
     {
-      ICalComponent *icomp = l->data;
-      const char *uid;
+      ECalComponent *comp;
+      ICalComponent *icomp = link->data;
 
-      uid = i_cal_component_get_uid (icomp);
+      if (!icomp || !i_cal_component_get_uid (icomp))
+        continue;
 
-      if (g_hash_table_lookup (app->appointments, uid) == NULL)
+      if (expand_recurrences &&
+          !e_cal_util_component_is_instance (icomp) &&
+          e_cal_util_component_has_recurrences (icomp))
         {
-          /* new appointment we don't know about => changed signal */
-          invalidate_cache (app);
-          app_schedule_changed (app);
+          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
+        {
+          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_schedule_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
@@ -606,9 +570,13 @@ on_objects_modified (ECalClientView *view,
                      gpointer        user_data)
 {
   App *app = user_data;
-  print_debug ("%s for calendar", G_STRFUNC);
-  invalidate_cache (app);
-  app_schedule_changed (app);
+  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
@@ -617,42 +585,60 @@ on_objects_removed (ECalClientView *view,
                     gpointer        user_data)
 {
   App *app = user_data;
-  print_debug ("%s for calendar", G_STRFUNC);
-  invalidate_cache (app);
-  app_schedule_changed (app);
-}
+  ECalClient *client;
+  GSList *link;
+  const gchar *source_uid;
 
-static void
-app_load_events (App *app)
-{
-  GList *clients;
-  GList *l;
-  GList *ll;
-  gchar *since_iso8601;
-  gchar *until_iso8601;
-  gchar *query;
-  const char *tz_location;
-
-  /* out with the old */
-  g_hash_table_remove_all (app->appointments);
-  /* nuke existing views */
-  for (ll = app->live_views; ll != NULL; ll = ll->next)
+  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))
     {
-      ECalClientView *view = E_CAL_CLIENT_VIEW (ll->data);
-      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);
-      e_cal_client_view_stop (view, NULL);
-      g_object_unref (view);
+      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_list_free (app->live_views);
-  app->live_views = NULL;
+
+  g_clear_object (&client);
+
+  if (app->notify_ids)
+    app_schedule_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;
+      return NULL;
     }
+
   /* timezone could have changed */
   app_update_timezone (app);
 
@@ -660,9 +646,10 @@ app_load_events (App *app)
   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",
+  print_debug ("Loading events since %s until %s for calendar '%s'",
                since_iso8601,
-               until_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\"",
@@ -670,94 +657,186 @@ app_load_events (App *app)
                            until_iso8601,
                            tz_location);
 
-  clients = calendar_sources_get_appointment_clients (app->sources);
-  for (l = clients; l != NULL; l = l->next)
+  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_update_views (App *app)
+{
+  GSList *link, *clients;
+
+  for (link = app->live_views; link; link = g_slist_next (link))
     {
-      ECalClient *cal = E_CAL_CLIENT (l->data);
-      GError *error;
+      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;
-      CollectAppointmentsData data;
-
-      e_cal_client_set_default_timezone (cal, app->zone);
-
-      data.client = cal;
-      data.appointments = app->appointments;
-      e_cal_client_generate_instances_sync (cal,
-                                            app->since,
-                                            app->until,
-                                            NULL,
-                                            generate_instances_cb,
-                                            &data);
-
-      error = NULL;
-      if (!e_cal_client_get_view_sync (cal,
-                                       query,
-                                       &view,
-                                       NULL, /* cancellable */
-                                       &error))
-        {
-          g_warning ("Error setting up live-query on calendar: %s\n", error->message);
-          g_error_free (error);
-        }
-      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);
-          app->live_views = g_list_prepend (app->live_views, view);
-        }
+
+      if (!cal_client)
+        continue;
+
+      view = app_start_view (app, cal_client);
+      if (view)
+        app->live_views = g_slist_prepend (app->live_views, view);
     }
-  g_list_free (clients);
-  g_free (since_iso8601);
-  g_free (until_iso8601);
-  g_free (query);
-  app->cache_invalid = FALSE;
+
+  g_slist_free_full (clients, g_object_unref);
 }
 
-static gboolean
-app_has_calendars (App *app)
+static void
+app_notify_has_calendars (App *app)
 {
-  return calendar_sources_has_sources (app->sources);
+  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 (g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL),
+                                 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
-on_appointment_sources_changed (CalendarSources *sources,
-                                gpointer         user_data)
+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);
 
-  print_debug ("Sources changed\n");
-  app_load_events (app);
-
-  /* Notify the HasCalendars property */
-  {
-    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 (g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL),
-                                   NULL,
-                                   "/org/gnome/Shell/CalendarServer",
-                                   "org.freedesktop.DBus.Properties",
-                                   "PropertiesChanged",
-                                   g_variant_new ("(sa{sv}as)",
-                                                  "org.gnome.Shell.CalendarServer",
-                                                  &dict_builder,
-                                                  NULL),
-                                   NULL);
-  }
+      /* 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 *
@@ -768,15 +847,14 @@ app_new (GDBusConnection *connection)
   app = g_new0 (App, 1);
   app->connection = g_object_ref (connection);
   app->sources = calendar_sources_get ();
-  app->sources_signal_id = g_signal_connect (app->sources,
-                                             "appointment-sources-changed",
-                                             G_CALLBACK (on_appointment_sources_changed),
-                                             app);
-
-  app->appointments = g_hash_table_new_full (g_str_hash,
-                                             g_str_equal,
-                                             g_free,
-                                             (GDestroyNotify) calendar_appointment_free);
+  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);
 
@@ -786,28 +864,32 @@ app_new (GDBusConnection *connection)
 static void
 app_free (App *app)
 {
-  GList *ll;
-  for (ll = app->live_views; ll != NULL; ll = ll->next)
+  GSList *ll;
+
+  g_clear_handle_id (&app->events_added_timeout_id, g_source_remove);
+  g_clear_handle_id (&app->events_removed_timeout_id, g_source_remove);
+
+  for (ll = app->live_views; ll != NULL; ll = g_slist_next (ll))
     {
       ECalClientView *view = E_CAL_CLIENT_VIEW (ll->data);
-      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);
-      e_cal_client_view_stop (view, NULL);
-      g_object_unref (view);
+
+      app_stop_view (app, view);
     }
-  g_list_free (app->live_views);
+
+  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_hash_table_unref (app->appointments);
+  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_clear_signal_handler (&app->sources_signal_id, app->sources);
   g_object_unref (app->sources);
 
-  g_clear_handle_id (&app->changed_timeout_id, g_source_remove);
-
   g_free (app);
 }
 
@@ -825,15 +907,12 @@ handle_method_call (GDBusConnection       *connection,
 {
   App *app = user_data;
 
-  if (g_strcmp0 (method_name, "GetEvents") == 0)
+  if (g_strcmp0 (method_name, "SetTimeRange") == 0)
     {
-      GVariantBuilder builder;
-      GHashTableIter hash_iter;
-      CalendarAppointment *a;
       gint64 since;
       gint64 until;
-      gboolean force_reload;
-      gboolean window_changed;
+      gboolean force_reload = FALSE;
+      gboolean window_changed = FALSE;
 
       g_variant_get (parameters,
                      "(xxb)",
@@ -849,13 +928,12 @@ handle_method_call (GDBusConnection       *connection,
           goto out;
         }
 
-      print_debug ("Handling GetEvents (since=%" G_GINT64_FORMAT ", until=%" G_GINT64_FORMAT ", force_reload=%s)",
+      print_debug ("Handling SetTimeRange (since=%" G_GINT64_FORMAT ", until=%" G_GINT64_FORMAT ", force_reload=%s)",
                    since,
                    until,
                    force_reload ? "true" : "false");
 
-      window_changed = FALSE;
-      if (!(app->until == until && app->since == since))
+      if (app->until != until || app->since != since)
         {
           GVariantBuilder *builder;
           GVariantBuilder *invalidated_builder;
@@ -880,61 +958,15 @@ handle_method_call (GDBusConnection       *connection,
                                                         builder,
                                                         invalidated_builder),
                                          NULL); /* GError** */
-        }
 
-      /* reload events if necessary */
-      if (window_changed || force_reload || app->cache_invalid)
-        {
-          app_load_events (app);
+          g_variant_builder_unref (builder);
+          g_variant_builder_unref (invalidated_builder);
         }
 
-      /* 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(sssbxxa{sv})"));
-      g_hash_table_iter_init (&hash_iter, app->appointments);
-      while (g_hash_table_iter_next (&hash_iter, NULL, (gpointer) &a))
-        {
-          GVariantBuilder extras_builder;
-          GSList *l;
-
-          for (l = a->occurrences; l; l = l->next)
-            {
-              CalendarOccurrence *o = l->data;
-              time_t start_time = o->start_time;
-              time_t end_time   = o->end_time;
-
-              if ((start_time >= app->since &&
-                   start_time < app->until) ||
-                  (start_time <= app->since &&
-                  (end_time - 1) > app->since))
-                {
-                  /* 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
-                   */
-                  char *id = g_strdup_printf ("%s\n%s\n%s",
-                                              a->source_id,
-                                              a->uid,
-                                              o->rid ? o->rid : "");
-
-                  g_variant_builder_init (&extras_builder, G_VARIANT_TYPE ("a{sv}"));
-                  g_variant_builder_add (&builder,
-                                         "(sssbxxa{sv})",
-                                         id,
-                                         a->summary != NULL ? a->summary : "",
-                                         a->description != NULL ? a->description : "",
-                                         (gboolean) a->is_all_day,
-                                         (gint64) start_time,
-                                         (gint64) end_time,
-                                         extras_builder);
-                  g_free (id);
-                }
-            }
-        }
-      g_dbus_method_invocation_return_value (invocation,
-                                             g_variant_new ("(a(sssbxxa{sv}))", &builder));
+      g_dbus_method_invocation_return_value (invocation, NULL);
+
+      if (window_changed || force_reload)
+        app_update_views (app);
     }
   else
     {
@@ -989,12 +1021,12 @@ on_bus_acquired (GDBusConnection *connection,
                  const gchar     *name,
                  gpointer         user_data)
 {
-  GError *error;
+  GMainLoop *main_loop = user_data;
   guint registration_id;
+  g_autoptr (GError) error = NULL;
 
   _global_app = app_new (connection);
 
-  error = NULL;
   registration_id = g_dbus_connection_register_object (connection,
                                                        "/org/gnome/Shell/CalendarServer",
                                                        introspection_data->interfaces[0],
@@ -1004,16 +1036,15 @@ on_bus_acquired (GDBusConnection *connection,
                                                        &error);
   if (registration_id == 0)
     {
-      g_printerr ("Error exporting object: %s (%s %d)",
+      g_printerr ("Error exporting object: %s (%s %d)\n",
                   error->message,
                   g_quark_to_string (error->domain),
                   error->code);
-      g_error_free (error);
-      _exit (1);
+      g_main_loop_quit (main_loop);
+      return;
     }
 
   print_debug ("Connected to the session bus");
-
 }
 
 static void
@@ -1060,7 +1091,7 @@ int
 main (int    argc,
       char **argv)
 {
-  GError *error;
+  g_autoptr (GError) error = NULL;
   GOptionContext *opt_context;
   GMainLoop *main_loop;
   gint ret;
@@ -1077,11 +1108,9 @@ main (int    argc,
 
   opt_context = g_option_context_new ("gnome-shell calendar server");
   g_option_context_add_main_entries (opt_context, opt_entries, NULL);
-  error = NULL;
   if (!g_option_context_parse (opt_context, &argc, &argv, &error))
     {
-      g_printerr ("Error parsing options: %s", error->message);
-      g_error_free (error);
+      g_printerr ("Error parsing options: %s\n", error->message);
       goto out;
     }
 
@@ -1120,41 +1149,6 @@ main (int    argc,
     g_bus_unown_name (name_owner_id);
   if (opt_context != NULL)
     g_option_context_free (opt_context);
-  return ret;
-}
-
-/* ---------------------------------------------------------------------------------------------------- */
-
-static void __attribute__((format(printf, 1, 0)))
-print_debug (const gchar *format, ...)
-{
-  g_autofree char *s = NULL;
-  g_autofree char *timestamp = NULL;
-  va_list ap;
-  g_autoptr (GDateTime) now = NULL;
-  static volatile gsize 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:
-  ;
+  return ret;
 }
openSUSE Build Service is sponsored by