[evolution-data-server] Introduce EReminderWatcher to listen for scheduled reminders



commit 8b25379ac0506a0200fb1bec1e6bce64646f148a
Author: Milan Crha <mcrha redhat com>
Date:   Tue Apr 17 18:27:53 2018 +0200

    Introduce EReminderWatcher to listen for scheduled reminders
    
    This object can be used by applications which want to implement
    their own notifications for scheduled reminders without a need
    to implement the core functionality.

 ...e.evolution-data-server.calendar.gschema.xml.in |   10 +-
 .../evolution-data-server-docs.sgml.in             |    1 +
 src/calendar/libecal/CMakeLists.txt                |    2 +
 src/calendar/libecal/e-reminder-watcher.c          | 2747 ++++++++++++++++++++
 src/calendar/libecal/e-reminder-watcher.h          |  159 ++
 src/calendar/libecal/libecal.h                     |    1 +
 6 files changed, 2919 insertions(+), 1 deletions(-)
---
diff --git a/data/org.gnome.evolution-data-server.calendar.gschema.xml.in 
b/data/org.gnome.evolution-data-server.calendar.gschema.xml.in
index 480da1a..2e82c97 100644
--- a/data/org.gnome.evolution-data-server.calendar.gschema.xml.in
+++ b/data/org.gnome.evolution-data-server.calendar.gschema.xml.in
@@ -23,6 +23,14 @@
       <_summary>Birthday and anniversary reminder units</_summary>
       <_description>Units for a birthday or anniversary reminder, “minutes”, “hours” or “days”</_description>
     </key>
-  </schema>
 
+    <key name="reminders-past" type="as">
+      <default>['']</default>
+      <_summary>Past reminders for EReminderWatcher</_summary>
+    </key>
+    <key name="reminders-snoozed" type="as">
+      <default>['']</default>
+      <_summary>Snoozed reminders for EReminderWatcher</_summary>
+    </key>
+  </schema>
 </schemalist>
diff --git a/docs/reference/evolution-data-server/evolution-data-server-docs.sgml.in 
b/docs/reference/evolution-data-server/evolution-data-server-docs.sgml.in
index 999cd7d..d3cb945 100644
--- a/docs/reference/evolution-data-server/evolution-data-server-docs.sgml.in
+++ b/docs/reference/evolution-data-server/evolution-data-server-docs.sgml.in
@@ -287,6 +287,7 @@
       <xi:include href="xml/e-timezone-cache.xml"/>
       <xi:include href="xml/e-cal-backend-util.xml"/>
       <xi:include href="xml/e-cal-types.xml"/>
+      <xi:include href="xml/e-reminder-watcher.xml"/>
     </chapter>
 
     <chapter>
diff --git a/src/calendar/libecal/CMakeLists.txt b/src/calendar/libecal/CMakeLists.txt
index 5240dd3..addfa07 100644
--- a/src/calendar/libecal/CMakeLists.txt
+++ b/src/calendar/libecal/CMakeLists.txt
@@ -20,6 +20,7 @@ set(SOURCES
        e-cal-util.c
        e-cal-view.c
        e-cal-view-private.h
+       e-reminder-watcher.c
        e-timezone-cache.c
        ${CMAKE_CURRENT_BINARY_DIR}/e-cal-enumtypes.c
 )
@@ -37,6 +38,7 @@ set(HEADERS
        e-cal-types.h
        e-cal-util.h
        e-cal-view.h
+       e-reminder-watcher.h
        e-timezone-cache.h
        ${CMAKE_CURRENT_BINARY_DIR}/e-cal-enumtypes.h
 )
diff --git a/src/calendar/libecal/e-reminder-watcher.c b/src/calendar/libecal/e-reminder-watcher.c
new file mode 100644
index 0000000..56c70c5
--- /dev/null
+++ b/src/calendar/libecal/e-reminder-watcher.c
@@ -0,0 +1,2747 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2018 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION: e-reminder-watcher
+ * @include: libecal/libecal.h
+ * @short_description: Calendar reminder watcher
+ *
+ * The #EReminderWatcher watches reminders in configured calendars
+ * and notifies the owner about them through signals. It also remembers
+ * past and snoozed reminders. It doesn't provide any GUI, it's all
+ * up to the owner to provide such functionality.
+ *
+ * The API is thread safe and each signal is emitted from the thread of
+ * the default main context of the process.
+ **/
+
+#include "evolution-data-server-config.h"
+
+#include <string.h>
+
+#include "libedataserver/libedataserver.h"
+
+#include "e-cal-client.h"
+#include "e-cal-time-util.h"
+
+#include "e-reminder-watcher.h"
+
+typedef struct _ClientData {
+       EReminderWatcher *watcher; /* Just as an owner, not referenced */
+       ECalClient *client;
+       ECalClientView *view;
+} ClientData;
+
+struct _EReminderWatcherPrivate {
+       GRecMutex lock;
+
+       ESourceRegistry *registry;
+       ESourceRegistryWatcher *registry_watcher;
+       GCancellable *cancellable;
+       GSettings *settings;
+       gulong past_changed_handler_id;
+       gulong snoozed_changed_handler_id;
+       guint expected_past_changes;
+       guint expected_snoozed_changes;
+
+       icaltimezone *default_zone;
+
+       GSList *clients; /* ClientData * */
+       GSList *snoozed; /* EReminderData * */
+       GHashTable *scheduled; /* gchar *source_uid ~> GSList * { EReminderData * } */
+
+       gulong construct_idle_id;
+       gulong timer_handler_id;
+
+       gint64 expected_wall_clock_time;
+       gulong wall_clock_handler_id;
+
+       gint64 next_midnight;
+       gint64 next_trigger;
+};
+
+enum {
+       PROP_0,
+       PROP_REGISTRY,
+       PROP_DEFAULT_ZONE
+};
+
+enum {
+       TRIGGERED,
+       REMOVED,
+       CHANGED,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (EReminderWatcher, e_reminder_watcher, G_TYPE_OBJECT)
+
+G_DEFINE_BOXED_TYPE (EReminderData, e_reminder_data, e_reminder_data_copy, e_reminder_data_free)
+G_DEFINE_BOXED_TYPE (EReminderWatcherZone, e_reminder_watcher_zone, e_reminder_watcher_zone_copy, 
e_reminder_watcher_zone_free)
+
+static void
+e_reminder_watcher_objects_added_cb (ECalClientView *view,
+                                    const GSList *objects, /* icalcomponent * */
+                                    gpointer user_data);
+static void
+e_reminder_watcher_objects_modified_cb (ECalClientView *view,
+                                       const GSList *objects, /* icalcomponent * */
+                                       gpointer user_data);
+static void
+e_reminder_watcher_objects_removed_cb (ECalClientView *view,
+                                      const GSList *uids, /* ECalComponentId * */
+                                      gpointer user_data);
+
+static gboolean
+e_reminder_watcher_debug_enabled (void)
+{
+       static gint enabled = -1;
+
+       if (enabled == -1)
+               enabled = g_strcmp0 (g_getenv ("ERW_DEBUG"), "1") == 0 ? 1 : 0;
+
+       return enabled == 1;
+}
+
+static void
+e_reminder_watcher_debug_print (const gchar *format,
+                               ...) G_GNUC_PRINTF (1, 2);
+
+static void
+e_reminder_watcher_debug_print (const gchar *format,
+                               ...)
+{
+       va_list args;
+
+       if (!e_reminder_watcher_debug_enabled ())
+               return;
+
+       va_start (args, format);
+       e_util_debug_printv ("ERW", format, args);
+       va_end (args);
+}
+
+static const gchar *
+e_reminder_watcher_timet_as_string (gint64 tt)
+{
+       static gchar buffers[10][32 + 1];
+       static volatile gint curr_index = 0;
+       gint counter = 0, index;
+       struct icaltimetype itt;
+
+       while (index = (curr_index + 1) % 10, !g_atomic_int_compare_and_exchange (&curr_index, curr_index, 
index)) {
+               counter++;
+               if (counter > 20)
+                       break;
+       }
+
+       itt = icaltime_from_timet_with_zone ((time_t) tt, 0, icaltimezone_get_utc_timezone ());
+
+       g_snprintf (buffers[index], 32, "%04d%02d%02dT%02d%02d%02d",
+               itt.year, itt.month, itt.day,
+               itt.hour, itt.minute, itt.second);
+
+       return buffers[index];
+}
+
+static ClientData *
+client_data_new (EReminderWatcher *watcher,
+                ECalClient *client) /* Assumes ownership of the 'client' */
+{
+       ClientData *cd;
+
+       g_return_val_if_fail (E_IS_REMINDER_WATCHER (watcher), NULL);
+       g_return_val_if_fail (E_IS_CAL_CLIENT (client), NULL);
+
+       cd = g_new0 (ClientData, 1);
+       cd->watcher = watcher;
+       cd->client = client;
+       cd->view = NULL;
+
+       return cd;
+}
+
+static void
+client_data_free_view (ClientData *cd)
+{
+       GError *local_error = NULL;
+
+       g_return_if_fail (cd != NULL);
+
+       if (!cd->view)
+               return;
+
+       e_cal_client_view_stop (cd->view, &local_error);
+
+       if (local_error) {
+               e_reminder_watcher_debug_print ("Failed to stop view for %s: %s\n",
+                       e_source_get_uid (e_client_get_source (E_CLIENT (cd->client))),
+                       local_error->message);
+
+               g_clear_error (&local_error);
+       } else {
+               e_reminder_watcher_debug_print ("Stopped view for %s\n",
+                       e_source_get_uid (e_client_get_source (E_CLIENT (cd->client))));
+       }
+
+       g_signal_handlers_disconnect_by_data (cd->view, cd->watcher);
+
+       g_clear_object (&cd->view);
+}
+
+static void
+client_data_free (gpointer ptr)
+{
+       ClientData *cd = ptr;
+
+       if (cd) {
+               client_data_free_view (cd);
+               g_clear_object (&cd->client);
+               g_free (cd);
+       }
+}
+
+static void
+client_data_source_written_cb (GObject *source_object,
+                              GAsyncResult *result,
+                              gpointer user_data)
+{
+       GError *local_error = NULL;
+
+       e_source_write_finish (E_SOURCE (source_object), result, &local_error);
+
+       if (local_error) {
+               g_warning ("Failed to write source %s changes: %s", e_source_get_uid (E_SOURCE 
(source_object)), local_error->message);
+               g_error_free (local_error);
+       }
+}
+
+static time_t
+client_get_last_notification_time (ECalClient *client)
+{
+       ESource *source;
+       ESourceAlarms *alarms_extension;
+       gchar *last_notified;
+       GTimeVal tmval = { 0 };
+       time_t value = 0, now;
+
+       if (!client)
+               return -1;
+
+       source = e_client_get_source (E_CLIENT (client));
+       if (!source || !e_source_has_extension (source, E_SOURCE_EXTENSION_ALARMS))
+               return -1;
+
+       alarms_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_ALARMS);
+       last_notified = e_source_alarms_dup_last_notified (alarms_extension);
+
+       if (last_notified && *last_notified &&
+           g_time_val_from_iso8601 (last_notified, &tmval)) {
+               now = time (NULL);
+               value = (time_t) tmval.tv_sec;
+
+               if (value > now)
+                       value = now;
+       }
+
+       g_free (last_notified);
+
+       return value;
+}
+
+static void
+client_set_last_notification_time (ECalClient *client,
+                                  time_t tt)
+{
+       ESource *source;
+       ESourceAlarms *alarms_extension;
+       GTimeVal tv = { 0 };
+       gchar *iso8601;
+       time_t now;
+
+       g_return_if_fail (client != NULL);
+       g_return_if_fail (tt > 0);
+
+       source = e_client_get_source (E_CLIENT (client));
+       if (!source)
+               return;
+
+       alarms_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_ALARMS);
+       iso8601 = e_source_alarms_dup_last_notified (alarms_extension);
+
+       if (iso8601) {
+               g_time_val_from_iso8601 (iso8601, &tv);
+               g_free (iso8601);
+       }
+
+       now = time (NULL);
+
+       if (tt > (time_t) tv.tv_sec || (time_t) tv.tv_sec > now) {
+               tv.tv_sec = (glong) tt;
+               iso8601 = g_time_val_to_iso8601 (&tv);
+               e_source_alarms_set_last_notified (alarms_extension, iso8601);
+
+               e_reminder_watcher_debug_print ("Changed last-notified for source %s (%s) to %s\n",
+                       e_source_get_uid (source),
+                       e_source_get_display_name (source),
+                       iso8601);
+
+               g_free (iso8601);
+
+               e_source_write (source, NULL, client_data_source_written_cb, NULL);
+       }
+}
+
+static void
+client_data_view_created_cb (GObject *source_object,
+                            GAsyncResult *result,
+                            gpointer user_data)
+{
+       ClientData *cd = user_data;
+       ECalClient *client;
+       ECalClientView *view = NULL;
+       GError *local_error = NULL;
+
+       g_return_if_fail (E_IS_CAL_CLIENT (source_object));
+       g_return_if_fail (cd != NULL);
+
+       client = E_CAL_CLIENT (source_object);
+
+       if (!e_cal_client_get_view_finish (client, result, &view, &local_error) || local_error || !view) {
+               e_reminder_watcher_debug_print ("Failed to get view for %s: %s\n",
+                       e_source_get_uid (e_client_get_source (E_CLIENT (cd->client))),
+                       local_error ? local_error->message : "Unknown error");
+       } else {
+               cd->view = view;
+
+               e_reminder_watcher_debug_print ("Got view for %s\n",
+                       e_source_get_uid (e_client_get_source (E_CLIENT (cd->client))));
+
+               g_signal_connect (
+                       cd->view, "objects-added",
+                       G_CALLBACK (e_reminder_watcher_objects_added_cb), cd->watcher);
+               g_signal_connect (
+                       cd->view, "objects-modified",
+                       G_CALLBACK (e_reminder_watcher_objects_modified_cb), cd->watcher);
+               g_signal_connect (
+                       cd->view, "objects-removed",
+                       G_CALLBACK (e_reminder_watcher_objects_removed_cb), cd->watcher);
+
+               e_cal_client_view_start (cd->view, &local_error);
+
+               if (local_error) {
+                       e_reminder_watcher_debug_print ("Failed to start view for %s: %s\n",
+                               e_source_get_uid (e_client_get_source (E_CLIENT (cd->client))),
+                               local_error ? local_error->message : "Unknown error");
+               }
+       }
+
+       g_clear_error (&local_error);
+}
+
+static void
+client_data_start_view (ClientData *cd,
+                       gint64 next_midnight,
+                       GCancellable *cancellable)
+{
+       gchar *iso_start, *iso_end;
+       time_t start_tt;
+
+       g_return_if_fail (cd != NULL);
+       g_return_if_fail (cd->client != NULL);
+
+       client_data_free_view (cd);
+
+       start_tt = client_get_last_notification_time (cd->client) + 1;
+       if (start_tt <= 0)
+               start_tt = time (NULL);
+
+       iso_start = isodate_from_time_t (start_tt);
+       iso_end = isodate_from_time_t ((time_t) next_midnight);
+
+       if (!iso_start || !iso_end) {
+               e_reminder_watcher_debug_print ("Failed to convert last notification %" G_GINT64_FORMAT " or 
next midnight %" G_GINT64_FORMAT " into iso strings for client %s\n",
+                       (gint64) start_tt, next_midnight, e_source_get_uid (e_client_get_source (E_CLIENT 
(cd->client))));
+       } else {
+               gchar *query;
+
+               query = g_strdup_printf ("(has-alarms-in-range? (make-time \"%s\") (make-time \"%s\"))", 
iso_start, iso_end);
+
+               e_reminder_watcher_debug_print ("Getting view for %s: %s\n",
+                       e_source_get_uid (e_client_get_source (E_CLIENT (cd->client))),
+                       query);
+
+               e_cal_client_get_view (cd->client, query, cancellable, client_data_view_created_cb, cd);
+
+               g_free (query);
+       }
+
+       g_free (iso_start);
+       g_free (iso_end);
+}
+
+static EReminderData *
+e_reminder_data_new_take_component (const gchar *source_uid,
+                                   ECalComponent *component,
+                                   const ECalComponentAlarmInstance *instance)
+{
+       EReminderData *rd;
+
+       g_return_val_if_fail (source_uid != NULL, NULL);
+       g_return_val_if_fail (component != NULL, NULL);
+       g_return_val_if_fail (instance != NULL, NULL);
+
+       rd = g_new0 (EReminderData, 1);
+       rd->source_uid = g_strdup (source_uid);
+       rd->component = component;
+       rd->instance.auid = g_strdup (instance->auid);
+       rd->instance.trigger = instance->trigger;
+       rd->instance.occur_start = instance->occur_start;
+       rd->instance.occur_end = instance->occur_end;
+
+       return rd;
+}
+
+/**
+ * e_reminder_data_new:
+ * @source_uid: an #ESource UID, to which the @component belongs
+ * @component: an #ECalComponent
+ * @instance: an #ECalComponentAlarmInstance describing one reminder instance
+ *
+ * Returns: (transfer full): a new #EReminderData prefilled with given values.
+ *    Free the returned structure with e_reminder_data_free() when no longer needed.
+ *
+ * Since: 3.30
+ **/
+EReminderData *
+e_reminder_data_new (const gchar *source_uid,
+                    const ECalComponent *component,
+                    const ECalComponentAlarmInstance *instance)
+{
+       g_return_val_if_fail (source_uid != NULL, NULL);
+       g_return_val_if_fail (component != NULL, NULL);
+       g_return_val_if_fail (instance != NULL, NULL);
+
+       return e_reminder_data_new_take_component (source_uid, e_cal_component_clone ((ECalComponent *) 
component), instance);
+}
+
+/**
+ * e_reminder_data_copy:
+ * @rd: (nullable): source #EReminderData, or %NULL
+ *
+ * Copies given #EReminderData structure. When the @rd is %NULL, simply returns %NULL as well.
+ *
+ * Returns: (transfer full): copy of @rd. Free the returned structure
+ *    with e_reminder_data_free() when no longer needed.
+ *
+ * Since: 3.30
+ **/
+EReminderData *
+e_reminder_data_copy (const EReminderData *rd)
+{
+       if (!rd)
+               return NULL;
+
+       return e_reminder_data_new_take_component (rd->source_uid, g_object_ref (rd->component), 
&(rd->instance));
+}
+
+/**
+ * e_reminder_data_free:
+ * @rd: (nullable): an #EReminderData, or %NULL
+ *
+ * Frees previously allocated #EReminderData structure with e_reminder_data_new()
+ * or e_reminder_data_copy(). The function does nothing when @rd is %NULL.
+ *
+ * Since: 3.30
+ **/
+void
+e_reminder_data_free (gpointer rd)
+{
+       EReminderData *ptr = rd;
+
+       if (ptr) {
+               g_clear_object (&ptr->component);
+               g_free (ptr->source_uid);
+               g_free (ptr->instance.auid);
+               g_free (ptr);
+       }
+}
+
+/**
+ * e_reminder_watcher_zone_copy:
+ * @watcher_zone: (nullable): an #EReminderWatcherZone to copy, or %NULL
+ *
+ * Returns: (transfer full): copy of @watcher_zone, or %NULL, when it's also %NULL.
+ *    Free returned instance with e_reminder_watcher_zone_free(), when no longer needed.
+ *
+ * Since: 3.30
+ **/
+EReminderWatcherZone *
+e_reminder_watcher_zone_copy (const EReminderWatcherZone *watcher_zone)
+{
+       if (!watcher_zone)
+               return NULL;
+
+       return (EReminderWatcherZone *) icaltimezone_copy ((icaltimezone *) watcher_zone);
+}
+
+/**
+ * e_reminder_watcher_zone_free:
+ * @watcher_zone: (nullable): an #EReminderWatcherZone to free
+ *
+ * Frees @watcher_zone, previously allocated with e_reminder_watcher_zone_copy().
+ * The function does nothing when the @watcher_zone is %NULL.
+ *
+ * Since: 3.30
+ **/
+void
+e_reminder_watcher_zone_free (EReminderWatcherZone *watcher_zone)
+{
+       if (watcher_zone)
+               icaltimezone_free ((icaltimezone *) watcher_zone, 1);
+}
+
+static gchar *
+e_reminder_data_to_string (const EReminderData *rd)
+{
+       GString *str;
+       gchar *icalstr;
+
+       g_return_val_if_fail (rd != NULL, NULL);
+       g_return_val_if_fail (rd->source_uid != NULL, NULL);
+       g_return_val_if_fail (rd->component != NULL, NULL);
+
+       icalstr = e_cal_component_get_as_string (rd->component);
+       g_return_val_if_fail (icalstr != NULL, NULL);
+
+       str = g_string_sized_new (strlen (icalstr) + 100);
+       g_string_append (str, rd->source_uid);
+       g_string_append_c (str, '\n');
+
+       if (rd->instance.auid)
+               g_string_append (str, rd->instance.auid);
+       g_string_append_c (str, '\n');
+
+       g_string_append_printf (str, "%" G_GINT64_FORMAT, rd->instance.trigger);
+       g_string_append_c (str, '\n');
+
+       g_string_append_printf (str, "%" G_GINT64_FORMAT, rd->instance.occur_start);
+       g_string_append_c (str, '\n');
+
+       g_string_append_printf (str, "%" G_GINT64_FORMAT, rd->instance.occur_end);
+       g_string_append_c (str, '\n');
+
+       g_string_append (str, icalstr);
+
+       g_free (icalstr);
+
+       return g_string_free (str, FALSE);
+}
+
+static EReminderData *
+e_reminder_data_from_string (const gchar *str)
+{
+       gchar **strv;
+       EReminderData *rd;
+       ECalComponent *component;
+       ECalComponentAlarmInstance instance;
+
+       g_return_val_if_fail (str != NULL, NULL);
+
+       strv = g_strsplit (str, "\n", 6);
+       if (!strv)
+               return NULL;
+
+       if (!strv[0] || !strv[1] || !strv[2] || !strv[3] || !strv[4] || !strv[5] || strv[6]) {
+               g_strfreev (strv);
+               return NULL;
+       }
+
+       component = e_cal_component_new_from_string (strv[5]);
+       if (!component) {
+               g_strfreev (strv);
+               return NULL;
+       }
+
+       instance.auid = (*(strv[1])) ? strv[1] : NULL;
+       instance.trigger = g_ascii_strtoll (strv[2], NULL, 10);
+       instance.occur_start = g_ascii_strtoll (strv[3], NULL, 10);
+       instance.occur_end = g_ascii_strtoll (strv[4], NULL, 10);
+
+       rd = e_reminder_data_new_take_component (strv[0], component, &instance);
+
+       g_strfreev (strv);
+
+       return rd;
+}
+
+static gint
+e_reminder_data_compare (gconstpointer ptr1,
+                        gconstpointer ptr2)
+{
+       const EReminderData *rd1 = ptr1, *rd2 = ptr2;
+
+       if (!rd1 || !rd2) {
+               if (rd1 == rd2)
+                       return 0;
+               return !rd1 ? -1 : 1;
+       }
+
+       if (rd1->instance.trigger == rd2->instance.trigger)
+               return 0;
+
+       return rd1->instance.trigger < rd2->instance.trigger ? -1 : 1;
+}
+
+static void
+e_reminder_watcher_free_rd_slist (gpointer ptr)
+{
+       GSList *lst = ptr;
+
+       g_slist_free_full (lst, e_reminder_data_free);
+}
+
+/* Moves those 'data' to a new GSList for which match_func() returns TRUE.
+   The passed-in slist has set the ::data members of the moved items to NULL.
+   The function reverses order of the items in the returned GSList. */
+static GSList *
+e_reminder_watcher_move_matched (GSList *slist,
+                                gboolean (* match_func) (gpointer data,
+                                                         gpointer user_data),
+                                gpointer user_data)
+{
+       GSList *res = NULL, *link;
+
+       g_return_val_if_fail (match_func != NULL, NULL);
+
+       for (link = slist; link; link = g_slist_next (link)) {
+               if (match_func (link->data, user_data)) {
+                       res = g_slist_prepend (res, link->data);
+                       link->data = NULL;
+               }
+       }
+
+       return res;
+}
+
+static gboolean
+match_not_component_id_cb (gpointer data,
+                          gpointer user_data)
+{
+       EReminderData *rd = data;
+       ECalComponentId *id = user_data, *rd_id;
+       gboolean match;
+
+       if (!rd || !id)
+               return FALSE;
+
+       rd_id = e_cal_component_get_id (rd->component);
+       match = rd_id && e_cal_component_id_equal (rd_id, id);
+       e_cal_component_free_id (rd_id);
+
+       return !match;
+}
+
+typedef struct _ObjectsChangedData {
+       ECalClient *client;
+       GSList *ids; /* ECalComponentId * */
+       time_t interval_start;
+       time_t interval_end;
+       icaltimezone *zone;
+} ObjectsChangedData;
+
+static void
+objects_changed_data_free (gpointer ptr)
+{
+       ObjectsChangedData *ocd = ptr;
+
+       if (ocd) {
+               g_clear_object (&ocd->client);
+               g_slist_free_full (ocd->ids, (GDestroyNotify) e_cal_component_free_id);
+               if (ocd->zone)
+                       e_reminder_watcher_zone_free (ocd->zone);
+               g_free (ocd);
+       }
+}
+
+static void
+e_reminder_watcher_objects_changed_thread (GTask *task,
+                                          gpointer source_object,
+                                          gpointer task_data,
+                                          GCancellable *cancellable)
+{
+       EReminderWatcher *watcher;
+       ESource *source;
+       ObjectsChangedData *ocd = task_data;
+       const gchar *source_uid;
+       GSList *link, *reminders = NULL;
+
+       g_return_if_fail (E_IS_REMINDER_WATCHER (source_object));
+       g_return_if_fail (ocd != NULL);
+
+       watcher = E_REMINDER_WATCHER (source_object);
+       source = e_client_get_source (E_CLIENT (ocd->client));
+       source_uid = e_source_get_uid (source);
+
+       for (link = ocd->ids; link && !g_cancellable_is_cancelled (cancellable); link = g_slist_next (link)) {
+               const ECalComponentId *id = link->data;
+               icalcomponent *icalcomp = NULL;
+               GError *local_error = NULL;
+
+               if (!id || !id->uid || !*id->uid)
+                       continue;
+
+               if (e_cal_client_get_object_sync (ocd->client, id->uid, id->rid, &icalcomp, cancellable, 
&local_error) && !local_error && icalcomp) {
+                       ECalComponent *ecomp;
+
+                       ecomp = e_cal_component_new_from_icalcomponent (icalcomp);
+                       if (ecomp) {
+                               ECalComponentAlarmAction omit[] = { -1 };
+                               ECalComponentAlarms *alarms;
+
+                               alarms = e_cal_util_generate_alarms_for_comp (
+                                       ecomp, ocd->interval_start, ocd->interval_end, omit, 
e_cal_client_resolve_tzid_cb,
+                                       ocd->client, ocd->zone);
+
+                               if (alarms && alarms->alarms) {
+                                       GSList *alink;
+
+                                       e_reminder_watcher_debug_print ("Source %s: Got %d alarms for object 
'%s':'%s' at interval %s .. %s\n", source_uid,
+                                               g_slist_length (alarms->alarms), id->uid, id->rid ? id->rid : 
"",
+                                               e_reminder_watcher_timet_as_string (ocd->interval_start),
+                                               e_reminder_watcher_timet_as_string (ocd->interval_end));
+
+                                       for (alink = alarms->alarms; alink; alink = g_slist_next (alink)) {
+                                               const ECalComponentAlarmInstance *instance = alink->data;
+
+                                               if (instance) {
+                                                       reminders = g_slist_prepend (reminders, 
e_reminder_data_new_take_component (
+                                                               source_uid, g_object_ref (alarms->comp), 
instance));
+                                               }
+                                       }
+                               } else {
+                                       e_reminder_watcher_debug_print ("Source %s: Got no alarms for object 
'%s':'%s' at interval %s .. %s\n", source_uid,
+                                               id->uid, id->rid ? id->rid : "",
+                                               e_reminder_watcher_timet_as_string (ocd->interval_start),
+                                               e_reminder_watcher_timet_as_string (ocd->interval_end));
+                               }
+
+                               if (alarms)
+                                       e_cal_component_alarms_free (alarms);
+
+                               g_object_unref (ecomp);
+                       }
+               } else {
+                       e_reminder_watcher_debug_print ("Source %s: Failed to get object '%s':'%s': %s\n", 
source_uid,
+                               id->uid, id->rid ? id->rid : "", local_error ? local_error->message : 
"Unknown error");
+                       g_clear_error (&local_error);
+               }
+
+       }
+
+       if (reminders && !g_cancellable_is_cancelled (cancellable)) {
+               g_rec_mutex_lock (&watcher->priv->lock);
+
+               if (watcher->priv->scheduled) {
+                       gpointer orig_key, orig_value;
+                       GSList *scheduled;
+                       gboolean needs_reverse = FALSE;
+
+                       if (g_hash_table_lookup_extended (watcher->priv->scheduled, source_uid, &orig_key, 
&orig_value)) {
+                               scheduled = orig_value;
+                               g_warn_if_fail (g_hash_table_steal (watcher->priv->scheduled, orig_key));
+                               g_free (orig_key);
+                       } else {
+                               scheduled = NULL;
+                       }
+
+                       for (link = ocd->ids; link && !g_cancellable_is_cancelled (cancellable); link = 
g_slist_next (link)) {
+                               ECalComponentId *id = link->data;
+                               GSList *new_scheduled;
+
+                               if (!id || !id->uid)
+                                       continue;
+
+                               new_scheduled = e_reminder_watcher_move_matched (scheduled, 
match_not_component_id_cb, id);
+                               g_slist_free_full (scheduled, e_reminder_data_free);
+                               scheduled = new_scheduled;
+                               needs_reverse = !needs_reverse;
+                       }
+
+                       if (reminders->next && reminders->next->next && reminders->next->next->next && 
reminders->next->next->next->next) {
+                               scheduled = g_slist_concat (reminders, scheduled);
+                               scheduled = g_slist_sort (scheduled, e_reminder_data_compare);
+                       } else {
+                               if (needs_reverse && scheduled)
+                                       scheduled = g_slist_reverse (scheduled);
+
+                               for (link = reminders; link; link = g_slist_next (link)) {
+                                       EReminderData *rd = link->data;
+
+                                       if (rd)
+                                               scheduled = g_slist_insert_sorted (scheduled, rd, 
e_reminder_data_compare);
+                               }
+
+                               /* ::data is taken by 'scheduled' */
+                               g_slist_free (reminders);
+                       }
+
+                       if (scheduled)
+                               g_hash_table_insert (watcher->priv->scheduled, g_strdup (source_uid), 
scheduled);
+               }
+
+               g_rec_mutex_unlock (&watcher->priv->lock);
+
+               e_reminder_watcher_timer_elapsed (watcher);
+       } else if (reminders) {
+               g_slist_free_full (reminders, e_reminder_data_free);
+       }
+}
+
+static void
+e_reminder_watcher_objects_changed_done_cb (GObject *source_object,
+                                           GAsyncResult *result,
+                                           gpointer user_data)
+{
+       /* Nothing to be done here */
+}
+
+static void
+e_reminder_watcher_objects_changed (EReminderWatcher *watcher,
+                                   ECalClient *client,
+                                   const GSList *objects) /* icalcomponent * */
+{
+       GSList *link, *ids = NULL;
+
+       g_return_if_fail (E_IS_REMINDER_WATCHER (watcher));
+       g_return_if_fail (E_IS_CAL_CLIENT (client));
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       if (!watcher->priv->scheduled) {
+               g_rec_mutex_unlock (&watcher->priv->lock);
+               return;
+       }
+
+       for (link = (GSList *) objects; link; link = g_slist_next (link)) {
+               icalcomponent *icalcomp = link->data;
+               ECalComponentId *id;
+               const gchar *uid;
+               gchar *rid = NULL;
+
+               uid = icalcomponent_get_uid (icalcomp);
+               if (!uid || !*uid)
+                       continue;
+
+               if (e_cal_util_component_is_instance (icalcomp)) {
+                       struct icaltimetype itt;
+
+                       itt = icalcomponent_get_recurrenceid (icalcomp);
+                       if (icaltime_is_valid_time (itt) && !icaltime_is_null_time (itt))
+                               rid = icaltime_as_ical_string_r (itt);
+                       else
+                               rid = g_strdup ("0");
+               }
+
+               id = e_cal_component_id_new (uid, rid && *rid ? rid : NULL);
+
+               g_free (rid);
+
+               if (id)
+                       ids = g_slist_prepend (ids, id);
+       }
+
+       if (ids) {
+               ObjectsChangedData *ocd;
+               GTask *task;
+
+               ocd = g_new0 (ObjectsChangedData, 1);
+               ocd->client = g_object_ref (client);
+               ocd->ids = ids;
+               ocd->interval_start = client_get_last_notification_time (client) + 1;
+               if (ocd->interval_start <= 0)
+                       ocd->interval_start = time (NULL);
+               ocd->interval_end = watcher->priv->next_midnight;
+               ocd->zone = e_reminder_watcher_dup_default_zone (watcher);
+
+               task = g_task_new (watcher, watcher->priv->cancellable, 
e_reminder_watcher_objects_changed_done_cb, NULL);
+               g_task_set_source_tag (task, e_reminder_watcher_objects_changed_thread);
+               g_task_set_task_data (task, ocd, objects_changed_data_free);
+
+               g_task_run_in_thread (task, e_reminder_watcher_objects_changed_thread);
+
+               g_object_unref (task);
+       }
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+}
+
+static void
+e_reminder_watcher_objects_removed (EReminderWatcher *watcher,
+                                   const gchar *source_uid,
+                                   const GSList *uids) /* ECalComponentId * */
+{
+       GSList *link, *scheduled;
+       gpointer orig_key = NULL, orig_value = NULL;
+       gboolean needs_reverse = FALSE;
+
+       g_return_if_fail (E_IS_REMINDER_WATCHER (watcher));
+       g_return_if_fail (source_uid != NULL);
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       if (!watcher->priv->scheduled) {
+               g_rec_mutex_unlock (&watcher->priv->lock);
+               return;
+       }
+
+       if (g_hash_table_lookup_extended (watcher->priv->scheduled, source_uid, &orig_key, &orig_value)) {
+               scheduled = orig_value;
+               g_warn_if_fail (g_hash_table_steal (watcher->priv->scheduled, orig_key));
+               g_free (orig_key);
+       } else {
+               scheduled = NULL;
+       }
+
+       for (link = (GSList *) uids; link && scheduled; link = g_slist_next (link)) {
+               ECalComponentId *id = link->data;
+               GSList *new_scheduled;
+
+               if (!id || !id->uid)
+                       continue;
+
+               new_scheduled = e_reminder_watcher_move_matched (scheduled, match_not_component_id_cb, id);
+               g_slist_free_full (scheduled, e_reminder_data_free);
+               scheduled = new_scheduled;
+               needs_reverse = !needs_reverse;
+       }
+
+       if (scheduled) {
+               if (needs_reverse)
+                       scheduled = g_slist_reverse (scheduled);
+               g_hash_table_insert (watcher->priv->scheduled, g_strdup (source_uid), scheduled);
+       }
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+}
+
+static void
+e_reminder_watcher_objects_added_cb (ECalClientView *view,
+                                    const GSList *objects, /* icalcomponent * */
+                                    gpointer user_data)
+{
+       EReminderWatcher *watcher = user_data;
+       ECalClient *client;
+       ESource *source;
+
+       g_return_if_fail (E_IS_REMINDER_WATCHER (watcher));
+
+       client = e_cal_client_view_ref_client (view);
+       source = client ? e_client_get_source (E_CLIENT (client)) : NULL;
+
+       e_reminder_watcher_debug_print ("View for %s added %d objects\n",
+               source ? e_source_get_uid (source) : "[null]",
+               g_slist_length ((GSList *) objects));
+
+       if (source)
+               e_reminder_watcher_objects_changed (watcher, client, objects);
+
+       g_clear_object (&client);
+}
+
+static void
+e_reminder_watcher_objects_modified_cb (ECalClientView *view,
+                                       const GSList *objects, /* icalcomponent * */
+                                       gpointer user_data)
+{
+       EReminderWatcher *watcher = user_data;
+       ECalClient *client;
+       ESource *source;
+
+       g_return_if_fail (E_IS_REMINDER_WATCHER (watcher));
+
+       client = e_cal_client_view_ref_client (view);
+       source = client ? e_client_get_source (E_CLIENT (client)) : NULL;
+
+       e_reminder_watcher_debug_print ("View for %s modified %d objects\n",
+               source ? e_source_get_uid (source) : "[null]",
+               g_slist_length ((GSList *) objects));
+
+       if (source)
+               e_reminder_watcher_objects_changed (watcher, client, objects);
+
+       g_clear_object (&client);
+}
+
+static void
+e_reminder_watcher_objects_removed_cb (ECalClientView *view,
+                                      const GSList *uids, /* ECalComponentId * */
+                                      gpointer user_data)
+{
+       EReminderWatcher *watcher = user_data;
+       ECalClient *client;
+       ESource *source;
+
+       g_return_if_fail (E_IS_REMINDER_WATCHER (watcher));
+
+       client = e_cal_client_view_ref_client (view);
+       source = client ? e_client_get_source (E_CLIENT (client)) : NULL;
+
+       e_reminder_watcher_debug_print ("View for %s removed %d objects\n",
+               source ? e_source_get_uid (source) : "[null]",
+               g_slist_length ((GSList *) uids));
+
+       if (source)
+               e_reminder_watcher_objects_removed (watcher, e_source_get_uid (source), uids);
+
+       g_clear_object (&client);
+}
+
+static void
+e_reminder_watcher_calc_next_midnight (EReminderWatcher *watcher)
+{
+       time_t now, midnight;
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       now = time (NULL);
+       midnight = time_day_end_with_zone (now, watcher->priv->default_zone);
+
+       while (midnight <= now) {
+               now += 60 * 60; /* increment one day */
+               midnight = time_day_end_with_zone (now, watcher->priv->default_zone);
+               e_reminder_watcher_debug_print ("Required correction of the day end, now at %s\n", 
e_reminder_watcher_timet_as_string ((gint64) midnight));
+       }
+
+       if (watcher->priv->next_midnight != midnight) {
+               GSList *link;
+
+               e_reminder_watcher_debug_print ("Next midnight at %s\n", e_reminder_watcher_timet_as_string 
((gint64) midnight));
+               watcher->priv->next_midnight = midnight;
+
+               for (link = watcher->priv->clients; link; link = g_slist_next (link)) {
+                       ClientData *cd = link->data;
+
+                       if (cd && cd->client)
+                               client_data_start_view (cd, watcher->priv->next_midnight, 
watcher->priv->cancellable);
+               }
+       }
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+}
+
+static gboolean
+e_reminder_watcher_timer_elapsed_cb (gpointer user_data)
+{
+       EReminderWatcher *watcher = user_data;
+
+       if (g_source_is_destroyed (g_main_current_source ()))
+               return FALSE;
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+       watcher->priv->timer_handler_id = 0;
+       g_rec_mutex_unlock (&watcher->priv->lock);
+
+       e_reminder_watcher_timer_elapsed (watcher);
+
+       return FALSE;
+}
+
+static void
+e_reminder_watcher_schedule_timer_impl (EReminderWatcher *watcher,
+                                       gint64 at_time)
+{
+       gint64 current_time;
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       current_time = g_get_real_time () / G_USEC_PER_SEC;
+
+       if (current_time >= at_time) {
+               e_reminder_watcher_timer_elapsed (watcher);
+       } else {
+               if (watcher->priv->timer_handler_id)
+                       g_source_remove (watcher->priv->timer_handler_id);
+
+               watcher->priv->timer_handler_id = e_named_timeout_add_seconds (at_time - current_time, 
e_reminder_watcher_timer_elapsed_cb, watcher);
+       }
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+}
+
+static GSList * /* EReminderData * */
+e_reminder_watcher_reminders_from_key (EReminderWatcher *watcher,
+                                      const gchar *key)
+{
+       GSList *list = NULL;
+
+       g_return_val_if_fail (E_IS_REMINDER_WATCHER (watcher), NULL);
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       if (watcher->priv->settings) {
+               gchar **strv;
+               gint ii;
+
+               strv = g_settings_get_strv (watcher->priv->settings, key);
+               if (strv) {
+                       for (ii = 0; strv[ii]; ii++) {
+                               EReminderData *rd;
+
+                               rd = e_reminder_data_from_string (strv[ii]);
+                               if (rd)
+                                       list = g_slist_prepend (list, rd);
+                       }
+
+                       g_strfreev (strv);
+               }
+       }
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+
+       return g_slist_reverse (list);
+}
+
+static gchar **
+e_reminder_watcher_slist_to_strv (const GSList *reminders) /* EReminderData * */
+{
+       GSList *link;
+       gint ii;
+       gchar **strv;
+
+       if (!reminders)
+               return NULL;
+
+       strv = g_new0 (gchar *, 1 + g_slist_length ((GSList *) reminders));
+
+       for (ii = 0, link = (GSList *) reminders; link; link = g_slist_next (link)) {
+               gchar *str;
+
+               str = e_reminder_data_to_string (link->data);
+               if (str) {
+                       strv[ii] = str;
+                       ii++;
+               }
+       }
+
+       strv[ii] = NULL;
+
+       return strv;
+}
+
+static EReminderData * /* one from reminders, corresponding to rd */
+e_reminder_watcher_find (GSList *reminders, /* EReminderData * */
+                        const EReminderData *rd)
+{
+       ECalComponentId *id1 = NULL;
+       EReminderData *found = NULL;
+       GSList *link;
+
+       g_return_val_if_fail (rd != NULL, NULL);
+
+       for (link = reminders; !found && link; link = g_slist_next (link)) {
+               EReminderData *rd2 = link->data;
+               ECalComponentId *id2;
+
+               if (!rd2 || g_strcmp0 (rd2->source_uid, rd->source_uid) != 0)
+                       continue;
+
+               if (!id1) {
+                       id1 = e_cal_component_get_id (rd->component);
+                       if (!id1)
+                               break;
+               }
+
+               id2 = e_cal_component_get_id (rd2->component);
+
+               if (id2) {
+                       if (g_strcmp0 (id1->uid, id2->uid) == 0 && (
+                           (g_strcmp0 (id1->rid, id2->rid) == 0 ||
+                           ((!id1->rid || !*(id1->rid)) && (!id2->rid || !*(id2->rid))))) &&
+                           g_strcmp0 (rd->instance.auid, rd2->instance.auid) == 0 &&
+                           rd->instance.trigger == rd2->instance.trigger)
+                               found = rd2;
+
+                       e_cal_component_free_id (id2);
+               }
+       }
+
+       if (id1)
+               e_cal_component_free_id (id1);
+
+       return found;
+}
+
+typedef struct _EmitSignalData {
+       EReminderWatcher *watcher;
+       guint signal_id;
+       GSList *reminders; /* EReminderData * */
+} EmitSignalData;
+
+static void
+emit_signal_data_free (gpointer ptr)
+{
+       EmitSignalData *esd = ptr;
+
+       if (esd) {
+               g_clear_object (&esd->watcher);
+               g_slist_free_full (esd->reminders, e_reminder_data_free);
+               g_free (esd);
+       }
+}
+
+static gboolean
+e_reminder_watcher_emit_signal_idle_cb (gpointer user_data)
+{
+       EmitSignalData *esd = user_data;
+
+       g_return_val_if_fail (esd != NULL, FALSE);
+       g_return_val_if_fail (E_IS_REMINDER_WATCHER (esd->watcher), FALSE);
+
+       g_signal_emit (esd->watcher, esd->signal_id, 0, esd->reminders, NULL);
+
+       return FALSE;
+}
+
+static void
+e_reminder_watcher_emit_signal_idle_multiple (EReminderWatcher *watcher,
+                                             guint signal_id,
+                                             const GSList *reminders) /* EReminderData * */
+{
+       EmitSignalData *esd;
+
+       esd = g_new0 (EmitSignalData, 1);
+       esd->watcher = g_object_ref (watcher);
+       esd->signal_id = signal_id;
+       esd->reminders = g_slist_copy_deep ((GSList *) reminders, (GCopyFunc) e_reminder_data_copy, NULL);
+
+       g_idle_add_full (G_PRIORITY_HIGH_IDLE, e_reminder_watcher_emit_signal_idle_cb, esd, 
emit_signal_data_free);
+}
+
+static void
+e_reminder_watcher_emit_signal_idle (EReminderWatcher *watcher,
+                                    guint signal_id,
+                                    const EReminderData *rd)
+{
+       GSList *reminders = NULL;
+
+       if (rd)
+               reminders = g_slist_prepend (NULL, e_reminder_data_copy (rd));
+
+       e_reminder_watcher_emit_signal_idle_multiple (watcher, signal_id, reminders);
+
+       g_slist_free_full (reminders, e_reminder_data_free);
+}
+
+static void
+e_reminder_watcher_save_list (EReminderWatcher *watcher,
+                             const gchar *key,
+                             const GSList *reminders) /* EReminderData * */
+{
+       gchar **strv;
+
+       strv = e_reminder_watcher_slist_to_strv (reminders);
+       g_settings_set_strv (watcher->priv->settings, key, (const gchar * const *) strv);
+       g_strfreev (strv);
+}
+
+static void
+e_reminder_watcher_save_past (EReminderWatcher *watcher,
+                             GSList *reminders) /* EReminderData * */
+{
+       g_rec_mutex_lock (&watcher->priv->lock);
+       watcher->priv->expected_past_changes++;
+       e_reminder_watcher_save_list (watcher, "reminders-past", reminders);
+       g_rec_mutex_unlock (&watcher->priv->lock);
+}
+
+static void
+e_reminder_watcher_save_snoozed (EReminderWatcher *watcher)
+{
+       g_rec_mutex_lock (&watcher->priv->lock);
+       watcher->priv->expected_snoozed_changes++;
+       e_reminder_watcher_save_list (watcher, "reminders-snoozed", watcher->priv->snoozed);
+       g_rec_mutex_unlock (&watcher->priv->lock);
+}
+
+static gboolean
+e_reminder_watcher_remove_from_past (EReminderWatcher *watcher,
+                                    const EReminderData *rd)
+{
+       GSList *reminders;
+       EReminderData *found;
+       gboolean changed = FALSE;
+
+       g_return_val_if_fail (rd != NULL, FALSE);
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       reminders = e_reminder_watcher_dup_past (watcher);
+       found = e_reminder_watcher_find (reminders, rd);
+       if (found) {
+               reminders = g_slist_remove (reminders, found);
+               changed = TRUE;
+
+               e_reminder_watcher_save_past (watcher, reminders);
+
+               e_reminder_watcher_emit_signal_idle (watcher, signals[REMOVED], found);
+
+               e_reminder_watcher_debug_print ("Removed reminder from past for '%s' from %s at %s\n",
+                       icalcomponent_get_summary (e_cal_component_get_icalcomponent (found->component)),
+                       found->source_uid,
+                       e_reminder_watcher_timet_as_string (found->instance.trigger));
+
+               e_reminder_data_free (found);
+       }
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+
+       g_slist_free_full (reminders, e_reminder_data_free);
+
+       return changed;
+}
+
+static gboolean
+e_reminder_watcher_remove_from_snoozed (EReminderWatcher *watcher,
+                                       const EReminderData *rd,
+                                       gboolean with_save)
+{
+       EReminderData *found;
+       gboolean changed = FALSE;
+
+       g_return_val_if_fail (rd != NULL, FALSE);
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       found = e_reminder_watcher_find (watcher->priv->snoozed, rd);
+       if (found) {
+               watcher->priv->snoozed = g_slist_remove (watcher->priv->snoozed, found);
+               changed = TRUE;
+
+               if (with_save)
+                       e_reminder_watcher_save_snoozed (watcher);
+
+               e_reminder_watcher_debug_print ("Removed reminder from snoozed for '%s' from %s at %s\n",
+                       icalcomponent_get_summary (e_cal_component_get_icalcomponent (found->component)),
+                       found->source_uid,
+                       e_reminder_watcher_timet_as_string (found->instance.trigger));
+
+               e_reminder_data_free (found);
+       }
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+
+       return changed;
+}
+
+static ECalClient *
+e_reminder_watcher_ref_client (EReminderWatcher *watcher,
+                              const gchar *source_uid)
+{
+       ECalClient *client = NULL;
+       GSList *link;
+
+       g_return_val_if_fail (source_uid != NULL, NULL);
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       for (link = watcher->priv->clients; link; link = g_slist_next (link)) {
+               const ClientData *cd = link->data;
+
+               if (!cd || !cd->client)
+                       continue;
+
+               if (g_strcmp0 (source_uid, e_source_get_uid (e_client_get_source (E_CLIENT (cd->client)))) == 
0) {
+                       client = g_object_ref (cd->client);
+                       break;
+               }
+       }
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+
+       return client;
+}
+
+static gboolean
+e_reminder_watcher_add (GSList **inout_reminders, /* EReminderData * */
+                       EReminderData *rd, /* assumes ownership of 'rd' */
+                       gboolean with_lookup,
+                       gboolean sorted)
+{
+       g_return_val_if_fail (inout_reminders != NULL, FALSE);
+
+       if (with_lookup) {
+               EReminderData *found;
+
+               found = e_reminder_watcher_find (*inout_reminders, rd);
+               if (found) {
+                       *inout_reminders = g_slist_remove (*inout_reminders, found);
+                       e_reminder_data_free (found);
+               }
+       }
+
+       if (sorted)
+               *inout_reminders = g_slist_insert_sorted (*inout_reminders, rd, e_reminder_data_compare);
+       else
+               *inout_reminders = g_slist_prepend (*inout_reminders, rd);
+
+       return TRUE;
+}
+
+static void
+e_reminder_watcher_gather_nearest_scheduled_cb (gpointer key,
+                                               gpointer value,
+                                               gpointer user_data)
+{
+       GSList *reminders = value;
+       gint *out_nearest = user_data;
+       EReminderData *rd = reminders ? reminders->data : NULL;
+
+       if (rd && out_nearest && (!*out_nearest || rd->instance.trigger < *out_nearest))
+               *out_nearest = rd->instance.trigger;
+}
+
+static gint64
+e_reminder_watcher_get_nearest_scheduled (EReminderWatcher *watcher)
+{
+       gint64 res = 0;
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       if (watcher->priv->scheduled)
+               g_hash_table_foreach (watcher->priv->scheduled, 
e_reminder_watcher_gather_nearest_scheduled_cb, &res);
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+
+       return res;
+}
+
+static void
+e_reminder_watcher_maybe_schedule_next_trigger (EReminderWatcher *watcher,
+                                               gint64 next_trigger)
+{
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       e_reminder_watcher_calc_next_midnight (watcher);
+
+       if (watcher->priv->snoozed && watcher->priv->snoozed->data) {
+               const EReminderData *rd = watcher->priv->snoozed->data;
+
+               if (next_trigger <= 0 || rd->instance.trigger < next_trigger)
+                       next_trigger = rd->instance.trigger;
+       }
+
+       if (watcher->priv->scheduled) {
+               gint64 nearest_scheduled = e_reminder_watcher_get_nearest_scheduled (watcher);
+
+               if (nearest_scheduled > 0 && (next_trigger <= 0 || nearest_scheduled < next_trigger))
+                       next_trigger = nearest_scheduled;
+       }
+
+       if (next_trigger <= 0 || watcher->priv->next_midnight < next_trigger)
+               next_trigger = watcher->priv->next_midnight;
+
+       if (watcher->priv->next_trigger != next_trigger) {
+               EReminderWatcherClass *klass;
+
+               watcher->priv->next_trigger = next_trigger;
+
+               klass = E_REMINDER_WATCHER_GET_CLASS (watcher);
+               g_warn_if_fail (klass->schedule_timer != NULL);
+
+               e_reminder_watcher_debug_print ("Going to schedule next trigger at %s\n", 
e_reminder_watcher_timet_as_string (next_trigger));
+
+               if (klass->schedule_timer)
+                       klass->schedule_timer (watcher, next_trigger);
+               else
+                       e_reminder_watcher_schedule_timer_impl (watcher, next_trigger);
+       }
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+}
+
+static void
+e_reminder_watcher_reminders_past_changed_cb (GSettings *settings,
+                                             const gchar *key,
+                                             gpointer user_data)
+{
+       EReminderWatcher *watcher = user_data;
+
+       g_return_if_fail (E_IS_REMINDER_WATCHER (watcher));
+       g_return_if_fail (key != NULL);
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       if (watcher->priv->expected_past_changes) {
+               watcher->priv->expected_past_changes--;
+               e_reminder_watcher_debug_print ("GSettings::%s possibly changed, but ignored, because it was 
expected\n", key);
+       } else {
+               e_reminder_watcher_debug_print ("GSettings::%s possibly changed\n", key);
+
+               /* Cannot determine whether it really changed, because the past reminders
+                  are not held in memory. */
+               e_reminder_watcher_emit_signal_idle (watcher, signals[CHANGED], NULL);
+       }
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+}
+
+static void
+e_reminder_watcher_reminders_snoozed_changed_cb (GSettings *settings,
+                                                const gchar *key,
+                                                gpointer user_data)
+{
+       EReminderWatcher *watcher = user_data;
+       GSList *new_snoozed, *old_snoozed, *link;
+       gboolean changed = FALSE;
+
+       g_return_if_fail (E_IS_REMINDER_WATCHER (watcher));
+       g_return_if_fail (key != NULL);
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       if (watcher->priv->expected_snoozed_changes) {
+               watcher->priv->expected_snoozed_changes--;
+               e_reminder_watcher_debug_print ("GSettings::%s possibly changed, but ignored, because it was 
expected\n", key);
+               g_rec_mutex_unlock (&watcher->priv->lock);
+
+               return;
+       }
+
+       e_reminder_watcher_debug_print ("GSettings::%s possibly changed\n", key);
+
+       new_snoozed = e_reminder_watcher_reminders_from_key (watcher, "reminders-snoozed");
+       if (new_snoozed) {
+               old_snoozed = watcher->priv->snoozed;
+               watcher->priv->snoozed = g_slist_sort (new_snoozed, e_reminder_data_compare);
+
+               for (link = old_snoozed; link; link = g_slist_next (link)) {
+                       EReminderData *rd = link->data;
+
+                       if (rd && !e_reminder_watcher_find (watcher->priv->snoozed, rd)) {
+                               changed = TRUE;
+
+                               e_reminder_watcher_debug_print ("Removed reminder from snoozed for '%s' from 
%s at %s\n",
+                                       icalcomponent_get_summary (e_cal_component_get_icalcomponent 
(rd->component)),
+                                       rd->source_uid,
+                                       e_reminder_watcher_timet_as_string (rd->instance.trigger));
+                       }
+               }
+
+               for (link = watcher->priv->snoozed; link; link = g_slist_next (link)) {
+                       EReminderData *rd = link->data;
+
+                       if (rd && !e_reminder_watcher_find (old_snoozed, rd)) {
+                               changed = TRUE;
+
+                               e_reminder_watcher_debug_print ("Added reminder to snoozed for '%s' from %s 
at %s\n",
+                                       icalcomponent_get_summary (e_cal_component_get_icalcomponent 
(rd->component)),
+                                       rd->source_uid,
+                                       e_reminder_watcher_timet_as_string (rd->instance.trigger));
+                       }
+               }
+
+               g_slist_free_full (old_snoozed, e_reminder_data_free);
+       } else if (watcher->priv->snoozed) {
+               old_snoozed = watcher->priv->snoozed;
+               watcher->priv->snoozed = NULL;
+
+               changed = TRUE;
+
+               for (link = old_snoozed; link; link = g_slist_next (link)) {
+                       EReminderData *rd = link->data;
+
+                       if (!rd)
+                               continue;
+
+                       e_reminder_watcher_debug_print ("Removed reminder from snoozed for '%s' from %s at 
%s\n",
+                               icalcomponent_get_summary (e_cal_component_get_icalcomponent (rd->component)),
+                               rd->source_uid,
+                               e_reminder_watcher_timet_as_string (rd->instance.trigger));
+               }
+
+               g_slist_free_full (old_snoozed, e_reminder_data_free);
+       }
+
+       if (changed) {
+               e_reminder_watcher_maybe_schedule_next_trigger (watcher, 0);
+               e_reminder_watcher_emit_signal_idle (watcher, signals[CHANGED], NULL);
+       }
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+}
+
+static gboolean
+e_reminder_watcher_filter_source_cb (ESourceRegistryWatcher *watcher,
+                                    ESource *source)
+{
+       g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
+
+       if (!e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR) &&
+           !e_source_has_extension (source, E_SOURCE_EXTENSION_MEMO_LIST) &&
+           !e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST))
+               return FALSE;
+
+       return !e_source_has_extension (source, E_SOURCE_EXTENSION_ALARMS) ||
+           e_source_alarms_get_include_me (e_source_get_extension (source, E_SOURCE_EXTENSION_ALARMS));
+}
+
+static void
+e_reminder_watcher_client_connect_cb (GObject *source_object,
+                                     GAsyncResult *result,
+                                     gpointer user_data)
+{
+       EReminderWatcher *watcher = user_data;
+       EClient *client;
+       ClientData *cd;
+       GError *local_error = NULL;
+
+       client = e_cal_client_connect_finish (result, &local_error);
+       if (!client) {
+               e_reminder_watcher_debug_print ("Failed to connect client: %s\n", local_error ? 
local_error->message : "Unknown error");
+               g_clear_error (&local_error);
+               return;
+       }
+
+       g_return_if_fail (E_IS_REMINDER_WATCHER (watcher));
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       cd = client_data_new (watcher, E_CAL_CLIENT (client));
+       if (cd) {
+               ESource *source = e_client_get_source (client);
+
+               e_reminder_watcher_debug_print ("Connected client: %s (%s)\n", e_source_get_uid (source), 
e_source_get_display_name (source));
+
+               watcher->priv->clients = g_slist_prepend (watcher->priv->clients, cd);
+               client_data_start_view (cd, watcher->priv->next_midnight, watcher->priv->cancellable);
+       }
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+}
+
+static void
+e_reminder_watcher_source_appeared_cb (EReminderWatcher *watcher,
+                                      ESource *source)
+{
+       ECalClientSourceType source_type;
+
+       g_return_if_fail (E_IS_REMINDER_WATCHER (watcher));
+       g_return_if_fail (E_IS_SOURCE (source));
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       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_rec_mutex_unlock (&watcher->priv->lock);
+               return;
+       }
+
+       e_cal_client_connect (source, source_type, 30, watcher->priv->cancellable, 
e_reminder_watcher_client_connect_cb, watcher);
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+}
+
+static void
+e_reminder_watcher_source_disappeared_cb (EReminderWatcher *watcher,
+                                         ESource *source)
+{
+       GSList *link;
+
+       g_return_if_fail (E_IS_REMINDER_WATCHER (watcher));
+       g_return_if_fail (E_IS_SOURCE (source));
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       for (link = watcher->priv->clients; link; link = g_slist_next (link)) {
+               ClientData *cd = link->data;
+
+               if (cd && cd->client && g_strcmp0 (e_source_get_uid (source),
+                   e_source_get_uid (e_client_get_source (E_CLIENT (cd->client)))) == 0) {
+                       e_reminder_watcher_debug_print ("Removed client: %s (%s)\n", e_source_get_uid 
(source), e_source_get_display_name (source));
+                       watcher->priv->clients = g_slist_remove (watcher->priv->clients, cd);
+                       client_data_free (cd);
+                       break;
+               }
+       }
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+}
+
+static gboolean
+e_reminder_watcher_construct_idle_cb (gpointer user_data)
+{
+       EReminderWatcher *watcher = user_data;
+
+       if (g_source_is_destroyed (g_main_current_source ()))
+               return FALSE;
+
+       g_return_val_if_fail (E_IS_REMINDER_WATCHER (watcher), FALSE);
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       watcher->priv->construct_idle_id = 0;
+       watcher->priv->registry_watcher = e_source_registry_watcher_new (watcher->priv->registry, NULL);
+
+       g_signal_connect (watcher->priv->registry_watcher, "filter",
+               G_CALLBACK (e_reminder_watcher_filter_source_cb), watcher);
+
+       g_signal_connect_swapped (watcher->priv->registry_watcher, "appeared",
+               G_CALLBACK (e_reminder_watcher_source_appeared_cb), watcher);
+
+       g_signal_connect_swapped (watcher->priv->registry_watcher, "disappeared",
+               G_CALLBACK (e_reminder_watcher_source_disappeared_cb), watcher);
+
+       e_source_registry_watcher_reclaim (watcher->priv->registry_watcher);
+
+       if (!watcher->priv->snoozed)
+               watcher->priv->snoozed = e_reminder_watcher_reminders_from_key (watcher, "reminders-snoozed");
+
+       e_reminder_watcher_maybe_schedule_next_trigger (watcher, 0);
+
+       if (!watcher->priv->past_changed_handler_id) {
+               watcher->priv->past_changed_handler_id = g_signal_connect (watcher->priv->settings, 
"changed::reminders-past",
+                       G_CALLBACK (e_reminder_watcher_reminders_past_changed_cb), watcher);
+       }
+
+       if (!watcher->priv->snoozed_changed_handler_id) {
+               watcher->priv->snoozed_changed_handler_id = g_signal_connect (watcher->priv->settings, 
"changed::reminders-snoozed",
+                       G_CALLBACK (e_reminder_watcher_reminders_snoozed_changed_cb), watcher);
+       }
+
+       /* Read from the keys, otherwise the "changed" signal won't be emitted by GSettings */
+       g_strfreev (g_settings_get_strv (watcher->priv->settings, "reminders-past"));
+       g_strfreev (g_settings_get_strv (watcher->priv->settings, "reminders-snoozed"));
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+
+       return FALSE;
+}
+
+static void
+reminder_watcher_set_registry (EReminderWatcher *watcher,
+                              ESourceRegistry *registry)
+{
+       g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+       g_return_if_fail (watcher->priv->registry == NULL);
+
+       watcher->priv->registry = g_object_ref (registry);
+}
+
+static void
+e_reminder_watcher_set_property (GObject *object,
+                                guint property_id,
+                                const GValue *value,
+                                GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_REGISTRY:
+                       reminder_watcher_set_registry (
+                               E_REMINDER_WATCHER (object),
+                               g_value_get_object (value));
+                       return;
+
+               case PROP_DEFAULT_ZONE:
+                       e_reminder_watcher_set_default_zone (
+                               E_REMINDER_WATCHER (object),
+                               g_value_get_boxed (value));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+e_reminder_watcher_get_property (GObject *object,
+                                guint property_id,
+                                GValue *value,
+                                GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_REGISTRY:
+                       g_value_set_object (
+                               value,
+                               e_reminder_watcher_get_registry (
+                               E_REMINDER_WATCHER (object)));
+                       return;
+
+               case PROP_DEFAULT_ZONE:
+                       g_value_take_boxed (
+                               value,
+                               e_reminder_watcher_dup_default_zone (
+                               E_REMINDER_WATCHER (object)));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+e_reminder_watcher_constructed (GObject *object)
+{
+       EReminderWatcher *watcher = E_REMINDER_WATCHER (object);
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (e_reminder_watcher_parent_class)->constructed (object);
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       watcher->priv->construct_idle_id = g_idle_add (e_reminder_watcher_construct_idle_cb, watcher);
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+}
+
+static void
+e_reminder_watcher_dispose (GObject *object)
+{
+       EReminderWatcher *watcher = E_REMINDER_WATCHER (object);
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       if (watcher->priv->construct_idle_id) {
+               g_source_remove (watcher->priv->construct_idle_id);
+               watcher->priv->construct_idle_id = 0;
+       }
+
+       if (watcher->priv->wall_clock_handler_id) {
+               g_source_remove (watcher->priv->wall_clock_handler_id);
+               watcher->priv->wall_clock_handler_id = 0;
+       }
+
+       if (watcher->priv->timer_handler_id) {
+               g_source_remove (watcher->priv->timer_handler_id);
+               watcher->priv->timer_handler_id = 0;
+       }
+
+       if (watcher->priv->cancellable)
+               g_cancellable_cancel (watcher->priv->cancellable);
+
+       g_slist_free_full (watcher->priv->clients, client_data_free);
+       watcher->priv->clients = NULL;
+
+       g_slist_free_full (watcher->priv->snoozed, e_reminder_data_free);
+       watcher->priv->snoozed = NULL;
+
+       if (watcher->priv->scheduled) {
+               g_hash_table_destroy (watcher->priv->scheduled);
+               watcher->priv->scheduled = NULL;
+       }
+
+       if (watcher->priv->settings && watcher->priv->past_changed_handler_id) {
+               g_signal_handler_disconnect (watcher->priv->settings, watcher->priv->past_changed_handler_id);
+               watcher->priv->past_changed_handler_id = 0;
+       }
+
+       if (watcher->priv->settings && watcher->priv->snoozed_changed_handler_id) {
+               g_signal_handler_disconnect (watcher->priv->settings, 
watcher->priv->snoozed_changed_handler_id);
+               watcher->priv->snoozed_changed_handler_id = 0;
+       }
+
+       g_clear_object (&watcher->priv->cancellable);
+       g_clear_object (&watcher->priv->settings);
+       g_clear_object (&watcher->priv->registry_watcher);
+       g_clear_object (&watcher->priv->registry);
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (e_reminder_watcher_parent_class)->dispose (object);
+}
+
+static void
+e_reminder_watcher_finalize (GObject *object)
+{
+       EReminderWatcher *watcher = E_REMINDER_WATCHER (object);
+
+       g_rec_mutex_clear (&watcher->priv->lock);
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (e_reminder_watcher_parent_class)->finalize (object);
+}
+
+static void
+e_reminder_watcher_class_init (EReminderWatcherClass *klass)
+{
+       GObjectClass *object_class;
+
+       g_type_class_add_private (klass, sizeof (EReminderWatcherPrivate));
+
+       object_class = G_OBJECT_CLASS (klass);
+       object_class->set_property = e_reminder_watcher_set_property;
+       object_class->get_property = e_reminder_watcher_get_property;
+       object_class->constructed = e_reminder_watcher_constructed;
+       object_class->dispose = e_reminder_watcher_dispose;
+       object_class->finalize = e_reminder_watcher_finalize;
+
+       klass->schedule_timer = e_reminder_watcher_schedule_timer_impl;
+
+       /**
+        * EReminderWatcher:registry:
+        *
+        * The #ESourceRegistry which manages #ESource instances.
+        *
+        * Since: 3.30
+        **/
+       g_object_class_install_property (
+               object_class,
+               PROP_REGISTRY,
+               g_param_spec_object (
+                       "registry",
+                       "Registry",
+                       "Data source registry",
+                       E_TYPE_SOURCE_REGISTRY,
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT_ONLY |
+                       G_PARAM_STATIC_STRINGS));
+
+       /**
+        * EReminderWatcher:default-zone:
+        *
+        * An icaltimezone to be used as the default time zone.
+        * It's encapsulated in a boxed type #EReminderWatcherZone.
+        *
+        * Since: 3.30
+        **/
+       g_object_class_install_property (
+               object_class,
+               PROP_DEFAULT_ZONE,
+               g_param_spec_boxed (
+                       "default-zone",
+                       "Default Zone",
+                       "The default time zone",
+                       E_TYPE_REMINDER_WATCHER_ZONE,
+                       G_PARAM_READWRITE |
+                       G_PARAM_STATIC_STRINGS));
+
+       /**
+        * EReminderWatcher::triggered:
+        * @watcher: an #EReminderWatcher
+        * @reminders: (element-type EReminderData): a #GSList of #EReminderData
+        *
+        * Signal is emitted when any reminder is either overdue or triggered.
+        *
+        * Since: 3.30
+        **/
+       signals[TRIGGERED] = g_signal_new (
+               "triggered",
+               G_OBJECT_CLASS_TYPE (klass),
+               G_SIGNAL_RUN_LAST,
+               G_STRUCT_OFFSET (EReminderWatcherClass, triggered),
+               NULL,
+               NULL,
+               g_cclosure_marshal_generic,
+               G_TYPE_NONE, 1,
+               G_TYPE_POINTER);
+
+       /**
+        * EReminderWatcher::removed:
+        * @watcher: an #EReminderWatcher
+        * @reminders: (element-type EReminderData): a #GSList of #EReminderData
+        *
+        * Signal is emitted when any reminder is removed from the past reminders.
+        * It's also followed by an EReminderWatcher::changed signal. This is used
+        * when it's known which reminders had been removed from the list of past
+        * reminders. It's not used when there's a notification from GSettings.
+        *
+        * Since: 3.30
+        **/
+       signals[REMOVED] = g_signal_new (
+               "removed",
+               G_OBJECT_CLASS_TYPE (klass),
+               G_SIGNAL_RUN_LAST,
+               G_STRUCT_OFFSET (EReminderWatcherClass, removed),
+               NULL,
+               NULL,
+               g_cclosure_marshal_generic,
+               G_TYPE_NONE, 1,
+               G_TYPE_POINTER);
+
+       /**
+        * EReminderWatcher::changed:
+        * @watcher: an #EReminderWatcher
+        *
+        * Signal is emitted when the list of past or snoozed reminders
+        * changes. It's called also when GSettings key for past reminders
+        * is notified as changed, because this list is not held in memory.
+        *
+        * Since: 3.30
+        **/
+       signals[CHANGED] = g_signal_new (
+               "changed",
+               G_OBJECT_CLASS_TYPE (klass),
+               G_SIGNAL_RUN_LAST,
+               G_STRUCT_OFFSET (EReminderWatcherClass, changed),
+               NULL,
+               NULL,
+               g_cclosure_marshal_generic,
+               G_TYPE_NONE, 0,
+               G_TYPE_NONE);
+}
+
+static void
+e_reminder_watcher_init (EReminderWatcher *watcher)
+{
+       watcher->priv = G_TYPE_INSTANCE_GET_PRIVATE (watcher, E_TYPE_REMINDER_WATCHER, 
EReminderWatcherPrivate);
+       watcher->priv->cancellable = g_cancellable_new ();
+       watcher->priv->settings = g_settings_new ("org.gnome.evolution-data-server.calendar");
+       watcher->priv->scheduled = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, 
e_reminder_watcher_free_rd_slist);
+       watcher->priv->default_zone = icaltimezone_get_utc_timezone ();
+
+       g_rec_mutex_init (&watcher->priv->lock);
+}
+
+/**
+ * e_reminder_watcher_new:
+ * @registry: (transfer none): an #ESourceRegistry
+ *
+ * Creates a new #EReminderWatcher, which will use the @registry. It adds
+ * its own reference to @registry. Free the created #EReminderWatcher
+ * with g_object_unref() when no longer needed.
+ *
+ * Returns: (transfer full): a new instance of #EReminderWatcher
+ *
+ * Since: 3.30
+ **/
+EReminderWatcher *
+e_reminder_watcher_new (ESourceRegistry *registry)
+{
+       g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+       return g_object_new (E_TYPE_REMINDER_WATCHER,
+               "registry", registry,
+               NULL);
+}
+
+/**
+ * e_reminder_watcher_get_registry:
+ * @watcher: an #EReminderWatcher
+ *
+ * Returns: (transfer none): an #ESourceRegistry with which the @watcher
+ *    had been created
+ **/
+ESourceRegistry *
+e_reminder_watcher_get_registry (EReminderWatcher *watcher)
+{
+       g_return_val_if_fail (E_IS_REMINDER_WATCHER (watcher), NULL);
+
+       return watcher->priv->registry;
+}
+
+/**
+ * e_reminder_watcher_set_default_zone:
+ * @watcher: an #EReminderWatcher
+ * @zone: (nullable): an icaltimezone or #EReminderWatcherZone structure
+ *
+ * Sets the default zone for the @watcher. This is used when calculating
+ * trigger times for floating component times. When the @zone is %NULL,
+ * then sets a UTC time zone.
+ *
+ * Since: 3.30
+ **/
+void
+e_reminder_watcher_set_default_zone (EReminderWatcher *watcher,
+                                    const icaltimezone *zone)
+{
+       const gchar *new_location;
+
+       g_return_if_fail (E_IS_REMINDER_WATCHER (watcher));
+
+       if (!zone)
+               zone = icaltimezone_get_utc_timezone ();
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       new_location = icaltimezone_get_location ((icaltimezone *) zone);
+
+       if (new_location && g_strcmp0 (new_location,
+           icaltimezone_get_location (watcher->priv->default_zone)) == 0) {
+               g_rec_mutex_unlock (&watcher->priv->lock);
+               return;
+       }
+
+       e_reminder_watcher_zone_free ((EReminderWatcherZone *) watcher->priv->default_zone);
+       watcher->priv->default_zone = (icaltimezone *) e_reminder_watcher_zone_copy ((const 
EReminderWatcherZone *) zone);
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+
+       g_object_notify (G_OBJECT (watcher), "default-zone");
+}
+
+/**
+ * e_reminder_watcher_dup_default_zone:
+ * @watcher: an #EReminderWatcher
+ *
+ * Returns: (transfer full): A copy of the currently set default time zone.
+ *    Use e_reminder_watcher_zone_free() to free it, when no longer needed.
+ *
+ * Since: 3.30
+ **/
+icaltimezone *
+e_reminder_watcher_dup_default_zone (EReminderWatcher *watcher)
+{
+       icaltimezone *zone;
+
+       g_return_val_if_fail (E_IS_REMINDER_WATCHER (watcher), NULL);
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       zone = (icaltimezone *) e_reminder_watcher_zone_copy ((EReminderWatcherZone *) 
watcher->priv->default_zone);
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+
+       return zone;
+}
+
+typedef struct _ForeachTriggerData {
+       gint64 current_time;
+       GSList *triggered; /* EReminderData * */
+       GHashTable *insert_back; /* gchar * ~> GSList * { EReminderData * } */
+} ForeachTriggerData;
+
+static gboolean
+foreach_trigger_cb (gpointer key,
+                   gpointer value,
+                   gpointer user_data)
+{
+       gchar *source_uid = key;
+       GSList *reminders = value, *link;
+       ForeachTriggerData *ftd = user_data;
+       EReminderData *rd;
+
+       if (!source_uid || !reminders || !ftd)
+               return FALSE;
+
+       for (link = reminders; link; link = g_slist_next (link)) {
+               rd = link->data;
+
+               if (!rd || rd->instance.trigger > ftd->current_time)
+                       break;
+       }
+
+       if (link == reminders)
+               return FALSE;
+
+       if (link) {
+               GSList *prev;
+
+               for (prev = reminders; prev; prev = g_slist_next (prev)) {
+                       if (prev->next == link) {
+                               prev->next = NULL;
+                               break;
+                       }
+               }
+       }
+
+       ftd->triggered = g_slist_concat (ftd->triggered, reminders);
+
+       if (link) {
+               g_hash_table_insert (ftd->insert_back, source_uid, link);
+       } else {
+               g_free (source_uid);
+       }
+
+       return TRUE;
+}
+
+/**
+ * e_reminder_watcher_timer_elapsed:
+ * @watcher: an #EReminderWatcher
+ *
+ * Notifies the #watcher that the timer previously scheduled
+ * with EReminderWatcherClass::schedule_timer elapsed. This can
+ * be used by the descendants which override the default implementation
+ * of EReminderWatcherClass::schedule_timer. There is always scheduled
+ * only one timer and once it's elapsed it should be also removed,
+ * the same when the EReminderWatcherClass::schedule_timer is called
+ * and the previously scheduled timer was not elapsed yet, the previous
+ * should be removed first, aka every call to EReminderWatcherClass::schedule_timer
+ * replaces any previously scheduled timer.
+ *
+ * Since: 3.30
+ **/
+void
+e_reminder_watcher_timer_elapsed (EReminderWatcher *watcher)
+{
+       ForeachTriggerData ftd;
+       GSList *snoozed, *link;
+       gboolean changed = FALSE;
+
+       g_return_if_fail (E_IS_REMINDER_WATCHER (watcher));
+
+       ftd.current_time = g_get_real_time () / G_USEC_PER_SEC;
+
+       e_reminder_watcher_debug_print ("Timer elapsed called at %s\n", e_reminder_watcher_timet_as_string 
(ftd.current_time));
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       if (!watcher->priv->scheduled) {
+               g_rec_mutex_unlock (&watcher->priv->lock);
+               return;
+       }
+
+       ftd.triggered = NULL;
+       ftd.insert_back = g_hash_table_new (g_str_hash, g_str_equal);
+
+       g_hash_table_foreach_steal (watcher->priv->scheduled, foreach_trigger_cb, &ftd);
+
+       if (g_hash_table_size (ftd.insert_back) > 0) {
+               GHashTableIter iter;
+               gpointer key, value;
+
+               g_hash_table_iter_init (&iter, ftd.insert_back);
+               while (g_hash_table_iter_next (&iter, &key, &value)) {
+                       g_warn_if_fail (key != NULL);
+                       g_warn_if_fail (value != NULL);
+
+                       if (key && value)
+                               g_hash_table_insert (watcher->priv->scheduled, key, value);
+               }
+       }
+
+       g_hash_table_destroy (ftd.insert_back);
+
+       snoozed = e_reminder_watcher_dup_snoozed (watcher);
+
+       for (link = snoozed; link; link = g_slist_next (link)) {
+               EReminderData *rd = link->data;
+
+               if (rd && rd->instance.trigger <= ftd.current_time) {
+                       link->data = NULL;
+
+                       changed = e_reminder_watcher_remove_from_snoozed (watcher, rd, FALSE) || changed;
+
+                       ftd.triggered = g_slist_prepend (ftd.triggered, rd);
+               }
+       }
+
+       g_slist_free_full (snoozed, e_reminder_data_free);
+
+       if (ftd.triggered) {
+               GHashTable *last_notifies;
+               GHashTableIter iter;
+               GSList *past;
+               gpointer key, value;
+
+               last_notifies = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
+
+               past = e_reminder_watcher_dup_past (watcher);
+
+               for (link = ftd.triggered; link; link = g_slist_next (link)) {
+                       EReminderData *rd = e_reminder_data_copy (link->data);
+
+                       if (rd) {
+                               if (e_reminder_watcher_add (&past, rd, TRUE, FALSE)) {
+                                       time_t *ptrigger;
+
+                                       ptrigger = g_hash_table_lookup (last_notifies, rd->source_uid);
+                                       if (ptrigger) {
+                                               if (*ptrigger < rd->instance.trigger)
+                                                       *ptrigger = rd->instance.trigger;
+                                       } else {
+                                               ptrigger = g_new0 (time_t, 1);
+                                               *ptrigger = rd->instance.trigger;
+                                               g_hash_table_insert (last_notifies, rd->source_uid, ptrigger);
+                                       }
+                               }
+                       }
+               }
+
+               e_reminder_watcher_save_past (watcher, past);
+
+               g_hash_table_iter_init (&iter, last_notifies);
+               while (g_hash_table_iter_next (&iter, &key, &value)) {
+                       const gchar *source_uid = key;
+                       const time_t *ptrigger = value;
+
+                       if (source_uid && ptrigger) {
+                               ECalClient *client = e_reminder_watcher_ref_client (watcher, source_uid);
+
+                               if (client) {
+                                       client_set_last_notification_time (client, *ptrigger);
+                                       g_object_unref (client);
+                               }
+                       }
+               }
+
+               /* Destroy before the 'past', because keys are from its data */
+               g_hash_table_destroy (last_notifies);
+               g_slist_free_full (past, e_reminder_data_free);
+       }
+
+       if (changed)
+               e_reminder_watcher_save_snoozed (watcher);
+
+       if (ftd.triggered) {
+               e_reminder_watcher_emit_signal_idle_multiple (watcher, signals[TRIGGERED], ftd.triggered);
+               e_reminder_watcher_emit_signal_idle (watcher, signals[CHANGED], NULL);
+
+               g_slist_free_full (ftd.triggered, e_reminder_data_free);
+       }
+
+       /* To make sure the timer is re-scheduled */
+       watcher->priv->next_trigger = 0;
+
+       e_reminder_watcher_maybe_schedule_next_trigger (watcher, 0);
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+}
+
+/**
+ * e_reminder_watcher_dup_past:
+ * @watcher: an #EReminderWatcher
+ *
+ * Gathers a #GSList of all past reminders which had not been removed after
+ * EReminderWatcher::triggered signal. Such reminders are remembered
+ * across sessions, until they are dismissed by e_reminder_watcher_dismiss()
+ * or its synchronous variant. These reminders can be also snoozed
+ * with e_reminder_watcher_snooze(), which removes them from the past
+ * reminders into the list of snoozed reminders, see e_reminder_watcher_dup_snoozed().
+ *
+ * Free the returned #GSList with
+ * g_slist_free_full (reminders, e_reminder_data_free);
+ * when no longer needed.
+ *
+ * Returns: (transfer full) (element-type EReminderData) (nullable): a newly
+ *    allocated #GSList of the past reminders, or %NULL, when there are none
+ *
+ * Since: 3.30
+ **/
+GSList *
+e_reminder_watcher_dup_past (EReminderWatcher *watcher)
+{
+       g_return_val_if_fail (E_IS_REMINDER_WATCHER (watcher), NULL);
+
+       return e_reminder_watcher_reminders_from_key (watcher, "reminders-past");
+}
+
+/**
+ * e_reminder_watcher_dup_snoozed:
+ * @watcher: an #EReminderWatcher
+ *
+ * Gathers a #GSList of currently snoozed reminder with e_reminder_watcher_snooze().
+ * The snoozed reminders are remembered across sessions and they are re-triggered
+ * when their snooze time elapses, which can move them back to the list of past reminders.
+ *
+ * Free the returned #GSList with
+ * g_slist_free_full (reminders, e_reminder_data_free);
+ * when no longer needed.
+ *
+ * Returns: (transfer full) (element-type EReminderData) (nullable): a newly
+ *    allocated #GSList of the snoozed reminders, or %NULL, when there are none
+ *
+ * Since: 3.30
+ **/
+GSList *
+e_reminder_watcher_dup_snoozed (EReminderWatcher *watcher)
+{
+       GSList *list;
+
+       g_return_val_if_fail (E_IS_REMINDER_WATCHER (watcher), NULL);
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       list = g_slist_copy_deep (watcher->priv->snoozed, (GCopyFunc) e_reminder_data_copy, NULL);
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+
+       return list;
+}
+
+/**
+ * e_reminder_watcher_snooze:
+ * @watcher: an #EReminderWatcher
+ * @rd: an #EReminderData identifying the reminder
+ * @until: time_t as gint64, when the @rd should be retriggered
+ *
+ * Snoozes @rd until @until, which is an absolute time when the @rd
+ * should be retriggered. This moves the @rd from the list of past
+ * reminders into the list of snoozed reminders and invokes the "removed"
+ * signal when the @rd was in the past reminders. It also invokes
+ * the "changed" signal.
+ *
+ * Since: 3.30
+ **/
+void
+e_reminder_watcher_snooze (EReminderWatcher *watcher,
+                          const EReminderData *rd,
+                          gint64 until)
+{
+       EReminderData *rd_copy;
+       gboolean changed;
+
+       g_return_if_fail (E_IS_REMINDER_WATCHER (watcher));
+       g_return_if_fail (rd != NULL);
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       rd_copy = e_reminder_data_copy (rd);
+       if (!rd_copy) {
+               g_rec_mutex_unlock (&watcher->priv->lock);
+               g_warn_if_reached ();
+               return;
+       }
+
+       changed = e_reminder_watcher_remove_from_past (watcher, rd_copy);
+       changed = e_reminder_watcher_remove_from_snoozed (watcher, rd_copy, FALSE) || changed;
+
+       rd_copy->instance.trigger = (time_t) until;
+
+       changed = e_reminder_watcher_add (&watcher->priv->snoozed, rd_copy, FALSE, TRUE) || changed;
+
+       e_reminder_watcher_debug_print ("Added reminder to snoozed for '%s' from %s at %s\n",
+               icalcomponent_get_summary (e_cal_component_get_icalcomponent (rd_copy->component)),
+               rd_copy->source_uid,
+               e_reminder_watcher_timet_as_string (rd_copy->instance.trigger));
+
+       e_reminder_watcher_save_snoozed (watcher);
+       e_reminder_watcher_maybe_schedule_next_trigger (watcher, until);
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+
+       if (changed)
+               e_reminder_watcher_emit_signal_idle (watcher, signals[CHANGED], NULL);
+}
+
+static void
+e_reminder_watcher_dismiss_thread (GTask *task,
+                                  gpointer source_object,
+                                  gpointer task_data,
+                                  GCancellable *cancellable)
+{
+       GError *local_error = NULL;
+
+       if (!e_reminder_watcher_dismiss_sync (E_REMINDER_WATCHER (source_object), task_data, cancellable, 
&local_error)) {
+               if (local_error)
+                       g_task_return_error (task, local_error);
+               else
+                       g_task_return_boolean (task, FALSE);
+       } else {
+               g_task_return_boolean (task, TRUE);
+       }
+}
+
+/**
+ * e_reminder_watcher_dismiss:
+ * @watcher: an #EReminderWatcher
+ * @rd: an #EReminderData to dismiss
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously dismiss single reminder in the past or snoozed reminders.
+ *
+ * When the operation is finished, @callback will be called. You can
+ * then call e_reminder_watcher_dismiss_finish() to get the result of
+ * the operation.
+ *
+ * Since: 3.30
+ **/
+void
+e_reminder_watcher_dismiss (EReminderWatcher *watcher,
+                           const EReminderData *rd,
+                           GCancellable *cancellable,
+                           GAsyncReadyCallback callback,
+                           gpointer user_data)
+{
+       GTask *task;
+
+       g_return_if_fail (E_IS_REMINDER_WATCHER (watcher));
+       g_return_if_fail (rd != NULL);
+
+       task = g_task_new (watcher, cancellable, callback, user_data);
+       g_task_set_source_tag (task, e_reminder_watcher_dismiss);
+       g_task_set_task_data (task, e_reminder_data_copy (rd), e_reminder_data_free);
+
+       g_task_run_in_thread (task, e_reminder_watcher_dismiss_thread);
+
+       g_object_unref (task);
+}
+
+/**
+ * e_reminder_watcher_dismiss_finish:
+ * @watcher: an #EReminderWatcher
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with e_reminder_watcher_dismiss().
+ *
+ * Returns: whether succeeded
+ *
+ * Since: 3.30
+ **/
+gboolean
+e_reminder_watcher_dismiss_finish (EReminderWatcher *watcher,
+                                  GAsyncResult *result,
+                                  GError **error)
+{
+       g_return_val_if_fail (E_IS_REMINDER_WATCHER (watcher), FALSE);
+       g_return_val_if_fail (g_task_is_valid (result, watcher), FALSE);
+
+       g_return_val_if_fail (
+               g_async_result_is_tagged (
+               result, e_reminder_watcher_dismiss), FALSE);
+
+       return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static gboolean
+e_reminder_watcher_dismiss_one_sync (ECalClient *client,
+                                    const EReminderData *rd,
+                                    GCancellable *cancellable,
+                                    GError **error)
+{
+       ECalComponentId *id;
+       gboolean success = TRUE;
+
+       g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
+       g_return_val_if_fail (rd != NULL, FALSE);
+
+       id = e_cal_component_get_id (rd->component);
+       if (id) {
+               GError *local_error = NULL;
+
+               success = e_cal_client_discard_alarm_sync (client, id->uid, id->rid, rd->instance.auid, 
cancellable, &local_error);
+
+               e_reminder_watcher_debug_print ("Discard alarm for '%s' from %s %s%s%s%s\n",
+                       icalcomponent_get_summary (e_cal_component_get_icalcomponent (rd->component)),
+                       rd->source_uid,
+                       success ? "succeeded" : "failed",
+                       (!success || local_error) ? " (" : "",
+                       local_error ? local_error->message : success ? "" : "Unknown error",
+                       (!success || local_error) ? ")" : "");
+
+               if (g_error_matches (local_error, E_CLIENT_ERROR, E_CLIENT_ERROR_NOT_SUPPORTED) ||
+                   g_error_matches (local_error, E_CAL_CLIENT_ERROR, E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND)) {
+                       success = TRUE;
+                       g_clear_error (&local_error);
+               } else if (local_error) {
+                       g_propagate_error (error, local_error);
+               }
+
+               e_cal_component_free_id (id);
+       }
+
+       return success;
+}
+
+/**
+ * e_reminder_watcher_dismiss_sync:
+ * @watcher: an #EReminderWatcher
+ * @rd: an #EReminderData to dismiss
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Synchronously dismiss single reminder in the past or snoozed reminders.
+ *
+ * Returns: whether succeeded
+ *
+ * Since: 3.30
+ **/
+gboolean
+e_reminder_watcher_dismiss_sync (EReminderWatcher *watcher,
+                                const EReminderData *rd,
+                                GCancellable *cancellable,
+                                GError **error)
+{
+       EReminderData *rd_copy;
+       ECalClient *client = NULL;
+       gboolean changed, success = TRUE;
+
+       g_return_val_if_fail (E_IS_REMINDER_WATCHER (watcher), FALSE);
+       g_return_val_if_fail (rd != NULL, FALSE);
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       rd_copy = e_reminder_data_copy (rd);
+       if (!rd_copy) {
+               g_rec_mutex_unlock (&watcher->priv->lock);
+               g_warn_if_reached ();
+               return FALSE;
+       }
+
+       changed = e_reminder_watcher_remove_from_past (watcher, rd_copy);
+       changed = e_reminder_watcher_remove_from_snoozed (watcher, rd_copy, TRUE) || changed;
+
+       if (changed)
+               client = e_reminder_watcher_ref_client (watcher, rd_copy->source_uid);
+
+       e_reminder_watcher_maybe_schedule_next_trigger (watcher, 0);
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+
+       if (changed)
+               e_reminder_watcher_emit_signal_idle (watcher, signals[CHANGED], NULL);
+
+       if (client) {
+               success = e_reminder_watcher_dismiss_one_sync (client, rd, cancellable, error);
+               g_object_unref (client);
+       }
+
+       e_reminder_data_free (rd_copy);
+
+       return success;
+}
+
+static void
+e_reminder_watcher_dismiss_all_thread (GTask *task,
+                                      gpointer source_object,
+                                      gpointer task_data,
+                                      GCancellable *cancellable)
+{
+       GError *local_error = NULL;
+
+       if (!e_reminder_watcher_dismiss_all_sync (E_REMINDER_WATCHER (source_object), cancellable, 
&local_error)) {
+               if (local_error)
+                       g_task_return_error (task, local_error);
+               else
+                       g_task_return_boolean (task, FALSE);
+       } else {
+               g_task_return_boolean (task, TRUE);
+       }
+}
+
+/**
+ * e_reminder_watcher_dismiss_all:
+ * @watcher: an #EReminderWatcher
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously dismiss all past reminders.
+ *
+ * When the operation is finished, @callback will be called. You can
+ * then call e_reminder_watcher_dismiss_all_finish() to get the result
+ * of the operation.
+ *
+ * Since: 3.30
+ **/
+void
+e_reminder_watcher_dismiss_all (EReminderWatcher *watcher,
+                               GCancellable *cancellable,
+                               GAsyncReadyCallback callback,
+                               gpointer user_data)
+{
+       GTask *task;
+
+       g_return_if_fail (E_IS_REMINDER_WATCHER (watcher));
+
+       task = g_task_new (watcher, cancellable, callback, user_data);
+       g_task_set_source_tag (task, e_reminder_watcher_dismiss_all);
+
+       g_task_run_in_thread (task, e_reminder_watcher_dismiss_all_thread);
+
+       g_object_unref (task);
+}
+
+/**
+ * e_reminder_watcher_dismiss_all_finish:
+ * @watcher: an #EReminderWatcher
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with e_reminder_watcher_dismiss_all().
+ *
+ * Returns: whether succeeded
+ *
+ * Since: 3.30
+ **/
+gboolean
+e_reminder_watcher_dismiss_all_finish (EReminderWatcher *watcher,
+                                      GAsyncResult *result,
+                                      GError **error)
+{
+       g_return_val_if_fail (E_IS_REMINDER_WATCHER (watcher), FALSE);
+       g_return_val_if_fail (g_task_is_valid (result, watcher), FALSE);
+
+       g_return_val_if_fail (
+               g_async_result_is_tagged (
+               result, e_reminder_watcher_dismiss_all), FALSE);
+
+       return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * e_reminder_watcher_dismiss_all_sync:
+ * @watcher: an #EReminderWatcher
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Synchronously dismiss all past reminders. The operation stops after
+ * the first error is encountered, which can be before all the past
+ * reminders are dismissed.
+ *
+ * Returns: whether succeeded.
+ *
+ * Since: 3.30
+ **/
+gboolean
+e_reminder_watcher_dismiss_all_sync (EReminderWatcher *watcher,
+                                    GCancellable *cancellable,
+                                    GError **error)
+{
+       GSList *reminders, *link, *dismissed = NULL;
+       gboolean success = TRUE, changed = FALSE;
+
+       g_return_val_if_fail (E_IS_REMINDER_WATCHER (watcher), FALSE);
+
+       g_rec_mutex_lock (&watcher->priv->lock);
+
+       reminders = e_reminder_watcher_dup_past (watcher);
+
+       for (link = reminders; link; link = g_slist_next (link)) {
+               EReminderData *rd = link->data;
+               ECalClient *client;
+
+               client = e_reminder_watcher_ref_client (watcher, rd->source_uid);
+               if (client) {
+                       success = e_reminder_watcher_dismiss_one_sync (client, rd, cancellable, error);
+                       g_object_unref (client);
+
+                       /* To keep the failed discard in the saved list. */
+                       if (!success)
+                               break;
+               }
+
+               dismissed = g_slist_prepend (dismissed, rd);
+               link->data = NULL;
+       }
+
+       if (dismissed) {
+               e_reminder_watcher_emit_signal_idle_multiple (watcher, signals[REMOVED], dismissed);
+               g_slist_free_full (dismissed, e_reminder_data_free);
+       }
+
+       if (link != reminders && reminders) {
+               e_reminder_watcher_save_past (watcher, link);
+               changed = TRUE;
+       }
+
+       g_slist_free_full (reminders, e_reminder_data_free);
+
+       g_rec_mutex_unlock (&watcher->priv->lock);
+
+       if (changed)
+               e_reminder_watcher_emit_signal_idle (watcher, signals[CHANGED], NULL);
+
+       return success;
+}
diff --git a/src/calendar/libecal/e-reminder-watcher.h b/src/calendar/libecal/e-reminder-watcher.h
new file mode 100644
index 0000000..96a830e
--- /dev/null
+++ b/src/calendar/libecal/e-reminder-watcher.h
@@ -0,0 +1,159 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2018 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined (__LIBECAL_H_INSIDE__) && !defined (LIBECAL_COMPILATION)
+#error "Only <libecal/libecal.h> should be included directly."
+#endif
+
+#ifndef E_REMINDER_WATCHER_H
+#define E_REMINDER_WATCHER_H
+
+#include <libedataserver/libedataserver.h>
+
+/* Standard GObject macros */
+#define E_TYPE_REMINDER_WATCHER \
+       (e_reminder_watcher_get_type ())
+#define E_REMINDER_WATCHER(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), E_TYPE_REMINDER_WATCHER, EReminderWatcher))
+#define E_REMINDER_WATCHER_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_CAST \
+       ((cls), E_TYPE_REMINDER_WATCHER, EReminderWatcherClass))
+#define E_IS_REMINDER_WATCHER(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), E_TYPE_REMINDER_WATCHER))
+#define E_IS_REMINDER_WATCHER_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_TYPE \
+       ((cls), E_TYPE_REMINDER_WATCHER))
+#define E_REMINDER_WATCHER_GET_CLASS(obj) \
+       (G_TYPE_INSTANCE_GET_CLASS \
+       ((obj), E_TYPE_REMINDER_WATCHER, EReminderWatcherClass))
+
+#define E_TYPE_REMINDER_DATA (e_reminder_data_get_type ())
+#define E_TYPE_REMINDER_WATCHER_ZONE (e_reminder_watcher_zone_get_type ())
+
+G_BEGIN_DECLS
+
+/**
+ * EReminderData:
+ *
+ * Contains data related to single reminder occurrence.
+ *
+ * Since: 3.30
+ **/
+typedef struct _EReminderData {
+       gchar *source_uid;
+       ECalComponent *component;
+       ECalComponentAlarmInstance instance;
+} EReminderData;
+
+GType          e_reminder_data_get_type        (void) G_GNUC_CONST;
+EReminderData *        e_reminder_data_new             (const gchar *source_uid,
+                                                const ECalComponent *component,
+                                                const ECalComponentAlarmInstance *instance);
+EReminderData *        e_reminder_data_copy            (const EReminderData *rd);
+void           e_reminder_data_free            (gpointer rd); /* EReminderData * */
+
+/**
+ * EReminderWatcherZone:
+ *
+ * A libical's icaltimezone encapsulated as a GByxed type.
+ * It can be retyped into icaltimezone directly.
+ *
+ * Since: 3.30
+ **/
+typedef icaltimezone EReminderWatcherZone;
+
+GType          e_reminder_watcher_zone_get_type(void) G_GNUC_CONST;
+EReminderWatcherZone *
+               e_reminder_watcher_zone_copy    (const EReminderWatcherZone *watcher_zone);
+void           e_reminder_watcher_zone_free    (EReminderWatcherZone *watcher_zone);
+
+typedef struct _EReminderWatcher EReminderWatcher;
+typedef struct _EReminderWatcherClass EReminderWatcherClass;
+typedef struct _EReminderWatcherPrivate EReminderWatcherPrivate;
+
+/**
+ * EReminderWatcher:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.30
+ **/
+struct _EReminderWatcher {
+       /*< private >*/
+       GObject parent;
+       EReminderWatcherPrivate *priv;
+};
+
+struct _EReminderWatcherClass {
+       GObjectClass parent_class;
+
+       /* Virtual methods and signals */
+       void            (* schedule_timer)      (EReminderWatcher *watcher,
+                                                gint64 at_time);
+       void            (* triggered)           (EReminderWatcher *watcher,
+                                                const GSList *reminders); /* EReminderData * */
+       void            (* removed)             (EReminderWatcher *watcher,
+                                                const GSList *reminders); /* EReminderData * */
+       void            (* changed)             (EReminderWatcher *watcher);
+
+       /* Padding for future expansion */
+       gpointer reserved[10];
+};
+
+GType          e_reminder_watcher_get_type             (void) G_GNUC_CONST;
+EReminderWatcher *
+               e_reminder_watcher_new                  (ESourceRegistry *registry);
+ESourceRegistry *
+               e_reminder_watcher_get_registry         (EReminderWatcher *watcher);
+void           e_reminder_watcher_set_default_zone     (EReminderWatcher *watcher,
+                                                        const icaltimezone *zone);
+icaltimezone * e_reminder_watcher_dup_default_zone     (EReminderWatcher *watcher);
+void           e_reminder_watcher_timer_elapsed        (EReminderWatcher *watcher);
+GSList *       e_reminder_watcher_dup_past             (EReminderWatcher *watcher); /* EReminderData * */
+GSList *       e_reminder_watcher_dup_snoozed          (EReminderWatcher *watcher); /* EReminderData * */
+void           e_reminder_watcher_snooze               (EReminderWatcher *watcher,
+                                                        const EReminderData *rd,
+                                                        gint64 until);
+void           e_reminder_watcher_dismiss              (EReminderWatcher *watcher,
+                                                        const EReminderData *rd,
+                                                        GCancellable *cancellable,
+                                                        GAsyncReadyCallback callback,
+                                                        gpointer user_data);
+gboolean       e_reminder_watcher_dismiss_finish       (EReminderWatcher *watcher,
+                                                        GAsyncResult *result,
+                                                        GError **error);
+gboolean       e_reminder_watcher_dismiss_sync         (EReminderWatcher *watcher,
+                                                        const EReminderData *rd,
+                                                        GCancellable *cancellable,
+                                                        GError **error);
+void           e_reminder_watcher_dismiss_all          (EReminderWatcher *watcher,
+                                                        GCancellable *cancellable,
+                                                        GAsyncReadyCallback callback,
+                                                        gpointer user_data);
+gboolean       e_reminder_watcher_dismiss_all_finish   (EReminderWatcher *watcher,
+                                                        GAsyncResult *result,
+                                                        GError **error);
+gboolean       e_reminder_watcher_dismiss_all_sync     (EReminderWatcher *watcher,
+                                                        GCancellable *cancellable,
+                                                        GError **error);
+
+G_END_DECLS
+
+#endif /* E_REMINDER_WATCHER_H */
diff --git a/src/calendar/libecal/libecal.h b/src/calendar/libecal/libecal.h
index f36ff3c..e14eb9d 100644
--- a/src/calendar/libecal/libecal.h
+++ b/src/calendar/libecal/libecal.h
@@ -34,6 +34,7 @@
 #include <libecal/e-cal-types.h>
 #include <libecal/e-cal-util.h>
 #include <libecal/e-cal-view.h>
+#include <libecal/e-reminder-watcher.h>
 #include <libecal/e-timezone-cache.h>
 
 #undef __LIBECAL_H_INSIDE__



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]