From b64b51e0e969fbf355d3a2bc1c69f4123c33078f Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Thu, 21 Feb 2013 15:22:08 +0100 Subject: [PATCH] Make network connection change listener actually working With this it's properly recognized when network connection changes, like when user connects to VPN, or disconnects from it, and the backend's online state is properly updated (if is provides connection address). --- libebackend/e-backend.c | 116 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 97 insertions(+), 19 deletions(-) diff --git a/libebackend/e-backend.c b/libebackend/e-backend.c index 6a291e5..c22fc29 100644 --- a/libebackend/e-backend.c +++ b/libebackend/e-backend.c @@ -62,6 +62,7 @@ struct _EBackendPrivate { GNetworkMonitor *network_monitor; gulong network_changed_handler_id; + guint network_changed_timeout_id; GMutex network_monitor_cancellable_lock; GCancellable *network_monitor_cancellable; @@ -90,6 +91,27 @@ async_context_free (AsyncContext *async_context) g_slice_free (AsyncContext, async_context); } +struct UpdateOnlineData +{ + EBackend *backend; + gboolean is_online; +}; + +static gpointer +set_backend_online_thread (gpointer user_data) +{ + struct UpdateOnlineData *uod = user_data; + + g_return_val_if_fail (uod != NULL, NULL); + + e_backend_set_online (uod->backend, uod->is_online); + + g_object_unref (uod->backend); + g_slice_free (struct UpdateOnlineData, uod); + + return NULL; +} + static void backend_network_monitor_can_reach_cb (GObject *source_object, GAsyncResult *result, @@ -97,6 +119,8 @@ backend_network_monitor_can_reach_cb (GObject *source_object, { EBackend *backend = E_BACKEND (user_data); gboolean host_is_reachable; + struct UpdateOnlineData *uod; + GThread *thread; GError *error = NULL; host_is_reachable = g_network_monitor_can_reach_finish ( @@ -107,26 +131,22 @@ backend_network_monitor_can_reach_cb (GObject *source_object, (host_is_reachable && (error == NULL)) || (!host_is_reachable && (error != NULL))); - if (host_is_reachable) { - e_backend_set_online (backend, TRUE); - - } else if (G_IS_IO_ERROR (error, G_IO_ERROR_CANCELLED)) { - /* Ignore cancellations. */ - - } else if (G_IS_IO_ERROR (error, G_IO_ERROR_HOST_UNREACHABLE)) { - e_backend_set_online (backend, FALSE); - - } else if (G_IS_RESOLVER_ERROR (error, G_RESOLVER_ERROR_NOT_FOUND)) { - e_backend_set_online (backend, FALSE); - - } else { - g_warning ("%s: %s", G_STRFUNC, error->message); + if (G_IS_IO_ERROR (error, G_IO_ERROR_CANCELLED) || + (host_is_reachable ? 1 : 0) == (e_backend_get_online (backend) ? 1 : 0)) { + g_clear_error (&error); + g_object_unref (backend); + return; } - if (error != NULL) - g_error_free (error); + g_clear_error (&error); + + uod = g_slice_new (struct UpdateOnlineData); + uod->backend = backend; + uod->is_online = host_is_reachable; - g_object_unref (backend); + /* do this in a separate thread, not the main thread */ + thread = g_thread_new (NULL, set_backend_online_thread, uod); + g_thread_unref (thread); } static void @@ -148,6 +168,36 @@ backend_update_online_state (EBackend *backend) cancellable = NULL; } + if (connectable && G_IS_NETWORK_ADDRESS (connectable)) { + GNetworkAddress *network_address = G_NETWORK_ADDRESS (connectable); + + /* create copy of the backend's connectable, because once the connectable + reaches its destination it caches the value and doesn't retry after + network changes. + + This should be eventually replaced with default implementation + of EBackend::get_destination_address() doing basically the same + what currently does the backend silently on construction, thus + it'll get also current values from the ESource, not stale from + construct time. + */ + connectable = g_network_address_new ( + g_network_address_get_hostname (network_address), + g_network_address_get_port (network_address)); + + g_object_unref (network_address); + } + + if (!connectable) { + gchar *host = NULL; + guint16 port = 0; + + if (e_backend_get_destination_address (backend, &host, &port) && host) + connectable = g_network_address_new (host, port); + + g_free (host); + } + if (connectable == NULL) { e_backend_set_online (backend, TRUE); } else { @@ -168,12 +218,30 @@ backend_update_online_state (EBackend *backend) g_object_unref (connectable); } +static gboolean +backend_network_changed_timeout_cb (gpointer user_data) +{ + EBackend *backend = user_data; + + if (!g_source_is_destroyed (g_main_current_source ())) { + backend->priv->network_changed_timeout_id = 0; + + backend_update_online_state (backend); + } + + return FALSE; +} + static void backend_network_changed_cb (GNetworkMonitor *network_monitor, gboolean network_available, EBackend *backend) { - backend_update_online_state (backend); + if (backend->priv->network_changed_timeout_id) + g_source_remove (backend->priv->network_changed_timeout_id); + + /* wait few seconds, a network change can fire this event multiple times */ + backend->priv->network_changed_timeout_id = g_timeout_add_seconds (3, backend_network_changed_timeout_cb, backend); } static void @@ -264,6 +332,11 @@ backend_dispose (GObject *object) priv->network_changed_handler_id = 0; } + if (priv->network_changed_timeout_id) { + g_source_remove (priv->network_changed_timeout_id); + priv->network_changed_timeout_id = 0; + } + g_clear_object (&priv->source); g_clear_object (&priv->prompter); g_clear_object (&priv->connectable); @@ -284,6 +357,11 @@ backend_finalize (GObject *object) g_mutex_clear (&priv->property_lock); g_mutex_clear (&priv->network_monitor_cancellable_lock); + if (priv->network_changed_timeout_id) { + g_source_remove (priv->network_changed_timeout_id); + priv->network_changed_timeout_id = 0; + } + /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (e_backend_parent_class)->finalize (object); } @@ -986,7 +1064,7 @@ e_backend_is_destination_reachable (EBackend *backend, GNetworkMonitor *network_monitor; GSocketConnectable *connectable; - network_monitor = g_network_monitor_get_default (); + network_monitor = backend->priv->network_monitor; connectable = g_network_address_new (host, port); if (connectable) { -- 2.7.4