[evolution-ews/gnome-3-36] EEwsNotification: Change how subscription to server change notifications work



commit bbd1f8e84dfb30aa74a9e535aa5feb46cf4439ed
Author: Milan Crha <mcrha redhat com>
Date:   Thu May 21 16:02:32 2020 +0200

    EEwsNotification: Change how subscription to server change notifications work
    
    * the subscription ID is independent of the connection, it can survive
      disconnects, thus it should try to unsubscribe, if any existed
    
    * the connection (EEwsConnection object) is shared between backends
      within one project and each of the backends can subscribe for change
      notifications, which can eventually mean a flood of server requests
      shortly after the backends connect to the server; this change adds
      a short delay before the notification is started, which gives time
      to the backends to register with their folders
    
    This is partly related to a downstream bug:
    https://bugzilla.redhat.com/show_bug.cgi?id=1823240

 src/addressbook/e-book-backend-ews.c |  30 ++++++
 src/calendar/e-cal-backend-ews.c     |  31 ++++++
 src/camel/camel-ews-store.c          |  32 +++++++
 src/server/e-ews-connection.c        | 177 +++++++++++++++++++++++++++++++++--
 src/server/e-ews-connection.h        |   5 +
 src/server/e-ews-notification.c      |  39 +++++++-
 src/server/e-ews-notification.h      |   3 +-
 7 files changed, 306 insertions(+), 11 deletions(-)
---
diff --git a/src/addressbook/e-book-backend-ews.c b/src/addressbook/e-book-backend-ews.c
index 7f91dc81..8497933e 100644
--- a/src/addressbook/e-book-backend-ews.c
+++ b/src/addressbook/e-book-backend-ews.c
@@ -96,6 +96,11 @@ struct _EBookBackendEwsPrivate {
 
        guint subscription_key;
 
+       /* The subscription ID is not tight to the actual connection, it survives
+          disconnects, thus remember it and pass it back to the new connection,
+          thus it can eventually unsubscribe from it. */
+       gchar *last_subscription_id;
+
        /* used for storing attachments */
        gchar *attachments_dir;
 };
@@ -3221,6 +3226,25 @@ ebb_ews_check_is_gal (EBookBackendEws *bbews)
        return is_gal;
 }
 
+static void
+ebb_ews_subscription_id_changed_cb (EEwsConnection *cnc,
+                                   const gchar *subscription_id,
+                                   gpointer user_data)
+{
+       EBookBackendEws *bbews = user_data;
+
+       g_return_if_fail (E_IS_BOOK_BACKEND_EWS (bbews));
+
+       g_rec_mutex_lock (&bbews->priv->cnc_lock);
+
+       if (g_strcmp0 (bbews->priv->last_subscription_id, subscription_id) != 0) {
+               g_free (bbews->priv->last_subscription_id);
+               bbews->priv->last_subscription_id = g_strdup (subscription_id);
+       }
+
+       g_rec_mutex_unlock (&bbews->priv->cnc_lock);
+}
+
 static gboolean
 ebb_ews_connect_sync (EBookMetaBackend *meta_backend,
                      const ENamedParameters *credentials,
@@ -3306,6 +3330,11 @@ ebb_ews_connect_sync (EBookMetaBackend *meta_backend,
 
                        folders = g_slist_prepend (folders, bbews->priv->folder_id);
 
+                       e_ews_connection_set_last_subscription_id (bbews->priv->cnc, 
bbews->priv->last_subscription_id);
+
+                       g_signal_connect_object (bbews->priv->cnc, "subscription-id-changed",
+                               G_CALLBACK (ebb_ews_subscription_id_changed_cb), bbews, 0);
+
                        e_ews_connection_enable_notifications_sync (bbews->priv->cnc,
                                folders, &bbews->priv->subscription_key);
 
@@ -4049,6 +4078,7 @@ e_book_backend_ews_finalize (GObject *object)
 
        g_free (bbews->priv->folder_id);
        g_free (bbews->priv->attachments_dir);
+       g_free (bbews->priv->last_subscription_id);
 
        g_rec_mutex_clear (&bbews->priv->cnc_lock);
 
diff --git a/src/calendar/e-cal-backend-ews.c b/src/calendar/e-cal-backend-ews.c
index dfd02fa4..88bbe38a 100644
--- a/src/calendar/e-cal-backend-ews.c
+++ b/src/calendar/e-cal-backend-ews.c
@@ -70,6 +70,12 @@ struct _ECalBackendEwsPrivate {
        gchar *folder_id;
 
        guint subscription_key;
+
+       /* The subscription ID is not tight to the actual connection, it survives
+          disconnects, thus remember it and pass it back to the new connection,
+          thus it can eventually unsubscribe from it. */
+       gchar *last_subscription_id;
+
        gboolean is_freebusy_calendar;
 
        gchar *attachments_dir;
@@ -1643,6 +1649,25 @@ ecb_ews_can_send_invitations (ECalBackendEws *cbews,
        return ecb_ews_organizer_is_user (cbews, comp);
 }
 
+static void
+ecb_ews_subscription_id_changed_cb (EEwsConnection *cnc,
+                                   const gchar *subscription_id,
+                                   gpointer user_data)
+{
+       ECalBackendEws *cbews = user_data;
+
+       g_return_if_fail (E_IS_CAL_BACKEND_EWS (cbews));
+
+       g_rec_mutex_lock (&cbews->priv->cnc_lock);
+
+       if (g_strcmp0 (cbews->priv->last_subscription_id, subscription_id) != 0) {
+               g_free (cbews->priv->last_subscription_id);
+               cbews->priv->last_subscription_id = g_strdup (subscription_id);
+       }
+
+       g_rec_mutex_unlock (&cbews->priv->cnc_lock);
+}
+
 static gboolean
 ecb_ews_connect_sync (ECalMetaBackend *meta_backend,
                      const ENamedParameters *credentials,
@@ -1705,6 +1730,11 @@ ecb_ews_connect_sync (ECalMetaBackend *meta_backend,
 
                        folders = g_slist_prepend (folders, cbews->priv->folder_id);
 
+                       e_ews_connection_set_last_subscription_id (cbews->priv->cnc, 
cbews->priv->last_subscription_id);
+
+                       g_signal_connect_object (cbews->priv->cnc, "subscription-id-changed",
+                               G_CALLBACK (ecb_ews_subscription_id_changed_cb), cbews, 0);
+
                        e_ews_connection_enable_notifications_sync (cbews->priv->cnc,
                                folders, &cbews->priv->subscription_key);
 
@@ -4332,6 +4362,7 @@ ecb_ews_finalize (GObject *object)
 
        g_free (cbews->priv->folder_id);
        g_free (cbews->priv->attachments_dir);
+       g_free (cbews->priv->last_subscription_id);
 
        g_rec_mutex_clear (&cbews->priv->cnc_lock);
 
diff --git a/src/camel/camel-ews-store.c b/src/camel/camel-ews-store.c
index 7dd9e085..acc6384a 100644
--- a/src/camel/camel-ews-store.c
+++ b/src/camel/camel-ews-store.c
@@ -77,6 +77,12 @@ struct _CamelEwsStorePrivate {
 
        gboolean listen_notifications;
        guint subscription_key;
+
+       /* The subscription ID is not tight to the actual connection, it survives
+          disconnects, thus remember it and pass it back to the new connection,
+          thus it can eventually unsubscribe from it. */
+       gchar *last_subscription_id;
+
        guint update_folder_id;
        guint update_folder_list_id;
        GCancellable *updates_cancellable;
@@ -1427,6 +1433,25 @@ camel_ews_store_check_all_cb (CamelEwsStore *ews_store,
        camel_ews_store_handle_notifications (ews_store, ews_settings);
 }
 
+static void
+ews_camel_subscription_id_changed_cb (EEwsConnection *cnc,
+                                     const gchar *subscription_id,
+                                     gpointer user_data)
+{
+       CamelEwsStore *ews_store = user_data;
+
+       g_return_if_fail (CAMEL_IS_EWS_STORE (ews_store));
+
+       g_mutex_lock (&ews_store->priv->connection_lock);
+
+       if (g_strcmp0 (ews_store->priv->last_subscription_id, subscription_id) != 0) {
+               g_free (ews_store->priv->last_subscription_id);
+               ews_store->priv->last_subscription_id = g_strdup (subscription_id);
+       }
+
+       g_mutex_unlock (&ews_store->priv->connection_lock);
+}
+
 static gboolean
 ews_connect_sync (CamelService *service,
                   GCancellable *cancellable,
@@ -1506,6 +1531,12 @@ ews_connect_sync (CamelService *service,
                                "server-notification",
                                G_CALLBACK (camel_ews_store_server_notification_cb),
                                ews_store);
+
+                       e_ews_connection_set_last_subscription_id (connection, 
ews_store->priv->last_subscription_id);
+
+                       g_signal_connect_object (connection, "subscription-id-changed",
+                               G_CALLBACK (ews_camel_subscription_id_changed_cb), ews_store, 0);
+
                        g_clear_object (&connection);
                }
        }
@@ -4000,6 +4031,7 @@ ews_store_finalize (GObject *object)
        ews_store = CAMEL_EWS_STORE (object);
 
        g_free (ews_store->storage_path);
+       g_free (ews_store->priv->last_subscription_id);
        g_mutex_clear (&ews_store->priv->get_finfo_lock);
        g_mutex_clear (&ews_store->priv->connection_lock);
        g_rec_mutex_clear (&ews_store->priv->update_lock);
diff --git a/src/server/e-ews-connection.c b/src/server/e-ews-connection.c
index 127b436f..55660237 100644
--- a/src/server/e-ews-connection.c
+++ b/src/server/e-ews-connection.c
@@ -82,6 +82,7 @@ struct _EEwsConnectionPrivate {
        GMainContext *soup_context;
        GProxyResolver *proxy_resolver;
        EEwsNotification *notification;
+       guint notification_delay_id;
 
        CamelEwsSettings *settings;
        guint concurrent_connections;
@@ -102,6 +103,10 @@ struct _EEwsConnectionPrivate {
 
        GHashTable *subscriptions;
        GSList *subscribed_folders;
+       /* The subscription ID is not tight to the actual connection, it survives
+          disconnects, thus remember it and unsubscribe from it, before adding
+          a new subscription. */
+       gchar *last_subscription_id;
 
        EEwsServerVersion version;
        gboolean backoff_enabled;
@@ -126,7 +131,8 @@ enum {
 enum {
        SERVER_NOTIFICATION,
        PASSWORD_WILL_EXPIRE,
-       LAST_SIGNAL,
+       SUBSCRIPTION_ID_CHANGED,
+       LAST_SIGNAL
 };
 
 static guint signals[LAST_SIGNAL];
@@ -2052,6 +2058,11 @@ ews_connection_dispose (GObject *object)
        g_mutex_unlock (&connecting);
 
        NOTIFICATION_LOCK (cnc);
+       if (cnc->priv->notification_delay_id) {
+               g_source_remove (cnc->priv->notification_delay_id);
+               cnc->priv->notification_delay_id = 0;
+       }
+
        if (cnc->priv->notification) {
                e_ews_notification_stop_listening_sync (cnc->priv->notification);
                g_clear_object (&cnc->priv->notification);
@@ -2110,6 +2121,7 @@ ews_connection_finalize (GObject *object)
        g_free (priv->hash_key);
        g_free (priv->impersonate_user);
        g_free (priv->ssl_certificate_pem);
+       g_free (priv->last_subscription_id);
 
        g_clear_object (&priv->bearer_auth);
 
@@ -2214,6 +2226,15 @@ e_ews_connection_class_init (EEwsConnectionClass *class)
                G_TYPE_NONE, 2,
                G_TYPE_INT,
                G_TYPE_STRING);
+
+       signals[SUBSCRIPTION_ID_CHANGED] = g_signal_new (
+               "subscription-id-changed",
+               G_OBJECT_CLASS_TYPE (object_class),
+               G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+               0, NULL, NULL,
+               g_cclosure_marshal_VOID__STRING,
+               G_TYPE_NONE, 1,
+               G_TYPE_STRING);
 }
 
 static void
@@ -3015,6 +3036,38 @@ e_ews_connection_set_disconnected_flag (EEwsConnection *cnc,
        cnc->priv->disconnected_flag = disconnected_flag;
 }
 
+gchar *
+e_ews_connection_dup_last_subscription_id (EEwsConnection *cnc)
+{
+       gchar *res;
+
+       g_return_val_if_fail (E_IS_EWS_CONNECTION (cnc), NULL);
+
+       g_mutex_lock (&cnc->priv->property_lock);
+
+       res = g_strdup (cnc->priv->last_subscription_id);
+
+       g_mutex_unlock (&cnc->priv->property_lock);
+
+       return res;
+}
+
+void
+e_ews_connection_set_last_subscription_id (EEwsConnection *cnc,
+                                          const gchar *subscription_id)
+{
+       g_return_if_fail (E_IS_EWS_CONNECTION (cnc));
+
+       g_mutex_lock (&cnc->priv->property_lock);
+
+       if (g_strcmp0 (subscription_id, cnc->priv->last_subscription_id) != 0) {
+               g_free (cnc->priv->last_subscription_id);
+               cnc->priv->last_subscription_id = g_strdup (subscription_id);
+       }
+
+       g_mutex_unlock (&cnc->priv->property_lock);
+}
+
 static xmlDoc *
 e_ews_autodiscover_ws_xml (const gchar *email_address)
 {
@@ -10494,6 +10547,97 @@ ews_connection_build_subscribed_folders_list (gpointer key,
        }
 }
 
+static void
+ews_connection_subscription_id_changed_cb (EEwsNotification *notification,
+                                          const gchar *subscription_id,
+                                          gpointer user_data)
+{
+       EEwsConnection *cnc = user_data;
+
+       g_return_if_fail (E_IS_EWS_CONNECTION (cnc));
+
+       NOTIFICATION_LOCK (cnc);
+
+       if (cnc->priv->notification == notification)
+               g_signal_emit (cnc, signals[SUBSCRIPTION_ID_CHANGED], 0, subscription_id, NULL);
+
+       NOTIFICATION_UNLOCK (cnc);
+}
+
+static gpointer
+ews_connection_notification_start_thread (gpointer user_data)
+{
+       GWeakRef *weakref = user_data;
+       EEwsConnection *cnc;
+
+       g_return_val_if_fail (weakref != NULL, NULL);
+
+       cnc = g_weak_ref_get (weakref);
+
+       if (cnc && !e_ews_connection_get_disconnected_flag (cnc)) {
+               gchar *last_subscription_id = e_ews_connection_dup_last_subscription_id (cnc);
+
+               NOTIFICATION_LOCK (cnc);
+
+               if (cnc->priv->subscribed_folders) {
+                       g_warn_if_fail (cnc->priv->notification == NULL);
+                       g_clear_object (&cnc->priv->notification);
+
+                       cnc->priv->notification = e_ews_notification_new (cnc, last_subscription_id);
+
+                       /* The 'notification' assumes ownership of the 'last_subscription_id' */
+                       last_subscription_id = NULL;
+
+                       g_signal_connect_object (cnc->priv->notification, "subscription-id-changed",
+                               G_CALLBACK (ews_connection_subscription_id_changed_cb), cnc, 0);
+
+                       e_ews_notification_start_listening_sync (cnc->priv->notification, 
cnc->priv->subscribed_folders);
+               }
+
+               NOTIFICATION_UNLOCK (cnc);
+
+               g_free (last_subscription_id);
+       }
+
+       g_clear_object (&cnc);
+       e_weak_ref_free (weakref);
+
+       return NULL;
+}
+
+static gboolean
+ews_connection_notification_delay_cb (gpointer user_data)
+{
+       GWeakRef *weakref = user_data;
+       EEwsConnection *cnc;
+
+       if (g_source_is_destroyed (g_main_current_source ()))
+               return FALSE;
+
+       g_return_val_if_fail (weakref != NULL, FALSE);
+
+       cnc = g_weak_ref_get (weakref);
+
+       if (cnc) {
+               NOTIFICATION_LOCK (cnc);
+
+               if (cnc->priv->notification_delay_id == g_source_get_id (g_main_current_source ())) {
+                       cnc->priv->notification_delay_id = 0;
+
+                       if (cnc->priv->subscribed_folders) {
+                               g_thread_unref (g_thread_new (NULL, ews_connection_notification_start_thread,
+                                       e_weak_ref_new (cnc)));
+                       }
+               }
+
+               NOTIFICATION_UNLOCK (cnc);
+
+               g_object_unref (cnc);
+       }
+
+       return FALSE;
+}
+
 /*
  * Enables server notification on a folder (or a set of folders).
  * The events we are listen for notifications are: Copied, Created, Deleted, Modified and Moved.
@@ -10521,7 +10665,7 @@ e_ews_connection_enable_notifications_sync (EEwsConnection *cnc,
                                            GSList *folders,
                                            guint *subscription_key)
 {
-       GSList *new_folders = NULL, *l;
+       GSList *new_folders = NULL, *l, *flink;
        gint subscriptions_size;
 
        g_return_if_fail (cnc != NULL);
@@ -10535,10 +10679,25 @@ e_ews_connection_enable_notifications_sync (EEwsConnection *cnc,
        if (subscriptions_size == G_MAXUINT - 1)
                goto exit;
 
-       if (subscriptions_size > 0) {
-               e_ews_notification_stop_listening_sync (cnc->priv->notification);
+       for (flink = folders; flink; flink = g_slist_next (flink)) {
+               for (l = cnc->priv->subscribed_folders; l; l = g_slist_next (l)) {
+                       if (g_strcmp0 (l->data, flink->data) == 0)
+                               break;
+               }
 
-               g_clear_object (&cnc->priv->notification);
+               if (!l)
+                       break;
+       }
+
+       /* All requested folders are already subscribed */
+       if (!flink && cnc->priv->notification)
+               goto exit;
+
+       if (subscriptions_size > 0) {
+               if (cnc->priv->notification) {
+                       e_ews_notification_stop_listening_sync (cnc->priv->notification);
+                       g_clear_object (&cnc->priv->notification);
+               }
 
                g_slist_free_full (cnc->priv->subscribed_folders, g_free);
                cnc->priv->subscribed_folders = NULL;
@@ -10558,9 +10717,11 @@ e_ews_connection_enable_notifications_sync (EEwsConnection *cnc,
 
        g_hash_table_foreach (cnc->priv->subscriptions, ews_connection_build_subscribed_folders_list, cnc);
 
-       cnc->priv->notification = e_ews_notification_new (cnc);
+       if (cnc->priv->notification_delay_id)
+               g_source_remove (cnc->priv->notification_delay_id);
 
-       e_ews_notification_start_listening_sync (cnc->priv->notification, cnc->priv->subscribed_folders);
+       cnc->priv->notification_delay_id = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT, 5,
+               ews_connection_notification_delay_cb, e_weak_ref_new (cnc), (GDestroyNotify) e_weak_ref_free);
 
 exit:
        *subscription_key = notification_key;
@@ -10591,7 +10752,7 @@ e_ews_connection_disable_notifications_sync (EEwsConnection *cnc,
        cnc->priv->subscribed_folders = NULL;
 
        g_hash_table_foreach (cnc->priv->subscriptions, ews_connection_build_subscribed_folders_list, cnc);
-       if (cnc->priv->subscribed_folders != NULL) {
+       if (cnc->priv->subscribed_folders != NULL && !e_ews_connection_get_disconnected_flag (cnc)) {
                e_ews_notification_start_listening_sync (cnc->priv->notification, 
cnc->priv->subscribed_folders);
        } else {
                g_clear_object (&cnc->priv->notification);
diff --git a/src/server/e-ews-connection.h b/src/server/e-ews-connection.h
index 37f9592f..f2643836 100644
--- a/src/server/e-ews-connection.h
+++ b/src/server/e-ews-connection.h
@@ -477,6 +477,11 @@ gboolean   e_ews_connection_get_disconnected_flag
 void           e_ews_connection_set_disconnected_flag
                                                (EEwsConnection *cnc,
                                                 gboolean disconnected_flag);
+gchar *                e_ews_connection_dup_last_subscription_id
+                                               (EEwsConnection *cnc);
+void           e_ews_connection_set_last_subscription_id
+                                               (EEwsConnection *cnc,
+                                                const gchar *subscription_id);
 EEwsConnection *e_ews_connection_find          (const gchar *uri,
                                                 const gchar *username);
 GSList *       e_ews_connection_list_existing  (void); /* EEwsConnection * */
diff --git a/src/server/e-ews-notification.c b/src/server/e-ews-notification.c
index c808245b..c4b84a49 100644
--- a/src/server/e-ews-notification.c
+++ b/src/server/e-ews-notification.c
@@ -36,6 +36,7 @@ struct _EEwsNotificationPrivate {
        GWeakRef connection_wk;
        GByteArray *chunk;
        GCancellable *cancellable;
+       gchar *last_subscription_id; /* guarded by the caller, because it can be set only after construct */
 };
 
 enum {
@@ -43,6 +44,13 @@ enum {
        PROP_CONNECTION
 };
 
+enum {
+       SUBSCRIPTION_ID_CHANGED,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
 static const gchar *default_events_names[] = {
        "CopiedEvent",
        "CreatedEvent",
@@ -91,13 +99,20 @@ ews_notification_authenticate (SoupSession *session,
 }
 
 EEwsNotification *
-e_ews_notification_new (EEwsConnection *connection)
+e_ews_notification_new (EEwsConnection *connection,
+                       gchar *last_subscription_id)
 {
+       EEwsNotification *notif;
+
        g_return_val_if_fail (E_IS_EWS_CONNECTION (connection), NULL);
 
-       return g_object_new (
+       notif = g_object_new (
                E_TYPE_EWS_NOTIFICATION,
                "connection", connection, NULL);
+
+       notif->priv->last_subscription_id = last_subscription_id;
+
+       return notif;
 }
 
 static void
@@ -203,6 +218,7 @@ ews_notification_finalize (GObject *object)
        notif = E_EWS_NOTIFICATION (object);
 
        g_weak_ref_clear (&notif->priv->connection_wk);
+       g_free (notif->priv->last_subscription_id);
 
        /* Chain up to parent's method. */
        G_OBJECT_CLASS (e_ews_notification_parent_class)->finalize (object);
@@ -233,6 +249,15 @@ e_ews_notification_class_init (EEwsNotificationClass *class)
                        G_PARAM_READWRITE |
                        G_PARAM_CONSTRUCT_ONLY |
                        G_PARAM_STATIC_STRINGS));
+
+       signals[SUBSCRIPTION_ID_CHANGED] = g_signal_new (
+               "subscription-id-changed",
+               G_OBJECT_CLASS_TYPE (object_class),
+               G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+               0, NULL, NULL,
+               g_cclosure_marshal_VOID__STRING,
+               G_TYPE_NONE, 1,
+               G_TYPE_STRING);
 }
 
 static void
@@ -416,6 +441,9 @@ e_ews_notification_subscribe_folder_sync (EEwsNotification *notification,
        }
 
        g_object_unref (response);
+
+       g_signal_emit (notification, signals[SUBSCRIPTION_ID_CHANGED], 0, *subscription_id, NULL);
+
        return TRUE;
 }
 
@@ -504,6 +532,8 @@ e_ews_notification_unsubscribe_folder_sync (EEwsNotification *notification,
                return FALSE;
        }
 
+       g_signal_emit (notification, signals[SUBSCRIPTION_ID_CHANGED], 0, NULL, NULL);
+
        return TRUE;
 }
 
@@ -898,6 +928,11 @@ e_ews_notification_get_events_thread (gpointer user_data)
        g_return_val_if_fail (td->notification != NULL, NULL);
        g_return_val_if_fail (td->folders != NULL, NULL);
 
+       if (td->notification->priv->last_subscription_id) {
+               e_ews_notification_unsubscribe_folder_sync (td->notification, 
td->notification->priv->last_subscription_id);
+               g_clear_pointer (&td->notification->priv->last_subscription_id, g_free);
+       }
+
        if (!e_ews_notification_subscribe_folder_sync (td->notification, td->folders, &subscription_id, 
td->cancellable))
                goto exit;
 
diff --git a/src/server/e-ews-notification.h b/src/server/e-ews-notification.h
index 2ecbc651..e790b8f0 100644
--- a/src/server/e-ews-notification.h
+++ b/src/server/e-ews-notification.h
@@ -53,7 +53,8 @@ struct _EEwsNotificationClass {
 
 GType          e_ews_notification_get_type     (void);
 EEwsNotification *
-               e_ews_notification_new          (EEwsConnection *connection);
+               e_ews_notification_new          (EEwsConnection *connection,
+                                                gchar *last_subscription_id); /* assumes ownership of it */
 
 void           e_ews_notification_start_listening_sync
                                                (EEwsNotification *notification,


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