[evolution-data-server] I#306 - Camel: Listen for change notifications on spool account



commit 190f7b78f1ddd797d9ca451c2eeb775b445099a9
Author: Milan Crha <mcrha redhat com>
Date:   Fri Mar 19 09:30:09 2021 +0100

    I#306 - Camel: Listen for change notifications on spool account
    
    Closes https://gitlab.gnome.org/GNOME/evolution-data-server/-/issues/306

 src/camel/providers/local/camel-local-provider.c |   2 +
 src/camel/providers/local/camel-spool-settings.c |  52 +++-
 src/camel/providers/local/camel-spool-settings.h |   5 +
 src/camel/providers/local/camel-spool-store.c    | 298 +++++++++++++++++++++++
 4 files changed, 356 insertions(+), 1 deletion(-)
---
diff --git a/src/camel/providers/local/camel-local-provider.c 
b/src/camel/providers/local/camel-local-provider.c
index 0bc6c6cf6..c4fea527e 100644
--- a/src/camel/providers/local/camel-local-provider.c
+++ b/src/camel/providers/local/camel-local-provider.c
@@ -106,6 +106,8 @@ static CamelProvider maildir_provider = {
 
 static CamelProviderConfEntry spool_conf_entries[] = {
        { CAMEL_PROVIDER_CONF_SECTION_START, "general", NULL, N_("Options") },
+       { CAMEL_PROVIDER_CONF_CHECKBOX, "listen-notifications", NULL,
+         N_("_Listen for change notifications"), "1" },
        { CAMEL_PROVIDER_CONF_CHECKBOX, "filter-inbox", NULL,
          N_("_Apply filters to new messages in Inbox"), "0" },
        { CAMEL_PROVIDER_CONF_CHECKBOX, "filter-junk", NULL,
diff --git a/src/camel/providers/local/camel-spool-settings.c 
b/src/camel/providers/local/camel-spool-settings.c
index ad0504f04..c931db898 100644
--- a/src/camel/providers/local/camel-spool-settings.c
+++ b/src/camel/providers/local/camel-spool-settings.c
@@ -19,11 +19,13 @@
 
 struct _CamelSpoolSettingsPrivate {
        gboolean use_xstatus_headers;
+       gboolean listen_notifications;
 };
 
 enum {
        PROP_0,
-       PROP_USE_XSTATUS_HEADERS
+       PROP_USE_XSTATUS_HEADERS,
+       PROP_LISTEN_NOTIFICATIONS
 };
 
 G_DEFINE_TYPE_WITH_PRIVATE (
@@ -43,6 +45,12 @@ spool_settings_set_property (GObject *object,
                                CAMEL_SPOOL_SETTINGS (object),
                                g_value_get_boolean (value));
                        return;
+
+               case PROP_LISTEN_NOTIFICATIONS:
+                       camel_spool_settings_set_listen_notifications (
+                               CAMEL_SPOOL_SETTINGS (object),
+                               g_value_get_boolean (value));
+                       return;
        }
 
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -61,6 +69,13 @@ spool_settings_get_property (GObject *object,
                                camel_spool_settings_get_use_xstatus_headers (
                                CAMEL_SPOOL_SETTINGS (object)));
                        return;
+
+               case PROP_LISTEN_NOTIFICATIONS:
+                       g_value_set_boolean (
+                               value,
+                               camel_spool_settings_get_listen_notifications (
+                               CAMEL_SPOOL_SETTINGS (object)));
+                       return;
        }
 
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -87,6 +102,19 @@ camel_spool_settings_class_init (CamelSpoolSettingsClass *class)
                        G_PARAM_CONSTRUCT |
                        G_PARAM_EXPLICIT_NOTIFY |
                        G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (
+               object_class,
+               PROP_LISTEN_NOTIFICATIONS,
+               g_param_spec_boolean (
+                       "listen-notifications",
+                       "Listen Notifications",
+                       "Whether to listen for file/directory change notifications",
+                       TRUE,
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT |
+                       G_PARAM_EXPLICIT_NOTIFY |
+                       G_PARAM_STATIC_STRINGS));
 }
 
 static void
@@ -137,3 +165,25 @@ camel_spool_settings_set_use_xstatus_headers (CamelSpoolSettings *settings,
 
        g_object_notify (G_OBJECT (settings), "use-xstatus-headers");
 }
+
+gboolean
+camel_spool_settings_get_listen_notifications (CamelSpoolSettings *settings)
+{
+       g_return_val_if_fail (CAMEL_IS_SPOOL_SETTINGS (settings), FALSE);
+
+       return settings->priv->listen_notifications;
+}
+
+void
+camel_spool_settings_set_listen_notifications (CamelSpoolSettings *settings,
+                                              gboolean listen_notifications)
+{
+       g_return_if_fail (CAMEL_IS_SPOOL_SETTINGS (settings));
+
+       if (settings->priv->listen_notifications == listen_notifications)
+               return;
+
+       settings->priv->listen_notifications = listen_notifications;
+
+       g_object_notify (G_OBJECT (settings), "listen-notifications");
+}
diff --git a/src/camel/providers/local/camel-spool-settings.h 
b/src/camel/providers/local/camel-spool-settings.h
index 53d8d5a05..2bd40b5d5 100644
--- a/src/camel/providers/local/camel-spool-settings.h
+++ b/src/camel/providers/local/camel-spool-settings.h
@@ -63,6 +63,11 @@ gboolean     camel_spool_settings_get_use_xstatus_headers
 void           camel_spool_settings_set_use_xstatus_headers
                                                (CamelSpoolSettings *settings,
                                                 gboolean use_xstatus_headers);
+gboolean       camel_spool_settings_get_listen_notifications
+                                               (CamelSpoolSettings *settings);
+void           camel_spool_settings_set_listen_notifications
+                                               (CamelSpoolSettings *settings,
+                                                gboolean listen_notifications);
 
 G_END_DECLS
 
diff --git a/src/camel/providers/local/camel-spool-store.c b/src/camel/providers/local/camel-spool-store.c
index e6bb8eed1..702451862 100644
--- a/src/camel/providers/local/camel-spool-store.c
+++ b/src/camel/providers/local/camel-spool-store.c
@@ -37,6 +37,8 @@
 
 #define d(x)
 
+#define REFRESH_INTERVAL 2
+
 typedef enum _camel_spool_store_t {
        CAMEL_SPOOL_STORE_INVALID,
        CAMEL_SPOOL_STORE_MBOX, /* a single mbox */
@@ -45,6 +47,10 @@ typedef enum _camel_spool_store_t {
 
 struct _CamelSpoolStorePrivate {
        camel_spool_store_t store_type;
+       GFileMonitor *monitor;
+       GMutex refresh_lock;
+       guint refresh_id;
+       gint64 last_modified_time;
 };
 
 G_DEFINE_TYPE_WITH_PRIVATE (
@@ -679,12 +685,302 @@ spool_store_get_meta_path (CamelLocalStore *ls,
        return path;
 }
 
+typedef struct _RefreshData {
+       GWeakRef *spool_weak_ref;
+       gchar *folder_name;
+} RefreshData;
+
+static void
+refresh_data_free (gpointer ptr)
+{
+       RefreshData *rd = ptr;
+
+       if (rd) {
+               camel_utils_weak_ref_free (rd->spool_weak_ref);
+               g_free (rd->folder_name);
+               g_slice_free (RefreshData, rd);
+       }
+}
+
+static void
+spool_store_refresh_folder_cb (CamelSession *session,
+                              GCancellable *cancellable,
+                              gpointer user_data,
+                              GError **error)
+{
+       RefreshData *rd = user_data;
+       CamelFolder *folder;
+       CamelSpoolStore *spool;
+
+       g_return_if_fail (rd != NULL);
+
+       spool = g_weak_ref_get (rd->spool_weak_ref);
+       if (!spool)
+               return;
+
+       if (rd->folder_name)
+               folder = camel_store_get_folder_sync (CAMEL_STORE (spool), rd->folder_name, 
CAMEL_STORE_FOLDER_NONE, cancellable, NULL);
+       else
+               folder = camel_store_get_inbox_folder_sync (CAMEL_STORE (spool), cancellable, NULL);
+
+       if (folder) {
+               CamelLocalFolder *lf;
+               GStatBuf st;
+
+               lf = CAMEL_LOCAL_FOLDER (folder);
+
+               if (g_stat (lf->folder_path, &st) == 0) {
+                       CamelFolderSummary *summary;
+
+                       summary = camel_folder_get_folder_summary (folder);
+
+                       if (summary && camel_folder_summary_get_timestamp (summary) != st.st_mtime)
+                               camel_folder_refresh_info_sync (folder, cancellable, error);
+               }
+
+               g_object_unref (folder);
+       }
+
+       g_object_unref (spool);
+}
+
+static gboolean
+spool_store_submit_refresh_job_cb (gpointer user_data)
+{
+       RefreshData *rd = user_data;
+       CamelSpoolStore *spool;
+       gboolean scheduled = FALSE;
+
+       g_return_val_if_fail (rd != NULL, FALSE);
+
+       if (g_source_is_destroyed (g_main_current_source ())) {
+               refresh_data_free (rd);
+               return FALSE;
+       }
+
+       spool = g_weak_ref_get (rd->spool_weak_ref);
+
+       if (spool) {
+               gboolean refresh = FALSE;
+
+               g_mutex_lock (&spool->priv->refresh_lock);
+               if (spool->priv->refresh_id == g_source_get_id (g_main_current_source ())) {
+                       spool->priv->refresh_id = 0;
+                       refresh = TRUE;
+               }
+               g_mutex_unlock (&spool->priv->refresh_lock);
+
+               if (refresh) {
+                       CamelSession *session;
+
+                       session = camel_service_ref_session (CAMEL_SERVICE (spool));
+
+                       if (session) {
+                               camel_session_submit_job (session, _("Refreshing spool folder"),
+                                       spool_store_refresh_folder_cb, rd, refresh_data_free);
+                               scheduled = TRUE;
+                               g_object_unref (session);
+                       }
+               }
+
+               g_object_unref (spool);
+       }
+
+       if (!scheduled)
+               refresh_data_free (rd);
+
+       return FALSE;
+}
+
+static void
+spool_store_monitor_changed_cb (GFileMonitor *monitor,
+                               GFile *file,
+                               GFile *other_file,
+                               GFileMonitorEvent event_type,
+                               gpointer user_data)
+{
+       CamelSpoolStore *spool = user_data;
+       GStatBuf st;
+       const gchar *file_path;
+       gchar *full_path = NULL;
+       gchar *basename = NULL;
+       gboolean refresh = FALSE;
+
+       g_return_if_fail (CAMEL_IS_SPOOL_STORE (spool));
+
+       if (event_type != G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)
+               return;
+
+       if (!file)
+               return;
+
+       file_path = g_file_peek_path (file);
+
+       switch (spool_store_get_type (spool, NULL)) {
+       case CAMEL_SPOOL_STORE_INVALID:
+               break;
+       case CAMEL_SPOOL_STORE_MBOX:
+               full_path = camel_local_store_get_full_path (CAMEL_LOCAL_STORE (spool), NULL);
+
+               if (g_strcmp0 (full_path, file_path) == 0)
+                       refresh = TRUE;
+               break;
+       case CAMEL_SPOOL_STORE_ELM:
+               basename = g_file_get_basename (file);
+               full_path = camel_local_store_get_full_path (CAMEL_LOCAL_STORE (spool), basename);
+               if (g_strcmp0 (full_path, file_path) == 0)
+                       refresh = TRUE;
+               break;
+       }
+
+       if (refresh && g_stat (file_path, &st) == 0 &&
+           st.st_mtime != spool->priv->last_modified_time) {
+               spool->priv->last_modified_time = st.st_mtime;
+
+               g_mutex_lock (&spool->priv->refresh_lock);
+
+               if (!spool->priv->refresh_id) {
+                       RefreshData *rd;
+
+                       rd = g_slice_new0 (RefreshData);
+                       rd->spool_weak_ref = camel_utils_weak_ref_new (spool);
+                       rd->folder_name = basename;
+                       basename = NULL;
+
+                       spool->priv->refresh_id = g_timeout_add_seconds (REFRESH_INTERVAL,
+                               spool_store_submit_refresh_job_cb, rd);
+               }
+
+               g_mutex_unlock (&spool->priv->refresh_lock);
+       }
+
+       g_free (full_path);
+       g_free (basename);
+}
+
+static void
+spool_store_update_listen_notifications_cb (GObject *settings,
+                                           GParamSpec *param,
+                                           gpointer user_data)
+{
+       CamelSpoolStore *spool = user_data;
+       gchar *path = NULL;
+       gboolean listen_notifications = FALSE;
+
+       g_return_if_fail (CAMEL_IS_SPOOL_STORE (spool));
+
+       g_object_get (settings,
+               "path", &path,
+               "listen-notifications", &listen_notifications,
+               NULL);
+
+       g_clear_object (&spool->priv->monitor);
+
+       spool->priv->store_type = CAMEL_SPOOL_STORE_INVALID;
+
+       if (listen_notifications && path &&
+           g_file_test (path, G_FILE_TEST_EXISTS)) {
+               GFile *file;
+
+               file = g_file_new_for_path (path);
+               spool->priv->monitor = g_file_monitor (file, G_FILE_MONITOR_WATCH_MOUNTS, NULL, NULL);
+
+               if (spool->priv->monitor) {
+                       g_signal_connect_object (spool->priv->monitor, "changed",
+                               G_CALLBACK (spool_store_monitor_changed_cb), spool, 0);
+               }
+
+               g_object_unref (file);
+       }
+
+       g_free (path);
+}
+
+static void
+spool_store_connect_settings (GObject *object)
+{
+       CamelSettings *settings;
+
+       g_return_if_fail (CAMEL_IS_SPOOL_STORE (object));
+
+       settings = camel_service_ref_settings (CAMEL_SERVICE (object));
+       if (!settings)
+               return;
+
+       g_signal_connect_object (settings, "notify::listen-notifications",
+               G_CALLBACK (spool_store_update_listen_notifications_cb), object, 0);
+
+       g_signal_connect_object (settings, "notify::path",
+               G_CALLBACK (spool_store_update_listen_notifications_cb), object, 0);
+
+       spool_store_update_listen_notifications_cb (G_OBJECT (settings), NULL, object);
+
+       g_object_unref (settings);
+}
+
+static void
+spool_store_settings_changed_cb (GObject *object,
+                                GParamSpec *param,
+                                gpointer user_data)
+{
+       g_return_if_fail (CAMEL_IS_SPOOL_STORE (object));
+
+       spool_store_connect_settings (object);
+}
+
+static void
+spool_store_constructed (GObject *object)
+{
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (camel_spool_store_parent_class)->constructed (object);
+
+       g_signal_connect (object, "notify::settings",
+               G_CALLBACK (spool_store_settings_changed_cb), NULL);
+
+       spool_store_connect_settings (object);
+}
+
+static void
+spool_store_dispose (GObject *object)
+{
+       CamelSpoolStore *spool = CAMEL_SPOOL_STORE (object);
+
+       g_mutex_lock (&spool->priv->refresh_lock);
+       if (spool->priv->refresh_id) {
+               g_source_remove (spool->priv->refresh_id);
+               spool->priv->refresh_id = 0;
+       }
+       g_mutex_unlock (&spool->priv->refresh_lock);
+
+       g_clear_object (&spool->priv->monitor);
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (camel_spool_store_parent_class)->dispose (object);
+}
+
+static void
+spool_store_finalize (GObject *object)
+{
+       CamelSpoolStore *spool = CAMEL_SPOOL_STORE (object);
+
+       g_mutex_clear (&spool->priv->refresh_lock);
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (camel_spool_store_parent_class)->finalize (object);
+}
+
 static void
 camel_spool_store_class_init (CamelSpoolStoreClass *class)
 {
        CamelServiceClass *service_class;
        CamelStoreClass *store_class;
        CamelLocalStoreClass *local_store_class;
+       GObjectClass *object_class;
+
+       object_class = G_OBJECT_CLASS (class);
+       object_class->constructed = spool_store_constructed;
+       object_class->dispose = spool_store_dispose;
+       object_class->finalize = spool_store_finalize;
 
        service_class = CAMEL_SERVICE_CLASS (class);
        service_class->settings_type = CAMEL_TYPE_SPOOL_SETTINGS;
@@ -707,4 +1003,6 @@ camel_spool_store_init (CamelSpoolStore *spool_store)
 {
        spool_store->priv = camel_spool_store_get_instance_private (spool_store);
        spool_store->priv->store_type = CAMEL_SPOOL_STORE_INVALID;
+
+       g_mutex_init (&spool_store->priv->refresh_lock);
 }


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