# HG changeset patch
# Node ID 07515d334a46
# Parent  efe66edc9676
diff --git a/libpurple/protocols/ircv3/purpleircv3protocol.c b/libpurple/protocols/ircv3/purpleircv3protocol.c
--- a/libpurple/protocols/ircv3/purpleircv3protocol.c
+++ b/libpurple/protocols/ircv3/purpleircv3protocol.c
@@ -29,6 +29,39 @@
 #include "purpleircv3protocolconversation.h"
 
 /******************************************************************************
+ * Callbacks
+ *****************************************************************************/
+static void
+purple_ircv3_protocol_can_reach_cb(GObject *self, GAsyncResult *result,
+                                   gpointer data)
+{
+	GError *error = NULL;
+	GTask *task = data;
+	gboolean can_reach = FALSE;
+
+	/* task and result share a cancellable, so either can be used here. */
+	if(g_task_return_error_if_cancelled(task)) {
+		g_clear_object(&task);
+
+		return;
+	}
+
+	can_reach = g_network_monitor_can_reach_finish(G_NETWORK_MONITOR(self), result,
+	                                               &error);
+
+	if(error != NULL) {
+		g_task_return_error(task, error);
+	} else if(!can_reach) {
+		g_task_return_new_error(task, PURPLE_IRCV3_DOMAIN, 0,
+		                        _("Unknown network error."));
+	} else {
+		g_task_return_boolean(task, TRUE);
+	}
+
+	g_clear_object(&task);
+}
+
+/******************************************************************************
  * PurpleProtocol Implementation
  *****************************************************************************/
 static GList *
@@ -148,6 +181,46 @@
 	return types;
 }
 
+static void
+purple_ircv3_protocol_can_connect_async(PurpleProtocol *protocol,
+                                        PurpleAccount *account,
+                                        GCancellable *cancellable,
+                                        GAsyncReadyCallback callback,
+                                        gpointer data)
+{
+	GNetworkMonitor *monitor = NULL;
+	GSocketConnectable *connectable = NULL;
+	GStrv parts = NULL;
+	GTask *task = NULL;
+	const char *username = NULL;
+	gint port = 0;
+
+	task = g_task_new(protocol, cancellable, callback, data);
+
+	monitor = g_network_monitor_get_default();
+
+	username = purple_contact_info_get_username(PURPLE_CONTACT_INFO(account));
+	parts = g_strsplit(username, "@", 2);
+	port = purple_account_get_int(account, "port",
+	                              PURPLE_IRCV3_DEFAULT_TLS_PORT);
+
+	connectable = g_network_address_new(parts[1], (guint16)port);
+	g_strfreev(parts);
+
+	g_network_monitor_can_reach_async(monitor, connectable, cancellable,
+	                                  purple_ircv3_protocol_can_reach_cb,
+	                                  task);
+	g_clear_object(&connectable);
+}
+
+static gboolean
+purple_ircv3_protocol_can_connect_finish(G_GNUC_UNUSED PurpleProtocol *protocol,
+                                         GAsyncResult *result,
+                                         GError **error)
+{
+	return g_task_propagate_boolean(G_TASK(result), error);
+}
+
 /******************************************************************************
  * GObject Implementation
  *****************************************************************************/
@@ -177,6 +250,10 @@
 	protocol_class->create_connection =
 		purple_ircv3_protocol_create_connection;
 	protocol_class->status_types = purple_ircv3_protocol_status_types;
+	protocol_class->can_connect_async =
+		purple_ircv3_protocol_can_connect_async;
+	protocol_class->can_connect_finish =
+		purple_ircv3_protocol_can_connect_finish;
 }
 
 /******************************************************************************
diff --git a/libpurple/purpleaccount.c b/libpurple/purpleaccount.c
--- a/libpurple/purpleaccount.c
+++ b/libpurple/purpleaccount.c
@@ -429,6 +429,54 @@
 	}
 }
 
+static void
+purple_account_can_connect_cb(GObject *source, GAsyncResult *result,
+                              gpointer data)
+{
+	PurpleAccount *account = data;
+	PurpleProtocol *protocol = PURPLE_PROTOCOL(source);
+	PurpleProtocolOptions options;
+	GError *error = NULL;
+	gboolean can_connect = FALSE;
+	gboolean require_password = TRUE;
+
+	can_connect = purple_protocol_can_connect_finish(protocol, result, &error);
+	if(!can_connect || error != NULL) {
+		PurpleConnectionErrorInfo *info = NULL;
+		const char *error_message = _("unknown error");
+
+		if(error != NULL) {
+			error_message = error->message;
+		}
+
+		info = purple_connection_error_info_new(PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+		                                        error_message);
+		purple_account_set_error(account, info);
+
+		g_clear_error(&error);
+
+		return;
+	}
+
+	options = purple_protocol_get_options(protocol);
+	if(options & OPT_PROTO_PASSWORD_OPTIONAL) {
+		require_password = purple_account_get_require_password(account);
+	} else if(options & OPT_PROTO_NO_PASSWORD) {
+		require_password = FALSE;
+	}
+
+	if(require_password) {
+		PurpleCredentialManager *manager = NULL;
+
+		manager = purple_credential_manager_get_default();
+		purple_credential_manager_read_password_async(manager, account, NULL,
+		                                              purple_account_connect_got_password_cb,
+		                                              account);
+	} else {
+		g_timeout_add_seconds(0, no_password_cb, account);
+	}
+}
+
 /******************************************************************************
  * XmlNode Helpers
  *****************************************************************************/
@@ -1065,11 +1113,8 @@
 void
 purple_account_connect(PurpleAccount *account)
 {
-	PurpleCredentialManager *manager = NULL;
 	PurpleProtocol *protocol = NULL;
-	PurpleProtocolOptions options;
 	const char *username = NULL;
-	gboolean require_password = TRUE;
 
 	g_return_if_fail(PURPLE_IS_ACCOUNT(account));
 
@@ -1095,23 +1140,8 @@
 		return;
 	}
 
-	purple_debug_info("account", "Connecting to account %s.\n", username);
-
-	options = purple_protocol_get_options(protocol);
-	if(options & OPT_PROTO_PASSWORD_OPTIONAL) {
-		require_password = purple_account_get_require_password(account);
-	} else if(options & OPT_PROTO_NO_PASSWORD) {
-		require_password = FALSE;
-	}
-
-	if(require_password) {
-		manager = purple_credential_manager_get_default();
-		purple_credential_manager_read_password_async(manager, account, NULL,
-		                                              purple_account_connect_got_password_cb,
-		                                              account);
-	} else {
-		g_timeout_add_seconds(0, no_password_cb, account);
-	}
+	purple_protocol_can_connect_async(protocol, account, NULL,
+	                                  purple_account_can_connect_cb, account);
 }
 
 void
diff --git a/libpurple/purpleprotocol.h b/libpurple/purpleprotocol.h
--- a/libpurple/purpleprotocol.h
+++ b/libpurple/purpleprotocol.h
@@ -317,6 +317,9 @@
  * Most protocol plugins will call [method@Gio.NetworkMonitor.can_reach_async]
  * to determine if a connection is possible.
  *
+ * If [vfunc@Protocol.can_connect_async] is not implemented, this function will
+ * create a [type@Gio.Task] that returns %TRUE.
+ *
  * Since: 3.0
  */
 PURPLE_AVAILABLE_IN_3_0
diff --git a/libpurple/purpleprotocol.c b/libpurple/purpleprotocol.c
--- a/libpurple/purpleprotocol.c
+++ b/libpurple/purpleprotocol.c
@@ -561,6 +561,13 @@
 	if(klass != NULL && klass->can_connect_async != NULL) {
 		klass->can_connect_async(protocol, account, cancellable, callback,
 		                         data);
+	} else {
+		GTask *task = g_task_new(protocol, cancellable, callback, data);
+
+		g_task_return_boolean(task, TRUE);
+		g_task_set_source_tag(task, purple_protocol_can_connect_async);
+
+		g_clear_object(&task);
 	}
 }
 
@@ -569,13 +576,19 @@
                                    GAsyncResult *result,
                                    GError **error)
 {
-	PurpleProtocolClass *klass = NULL;
+	gpointer tag = NULL;
 
 	g_return_val_if_fail(PURPLE_IS_PROTOCOL(protocol), FALSE);
 
-	klass = PURPLE_PROTOCOL_GET_CLASS(protocol);
-	if(klass != NULL && klass->can_connect_finish != NULL) {
-		return klass->can_connect_finish(protocol, result, error);
+	tag = g_task_get_source_tag(G_TASK(result));
+	if(tag == purple_protocol_can_connect_async) {
+		return g_task_propagate_boolean(G_TASK(result), error);
+	} else {
+		PurpleProtocolClass *klass = PURPLE_PROTOCOL_GET_CLASS(protocol);
+
+		if(klass != NULL && klass->can_connect_finish != NULL) {
+			return klass->can_connect_finish(protocol, result, error);
+		}
 	}
 
 	return FALSE;
diff --git a/protocols/bonjour/purplebonjourprotocol.c b/protocols/bonjour/purplebonjourprotocol.c
--- a/protocols/bonjour/purplebonjourprotocol.c
+++ b/protocols/bonjour/purplebonjourprotocol.c
@@ -22,6 +22,7 @@
 
 #include "purplebonjourconnection.h"
 #include "purplebonjourconstants.h"
+#include "purplebonjourcore.h"
 
 struct _PurpleBonjourProtocol {
 	PurpleProtocol parent;
@@ -60,6 +61,41 @@
 	return types;
 }
 
+static void
+purple_bonjour_protocol_can_connect_async(PurpleProtocol *protocol,
+                                          G_GNUC_UNUSED PurpleAccount *account,
+                                          GCancellable *cancellable,
+                                          GAsyncReadyCallback callback,
+                                          gpointer data)
+{
+	GTask *task = NULL;
+	GNetworkMonitor *monitor = NULL;
+	GNetworkConnectivity connectivity = 0;
+
+	task = g_task_new(protocol, cancellable, callback, data);
+
+	/* Bonjour runs on LANs so we just need local connectivity. */
+	monitor = g_network_monitor_get_default();
+	connectivity = g_network_monitor_get_connectivity(monitor);
+
+	if(connectivity >= G_NETWORK_CONNECTIVITY_LOCAL) {
+		g_task_return_boolean(task, TRUE);
+	} else {
+		g_task_return_new_error(task, PURPLE_BONJOUR_DOMAIN, 0,
+		                        _("Network connection not detected."));
+	}
+
+	g_clear_object(&task);
+}
+
+static gboolean
+purple_bonjour_protocol_can_connect_finish(G_GNUC_UNUSED PurpleProtocol *protocol,
+                                           GAsyncResult *result,
+                                           GError **error)
+{
+	return g_task_propagate_boolean(G_TASK(result), error);
+}
+
 static PurpleConnection *
 purple_bonjour_protocol_create_connection(PurpleProtocol *protocol,
                                           PurpleAccount *account,
@@ -99,7 +135,8 @@
 
 	protocol_class->get_account_options = purple_bonjour_protocol_get_account_options;
 	protocol_class->status_types = purple_bonjour_protocol_status_types;
-
+	protocol_class->can_connect_async = purple_bonjour_protocol_can_connect_async;
+	protocol_class->can_connect_finish = purple_bonjour_protocol_can_connect_finish;
 	protocol_class->create_connection = purple_bonjour_protocol_create_connection;
 }
 
diff --git a/protocols/xmpp/purplexmppprotocol.c b/protocols/xmpp/purplexmppprotocol.c
--- a/protocols/xmpp/purplexmppprotocol.c
+++ b/protocols/xmpp/purplexmppprotocol.c
@@ -22,6 +22,7 @@
 
 #include "purplexmppconnection.h"
 #include "purplexmppconstants.h"
+#include "purplexmppcore.h"
 
 struct _PurpleXmppProtocol {
 	PurpleProtocol parent;
@@ -110,6 +111,43 @@
 	return types;
 }
 
+static void
+purple_xmpp_protocol_can_connect_async(PurpleProtocol *protocol,
+                                       G_GNUC_UNUSED PurpleAccount *account,
+                                       GCancellable *cancellable,
+                                       GAsyncReadyCallback callback,
+                                       gpointer data)
+{
+	GTask *task = NULL;
+	GNetworkMonitor *monitor = NULL;
+	GNetworkConnectivity connectivity = 0;
+
+	task = g_task_new(protocol, cancellable, callback, data);
+
+	/* Since we could be connecting to localhost, we only need to verify that
+	 * our connectivity is at least G_NETWORK_CONNECTIVITY_LOCAL.
+	 */
+	monitor = g_network_monitor_get_default();
+	connectivity = g_network_monitor_get_connectivity(monitor);
+
+	if(connectivity >= G_NETWORK_CONNECTIVITY_LOCAL) {
+		g_task_return_boolean(task, TRUE);
+	} else {
+		g_task_return_new_error(task, PURPLE_XMPP_DOMAIN, 0,
+		                        _("Network connection not detected."));
+	}
+
+	g_clear_object(&task);
+}
+
+static gboolean
+purple_xmpp_protocol_can_connect_finish(G_GNUC_UNUSED PurpleProtocol *protocol,
+                                        GAsyncResult *result,
+                                        GError **error)
+{
+	return g_task_propagate_boolean(G_TASK(result), error);
+}
+
 static PurpleConnection *
 purple_xmpp_protocol_create_connection(PurpleProtocol *protocol,
                                        PurpleAccount *account,
@@ -149,7 +187,8 @@
 	protocol_class->get_user_splits = purple_xmpp_protocol_get_user_splits;
 	protocol_class->get_account_options = purple_xmpp_protocol_get_account_options;
 	protocol_class->status_types = purple_xmpp_protocol_status_types;
-
+	protocol_class->can_connect_async = purple_xmpp_protocol_can_connect_async;
+	protocol_class->can_connect_finish = purple_xmpp_protocol_can_connect_finish;
 	protocol_class->create_connection = purple_xmpp_protocol_create_connection;
 }
 
