From c00d79bae229d2c6edab14db678afb59501ad46c Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Thu, 2 Apr 2020 09:24:28 +0200 Subject: [PATCH] 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 | 87 +- meson.build | 2 +- src/calendar-server/calendar-sources.c | 729 ++++++------ src/calendar-server/calendar-sources.h | 29 +- .../gnome-shell-calendar-server.c | 1050 ++++++++--------- 6 files changed, 961 insertions(+), 955 deletions(-) diff --git a/data/dbus-interfaces/org.gnome.Shell.CalendarServer.xml b/data/dbus-interfaces/org.gnome.Shell.CalendarServer.xml index c19883095..51b71ef03 100644 --- a/data/dbus-interfaces/org.gnome.Shell.CalendarServer.xml +++ b/data/dbus-interfaces/org.gnome.Shell.CalendarServer.xml @@ -1,12 +1,19 @@ - - - - - + + + + + + + + + + + + + - diff --git a/js/ui/calendar.js b/js/ui/calendar.js index 94982cf88..1a25dde25 100644 --- a/js/ui/calendar.js +++ b/js/ui/calendar.js @@ -222,7 +222,12 @@ class DBusEventSource extends EventSourceBase { } } - 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) @@ -258,7 +263,7 @@ class DBusEventSource extends EventSourceBase { } _resetCache() { - this._events = []; + this._events = new Map(); this._lastRequestBegin = null; this._lastRequestEnd = null; } @@ -274,28 +279,47 @@ class DBusEventSource extends EventSourceBase { this.emit('changed'); } - _onChanged() { - this._loadEvents(false); + _onEventsAddedOrUpdated(dbusProxy, nameOwner, argArray) { + const [appointments = []] = argArray; + let changed = false; + + for (let n = 0; n < appointments.length; n++) { + 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); + this._events.set(event.id, event); + + changed = true; + } + + if (changed) + this.emit('changed'); } - _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]; - let event = new CalendarEvent(id, date, end, summary, allDay); - newEvents.push(event); - } - newEvents.sort((ev1, ev2) => ev1.date.getTime() - ev2.date.getTime()); + _onEventsRemoved(dbusProxy, nameOwner, argArray) { + const [ids = []] = argArray; - this._events = newEvents; - this._isLoading = false; - this.emit('changed'); + 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) { @@ -304,27 +328,30 @@ class DBusEventSource extends EventSourceBase { 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) { + for (const event of this._events.values()) { if (_dateIntervalsOverlap(event.date, event.end, begin, end)) yield event; } @@ -879,7 +906,7 @@ 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 95a6868f3..3dacbaa2d 100644 --- a/meson.build +++ b/meson.build @@ -19,7 +19,7 @@ cogl_pango_pc = 'mutter-cogl-pango-' + mutter_api_version libmutter_pc = 'libmutter-' + mutter_api_version 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 3281500ec..d09956831 100644 --- a/src/calendar-server/calendar-sources.c +++ b/src/calendar-server/calendar-sources.c @@ -45,137 +45,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 +registry_watcher_source_appeared_cb (ESourceRegistryWatcher *watcher, + ESource *source, + CalendarSources *sources) +{ + ECalClientSourceType source_type; + + if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR)) + source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS; + else if (e_source_has_extension (source, E_SOURCE_EXTENSION_MEMO_LIST)) + source_type = E_CAL_CLIENT_SOURCE_TYPE_MEMOS; + else if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST)) + source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS; + else + g_return_if_reached (); + + calendar_sources_connect_client (sources, source, source_type, 30, NULL, calendar_sources_client_connected_cb, g_object_ref (source)); +} + +static void +registry_watcher_source_disappeared_cb (ESourceRegistryWatcher *watcher, + ESource *source, + CalendarSources *sources) +{ + gboolean emit; + + g_mutex_lock (&sources->clients_lock); + + emit = g_hash_table_remove (sources->clients, source); + + g_mutex_unlock (&sources->clients_lock); + + if (emit) + g_signal_emit (sources, signals[CLIENT_DISAPPEARED], 0, e_source_get_uid (source), NULL); +} static void client_data_free (ClientData *data) { - g_clear_signal_handler (&data->backend_died_id, data->client); + g_signal_handler_disconnect (data->client, data->backend_died_id); g_object_unref (data->client); g_slice_free (ClientData, data); } static void -calendar_sources_class_init (CalendarSourcesClass *klass) -{ - GObjectClass *gobject_class = (GObjectClass *) klass; - - parent_class = g_type_class_peek_parent (klass); - - gobject_class->finalize = calendar_sources_finalize; - - signals [APPOINTMENT_SOURCES_CHANGED] = - g_signal_new ("appointment-sources-changed", - G_TYPE_FROM_CLASS (gobject_class), - G_SIGNAL_RUN_LAST, - 0, - NULL, - NULL, - NULL, - G_TYPE_NONE, - 0); - - signals [TASK_SOURCES_CHANGED] = - g_signal_new ("task-sources-changed", - G_TYPE_FROM_CLASS (gobject_class), - G_SIGNAL_RUN_LAST, - 0, - NULL, - NULL, - NULL, - G_TYPE_NONE, - 0); -} - -static void -calendar_sources_init (CalendarSources *sources) +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 @@ -185,53 +170,30 @@ calendar_sources_init (CalendarSources *sources) exit (EXIT_FAILURE); } - g_object_unref (session_bus); + g_return_if_fail (registry != NULL); - 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->registry_watcher = e_source_registry_watcher_new (registry, NULL); - 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; + g_clear_object (®istry); - 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; -} + 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); -static void -calendar_sources_finalize_source_data (CalendarSources *sources, - CalendarSourceData *source_data) -{ - if (source_data->loaded) - { - g_hash_table_destroy (source_data->clients); - source_data->clients = NULL; - - g_clear_handle_id (&source_data->timeout_id, g_source_remove); - - source_data->loaded = FALSE; - } + e_source_registry_watcher_reclaim (sources->registry_watcher); } static void @@ -239,28 +201,67 @@ calendar_sources_finalize (GObject *object) { CalendarSources *sources = CALENDAR_SOURCES (object); - if (sources->registry) + g_clear_pointer (&sources->clients, g_hash_table_destroy); + + if (sources->registry_watcher) { - 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); + 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); } - sources->registry = NULL; - calendar_sources_finalize_source_data (sources, &sources->appointment_sources); - calendar_sources_finalize_source_data (sources, &sources->task_sources); + g_mutex_clear (&sources->clients_lock); - if (G_OBJECT_CLASS (parent_class)->finalize) - G_OBJECT_CLASS (parent_class)->finalize (object); + G_OBJECT_CLASS (calendar_sources_parent_class)->finalize (object); +} + +static void +calendar_sources_class_init (CalendarSourcesClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->constructed = calendar_sources_constructed; + gobject_class->finalize = calendar_sources_finalize; + + signals [CLIENT_APPEARED] = + g_signal_new ("client-appeared", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + E_TYPE_CAL_CLIENT); + + signals [CLIENT_DISAPPEARED] = + g_signal_new ("client-disappeared", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_STRING); /* ESource::uid of the disappeared client */ +} + +static void +calendar_sources_init (CalendarSources *sources) +{ + g_mutex_init (&sources->clients_lock); } CalendarSources * calendar_sources_get (void) { + static CalendarSources *calendar_sources_singleton = NULL; gpointer singleton_location = &calendar_sources_singleton; if (calendar_sources_singleton) @@ -273,80 +274,65 @@ calendar_sources_get (void) return calendar_sources_singleton; } -/* The clients are just created here but not loaded */ -static void -create_client_for_source (ESource *source, - ECalClientSourceType source_type, - CalendarSourceData *source_data) +ESourceRegistry * +calendar_sources_get_registry (CalendarSources *sources) { - ClientData *data; - EClient *client; - GError *error = NULL; - - client = g_hash_table_lookup (source_data->clients, source); - g_return_if_fail (client == NULL); - - 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; - } - - 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_hash_table_insert (source_data->clients, g_object_ref (source), data); -} - -static inline void -debug_dump_ecal_list (GHashTable *clients) -{ -#ifdef CALENDAR_ENABLE_DEBUG - GList *list, *link; - - dprintf ("Loaded clients:\n"); - list = g_hash_table_get_keys (clients); - for (link = list; link != NULL; link = g_list_next (link)) - { - ESource *source = E_SOURCE (link->data); - - dprintf (" %s %s\n", - e_source_get_uid (source), - e_source_get_display_name (source)); - } - g_list_free (list); -#endif + return e_source_registry_watcher_get_registry (sources->registry_watcher); } static void -calendar_sources_load_esource_list (ESourceRegistry *registry, - CalendarSourceData *source_data); - -static gboolean -backend_restart (gpointer data) +gather_event_clients_cb (gpointer key, + gpointer value, + gpointer user_data) { - CalendarSourceData *source_data = data; - ESourceRegistry *registry; + GSList **plist = user_data; + ClientData *cd = value; - registry = source_data->sources->registry; - calendar_sources_load_esource_list (registry, source_data); - g_signal_emit (source_data->sources, source_data->changed_signal, 0); + if (cd) + *plist = g_slist_prepend (*plist, g_object_ref (cd->client)); +} - source_data->timeout_id = 0; - - return FALSE; +GSList * +calendar_sources_ref_clients (CalendarSources *sources) +{ + GSList *list = NULL; + + g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL); + + g_mutex_lock (&sources->clients_lock); + g_hash_table_foreach (sources->clients, gather_event_clients_cb, &list); + g_mutex_unlock (&sources->clients_lock); + + return list; +} + +gboolean +calendar_sources_has_clients (CalendarSources *sources) +{ + GHashTableIter iter; + gpointer value; + gboolean has = FALSE; + + g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), FALSE); + + g_mutex_lock (&sources->clients_lock); + + g_hash_table_iter_init (&iter, sources->clients); + while (!has && g_hash_table_iter_next (&iter, NULL, &value)) + { + ClientData *cd = value; + + has = cd != NULL; + } + + g_mutex_unlock (&sources->clients_lock); + + return has; } static void -backend_died_cb (EClient *client, CalendarSourceData *source_data) +backend_died_cb (EClient *client, + CalendarSources *sources) { ESource *source; const char *display_name; @@ -354,196 +340,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) + g_mutex_lock (&sources->clients_lock); + client_data = g_hash_table_lookup (sources->clients, source); + if (client_data) + client = E_CLIENT (g_object_ref (client_data->client)); + g_mutex_unlock (&sources->clients_lock); + + if (client) + return client; + + client = e_cal_client_connect_sync (source, source_type, wait_for_connected_seconds, cancellable, error); + if (!client) + return NULL; + + g_mutex_lock (&sources->clients_lock); + client_data = g_hash_table_lookup (sources->clients, source); + if (client_data) { - 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_clear_object (&client); + client = E_CLIENT (g_object_ref (client_data->client)); } - - list = e_source_registry_list_sources (registry, extension_name); - - for (link = list; link != NULL; link = g_list_next (link)) + else { - ESource *source = E_SOURCE (link->data); - ESourceSelectable *extension; - gboolean show_source; + 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); - 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); + g_hash_table_insert (sources->clients, g_object_ref (source), client_data); } + g_mutex_unlock (&sources->clients_lock); - debug_dump_ecal_list (source_data->clients); - - g_list_free_full (list, g_object_unref); + return client; } +typedef struct _AsyncContext { + ESource *source; + ECalClientSourceType source_type; + guint32 wait_for_connected_seconds; +} AsyncContext; + static void -calendar_sources_registry_source_changed_cb (ESourceRegistry *registry, - ESource *source, - CalendarSources *sources) +async_context_free (gpointer ptr) { - if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR)) + AsyncContext *ctx = ptr; + + if (ctx) { - 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); - } - } - - if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST)) - { - CalendarSourceData *source_data; - ESourceSelectable *extension; - gboolean have_client; - gboolean show_source; - - source_data = &sources->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); - } + g_clear_object (&ctx->source); + g_slice_free (AsyncContext, ctx); } } static void -calendar_sources_registry_source_removed_cb (ESourceRegistry *registry, - ESource *source, - CalendarSources *sources) +calendar_sources_connect_client_thread (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) { - if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR)) + 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) { - CalendarSourceData *source_data; - - 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)) - { - 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); + 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); } } -static void -ensure_appointment_sources (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) { - if (!sources->appointment_sources.loaded) + AsyncContext *ctx; + g_autoptr (GTask) task = 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; + + task = g_task_new (sources, cancellable, callback, user_data); + g_task_set_source_tag (task, calendar_sources_connect_client); + g_task_set_task_data (task, ctx, async_context_free); + + g_task_run_in_thread (task, calendar_sources_connect_client_thread); +} + +EClient * +calendar_sources_connect_client_finish (CalendarSources *sources, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, sources), NULL); + g_return_val_if_fail (g_async_result_is_tagged (result, calendar_sources_connect_client), NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} + + +void +print_debug (const gchar *format, + ...) +{ + g_autofree char *s = NULL; + g_autofree char *timestamp = NULL; + va_list ap; + g_autoptr (GDateTime) now = NULL; + static 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->appointment_sources); - sources->appointment_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_appointment_clients (CalendarSources *sources) -{ - GList *list, *link; - - g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL); - - ensure_appointment_sources (sources); - - list = g_hash_table_get_values (sources->appointment_sources.clients); - - for (link = list; link != NULL; link = g_list_next (link)) - link->data = ((ClientData *) link->data)->client; - - return list; -} - -static void -ensure_task_sources (CalendarSources *sources) -{ - if (!sources->task_sources.loaded) - { - calendar_sources_load_esource_list (sources->registry, - &sources->task_sources); - sources->task_sources.loaded = TRUE; - } -} - -GList * -calendar_sources_get_task_clients (CalendarSources *sources) -{ - GList *list, *link; - - g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL); - - ensure_task_sources (sources); - - list = g_hash_table_get_values (sources->task_sources.clients); - - for (link = list; link != NULL; link = g_list_next (link)) - link->data = ((ClientData *) link->data)->client; - - return list; -} - -gboolean -calendar_sources_has_sources (CalendarSources *sources) -{ - g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), FALSE); - - ensure_appointment_sources (sources); - ensure_task_sources (sources); - - return g_hash_table_size (sources->appointment_sources.clients) > 0 || - g_hash_table_size (sources->task_sources.clients) > 0; + + if (!show_debug) + goto out; + + now = g_date_time_new_now_local (); + timestamp = g_date_time_format (now, "%H:%M:%S"); + + va_start (ap, format); + s = g_strdup_vprintf (format, ap); + va_end (ap); + + g_print ("gnome-shell-calendar-server[%d]: %s.%03d: %s\n", + pid, timestamp, g_date_time_get_microsecond (now), s); + out: + ; } diff --git a/src/calendar-server/calendar-sources.h b/src/calendar-server/calendar-sources.h index d11850fff..1ffc8ad01 100644 --- a/src/calendar-server/calendar-sources.h +++ b/src/calendar-server/calendar-sources.h @@ -26,17 +26,38 @@ #include +#define EDS_DISABLE_DEPRECATED +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +#include +#include +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); -gboolean calendar_sources_has_sources (CalendarSources *sources); +void calendar_sources_connect_client (CalendarSources *sources, + ESource *source, + ECalClientSourceType source_type, + guint32 wait_for_connected_seconds, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +EClient *calendar_sources_connect_client_finish + (CalendarSources *sources, + GAsyncResult *result, + GError **error); + +/* Set the environment variable CALENDAR_SERVER_DEBUG to show debug */ +void print_debug (const gchar *str, + ...) G_GNUC_PRINTF (1, 2); G_END_DECLS diff --git a/src/calendar-server/gnome-shell-calendar-server.c b/src/calendar-server/gnome-shell-calendar-server.c index 23398bb03..8ee8d3f1c 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[] = "" " " - " " + " " " " " " " " - " " " " - " " + " " + " " + " " + " " + " " + " " + " " + " " + " " " " " " " " @@ -75,35 +79,40 @@ static App *_global_app = NULL; /* ---------------------------------------------------------------------------------------------------- */ -typedef struct +/* 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) { - char *rid; - time_t start_time; - time_t end_time; -} CalendarOccurrence; - -typedef struct -{ - char *uid; - char *source_id; - char *backend_name; - char *summary; - char *description; - char *color_string; - time_t start_time; - time_t end_time; - guint is_all_day : 1; - - /* Only used internally */ - GSList *occurrences; -} CalendarAppointment; + return g_strconcat ( + source_uid ? source_uid : "", + "\n", + comp_uid ? comp_uid : "", + "\n", + comp_rid ? comp_rid : "", + NULL); +} typedef struct { ECalClient *client; - GHashTable *appointments; + GSList **pappointments; /* CalendarAppointment * */ } CollectAppointmentsData; +typedef struct +{ + gchar *id; + gchar *summary; + time_t start_time; + time_t end_time; + guint is_all_day : 1; +} CalendarAppointment; + 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) -{ - ESource *source; - ECalClientSourceType source_type; - ESourceBackend *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_backend_dup_backend_name (extension); -} - -static inline int -null_safe_strcmp (const char *a, - const char *b) -{ - return (!a && !b) ? 0 : (a && !b) || (!a && b) ? 1 : strcmp (a, b); -} - -static inline gboolean -calendar_appointment_equal (CalendarAppointment *a, - CalendarAppointment *b) -{ - GSList *la, *lb; - - if (g_slist_length (a->occurrences) != g_slist_length (b->occurrences)) - return FALSE; - - for (la = a->occurrences, lb = b->occurrences; la && lb; la = la->next, lb = lb->next) - { - 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; - } - - 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) +calendar_appointment_new (ECalClient *cal, + ECalComponent *comp) { - CalendarAppointment *appointment; + CalendarAppointment *appt; + ICalTimezone *default_zone; + ICalComponent *ical; + ECalComponentId *id; - appointment = g_new0 (CalendarAppointment, 1); + default_zone = e_cal_client_get_default_timezone (cal); + ical = e_cal_component_get_icalcomponent (comp); + id = e_cal_component_get_id (comp); - calendar_appointment_init (appointment, icomp, cal); - return appointment; + appt = g_new0 (CalendarAppointment, 1); + + appt->id = create_event_id (e_source_get_uid (e_client_get_source (E_CLIENT (cal))), + id ? e_cal_component_id_get_uid (id) : NULL, + id ? e_cal_component_id_get_rid (id) : NULL); + appt->summary = g_strdup (i_cal_component_get_summary (ical)); + appt->start_time = get_ical_start_time (cal, ical, default_zone); + appt->end_time = get_ical_end_time (cal, ical, default_zone); + appt->is_all_day = get_ical_is_all_day (cal, + ical, + appt->start_time, + default_zone); + + e_cal_component_id_free (id); + + return appt; +} + +static void +calendar_appointment_free (gpointer ptr) +{ + CalendarAppointment *appt = ptr; + + if (appt) + { + g_free (appt->id); + g_free (appt->summary); + g_free (appt); + } } static time_t @@ -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; + GSList *notify_appointments; /* CalendarAppointment *, for EventsAdded */ + GSList *notify_ids; /* gchar *, for EventsRemoved */ + guint events_added_timeout_id; + guint events_removed_timeout_id; - gboolean cache_invalid; - - 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,48 +365,188 @@ 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 +app_schedule_events_removed (App *app) +{ + 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 -invalidate_cache (App *app) +app_process_added_modified_objects (App *app, + ECalClientView *view, + GSList *objects) /* ICalComponent * */ { - app->cache_invalid = TRUE; + ECalClient *cal_client; + GSList *link; + gboolean expand_recurrences; + + 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 (link = objects; link; link = g_slist_next (link)) + { + ECalComponent *comp; + ICalComponent *icomp = link->data; + + if (!icomp || !i_cal_component_get_uid (icomp)) + continue; + + if (expand_recurrences && + !e_cal_util_component_is_instance (icomp) && + e_cal_util_component_has_recurrences (icomp)) + { + CollectAppointmentsData data; + + data.client = cal_client; + data.pappointments = &app->notify_appointments; + + e_cal_client_generate_instances_for_object_sync (cal_client, icomp, app->since, app->until, NULL, + generate_instances_cb, &data); + } + else + { + 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 @@ -580,24 +555,13 @@ on_objects_added (ECalClientView *view, gpointer user_data) { App *app = user_data; - GSList *l; + ECalClient *client; - print_debug ("%s for calendar", G_STRFUNC); + 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); - for (l = objects; l != NULL; l = l->next) - { - ICalComponent *icomp = l->data; - const char *uid; - - uid = i_cal_component_get_uid (icomp); - - if (g_hash_table_lookup (app->appointments, uid) == NULL) - { - /* new appointment we don't know about => changed signal */ - invalidate_cache (app); - app_schedule_changed (app); - } - } + 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; + + client = e_cal_client_view_ref_client (view); + source_uid = e_source_get_uid (e_client_get_source (E_CLIENT (client))); + + print_debug ("%s (%d) for calendar '%s'", G_STRFUNC, g_slist_length (uids), source_uid); + + for (link = uids; link; link = g_slist_next (link)) + { + ECalComponentId *id = link->data; + + if (!id) + continue; + + app->notify_ids = g_slist_prepend (app->notify_ids, + create_event_id (source_uid, + e_cal_component_id_get_uid (id), + e_cal_component_id_get_rid (id))); + } + + g_clear_object (&client); + + if (app->notify_ids) + app_schedule_events_removed (app); } -static void -app_load_events (App *app) +static gboolean +app_has_calendars (App *app) { - GList *clients; - GList *l; - GList *ll; - gchar *since_iso8601; - gchar *until_iso8601; - gchar *query; - const char *tz_location; + return app->live_views != NULL; +} - /* out with the old */ - g_hash_table_remove_all (app->appointments); - /* nuke existing views */ - for (ll = app->live_views; ll != NULL; ll = ll->next) - { - 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); - } - g_list_free (app->live_views); - 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)) { - ECalClient *cal = E_CAL_CLIENT (l->data); - GError *error; - 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); - } + 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); } - g_list_free (clients); - g_free (since_iso8601); - g_free (until_iso8601); - g_free (query); - app->cache_invalid = FALSE; -} -static gboolean -app_has_calendars (App *app) -{ - return calendar_sources_has_sources (app->sources); + return view; } static void -on_appointment_sources_changed (CalendarSources *sources, - gpointer user_data) +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)) + { + app_stop_view (app, link->data); + } + + g_slist_free_full (app->live_views, g_object_unref); + app->live_views = NULL; + + clients = calendar_sources_ref_clients (app->sources); + + for (link = clients; link; link = g_slist_next (link)) + { + ECalClient *cal_client = link->data; + ECalClientView *view; + + if (!cal_client) + continue; + + view = app_start_view (app, cal_client); + if (view) + app->live_views = g_slist_prepend (app->live_views, view); + } + + g_slist_free_full (clients, g_object_unref); +} + +static void +app_notify_has_calendars (App *app) +{ + GVariantBuilder dict_builder; + + g_variant_builder_init (&dict_builder, G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_add (&dict_builder, "{sv}", "HasCalendars", + g_variant_new_boolean (app_has_calendars (app))); + + g_dbus_connection_emit_signal (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_client_appeared_cb (CalendarSources *sources, + ECalClient *client, + gpointer user_data) { App *app = user_data; + ECalClientView *view; + GSList *link; + const gchar *source_uid; - print_debug ("Sources changed\n"); - app_load_events (app); + source_uid = e_source_get_uid (e_client_get_source (E_CLIENT (client))); - /* Notify the HasCalendars property */ - { - GVariantBuilder dict_builder; + print_debug ("Client appeared '%s'", source_uid); - 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))); + for (link = app->live_views; link; link = g_slist_next (link)) + { + ECalClientView *view = link->data; + ECalClient *cal_client; + ESource *source; - 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); - } + cal_client = e_cal_client_view_ref_client (view); + source = e_client_get_source (E_CLIENT (cal_client)); + + if (g_strcmp0 (source_uid, e_source_get_uid (source)) == 0) + { + g_clear_object (&cal_client); + return; + } + + g_clear_object (&cal_client); + } + + view = app_start_view (app, client); + + if (view) + { + app->live_views = g_slist_prepend (app->live_views, view); + + /* It's the first view, notify that it has calendars now */ + if (!g_slist_next (app->live_views)) + app_notify_has_calendars (app); + } +} + +static void +on_client_disappeared_cb (CalendarSources *sources, + const gchar *source_uid, + gpointer user_data) +{ + App *app = user_data; + GSList *link; + + print_debug ("Client disappeared '%s'", source_uid); + + for (link = app->live_views; link; link = g_slist_next (link)) + { + ECalClientView *view = link->data; + ECalClient *cal_client; + ESource *source; + + cal_client = e_cal_client_view_ref_client (view); + source = e_client_get_source (E_CLIENT (cal_client)); + + if (g_strcmp0 (source_uid, e_source_get_uid (source)) == 0) + { + g_clear_object (&cal_client); + app_stop_view (app, view); + app->live_views = g_slist_remove (app->live_views, view); + g_object_unref (view); + + print_debug ("Emitting ClientDisappeared for '%s'", source_uid); + + g_dbus_connection_emit_signal (app->connection, + NULL, /* destination_bus_name */ + "/org/gnome/Shell/CalendarServer", + "org.gnome.Shell.CalendarServer", + "ClientDisappeared", + g_variant_new ("(s)", source_uid), + NULL); + + /* It was the last view, notify that it doesn't have calendars now */ + if (!app->live_views) + app_notify_has_calendars (app); + + break; + } + + g_clear_object (&cal_client); + } } static App * @@ -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** */ + + g_variant_builder_unref (builder); + g_variant_builder_unref (invalidated_builder); } - /* reload events if necessary */ - if (window_changed || force_reload || app->cache_invalid) - { - app_load_events (app); - } + g_dbus_method_invocation_return_value (invocation, NULL); - /* 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)); + 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: - ; -}