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: - ; -}