# HG changeset patch
# Node ID 9c59801d841a
# Parent  5375c1d58c50
diff --git a/libpurple/account.c b/libpurple/account.c
--- a/libpurple/account.c
+++ b/libpurple/account.c
@@ -138,6 +138,46 @@
  * Helpers
  *****************************************************************************/
 static void
+purple_account_real_connect(PurpleAccount *account, const char *password) {
+	PurpleConnection *connection = NULL;
+	PurpleProtocol *protocol = NULL;
+	GError *error = NULL;
+
+	protocol = purple_account_get_protocol(account);
+
+	connection = purple_protocol_create_connection(protocol, account, password,
+	                                               &error);
+	if(error != NULL) {
+		purple_debug_warning("failed to create connection for %s: %s",
+		                     purple_account_get_username(account),
+		                     error->message);
+		g_clear_error(&error);
+
+		return;
+	}
+
+	g_return_if_fail(PURPLE_IS_CONNECTION(connection));
+
+	purple_account_set_connection(account, connection);
+	if(!purple_connection_connect(connection, &error)) {
+		const char *message = "unknown error";
+
+		if(error != NULL && error->message != NULL) {
+			message = error->message;
+		}
+
+		purple_connection_error(connection,
+		                        PURPLE_CONNECTION_ERROR_OTHER_ERROR,
+		                        message);
+
+		g_clear_error(&error);
+	}
+
+	/* Finally remove our reference to the connection. */
+	g_object_unref(connection);
+}
+
+static void
 purple_account_register_got_password_cb(GObject *obj, GAsyncResult *res,
                                         gpointer data)
 {
@@ -148,14 +188,17 @@
 
 	password = purple_credential_manager_read_password_finish(manager, res,
 	                                                          &error);
-
 	if(error != NULL) {
+		/* If we failed to read a password, just log it, as it could not be
+		 * stored yet, in which case we will just prompt the user later in the
+		 * connection process.
+		 */
 		purple_debug_warning("account", "failed to read password: %s",
 		                     error->message);
-		g_error_free(error);
+		g_clear_error(&error);
 	}
 
-	_purple_connection_new(account, TRUE, password);
+	purple_account_real_connect(account, password);
 
 	g_free(password);
 }
@@ -255,7 +298,7 @@
 		                  error != NULL ? error->message : "unknown error");
 	}
 
-	_purple_connection_new(account, FALSE, password);
+	purple_account_real_connect(account, password);
 
 	g_free(password);
 }
@@ -294,7 +337,7 @@
 		                                               request_password_write_cb,
 		                                               account);
 	} else {
-		_purple_connection_new(account, FALSE, entry);
+		purple_account_real_connect(account, entry);
 	}
 }
 
@@ -341,7 +384,7 @@
 			G_CALLBACK(request_password_ok_cb),
 			G_CALLBACK(request_password_cancel_cb), account);
 	} else {
-		_purple_connection_new(account, FALSE, password);
+		purple_account_real_connect(account, password);
 	}
 
 	g_free(password);
@@ -385,7 +428,7 @@
 no_password_cb(gpointer data) {
 	PurpleAccount *account = data;
 
-	_purple_connection_new(account, FALSE, NULL);
+	purple_account_real_connect(account, NULL);
 
 	return G_SOURCE_REMOVE;
 }
@@ -867,6 +910,7 @@
 		purple_account_disconnect(account);
 	}
 
+	g_clear_object(&account->gc);
 	g_clear_object(&account->presence);
 
 	G_OBJECT_CLASS(purple_account_parent_class)->dispose(object);
@@ -1155,6 +1199,7 @@
 void
 purple_account_disconnect(PurpleAccount *account)
 {
+	GError *error = NULL;
 	const char *username;
 
 	g_return_if_fail(PURPLE_IS_ACCOUNT(account));
@@ -1167,6 +1212,14 @@
 
 	account->disconnecting = TRUE;
 
+	if(!purple_connection_disconnect(account->gc, &error)) {
+		g_warning("error while disconnecting account %s (%s): %s",
+		          purple_account_get_username(account),
+		          purple_account_get_protocol_id(account),
+		          (error != NULL) ? error->message : "unknown error");
+		g_clear_error(&error);
+	}
+
 	purple_account_set_connection(account, NULL);
 
 	account->disconnecting = FALSE;
@@ -1416,14 +1469,13 @@
 }
 
 void
-purple_account_set_connection(PurpleAccount *account, PurpleConnection *gc)
-{
+purple_account_set_connection(PurpleAccount *account, PurpleConnection *gc) {
 	g_return_if_fail(PURPLE_IS_ACCOUNT(account));
 
-	g_clear_object(&account->gc);
-	account->gc = gc;
-
-	g_object_notify_by_pspec(G_OBJECT(account), properties[PROP_CONNECTION]);
+	if(g_set_object(&account->gc, gc)) {
+		g_object_notify_by_pspec(G_OBJECT(account),
+		                         properties[PROP_CONNECTION]);
+	}
 }
 
 void
diff --git a/libpurple/connection.h b/libpurple/connection.h
--- a/libpurple/connection.h
+++ b/libpurple/connection.h
@@ -117,6 +117,9 @@
 	/*< private >*/
 	GObjectClass parent;
 
+	gboolean (*connect)(PurpleConnection *connection, GError **error);
+	gboolean (*disconnect)(PurpleConnection *connection, GError **error);
+
 	/*< private >*/
 	gpointer reserved[8];
 };
@@ -182,6 +185,48 @@
 /**************************************************************************/
 
 /**
+ * purple_connection_connect:
+ * @connection: The instance.
+ * @error: (optional): An optional return address for a [type@GLib.GError].
+ *
+ * Tells the connection to connect. This is done by calling the
+ * [vfunc@Purple.Connection.connect] function. State is managed by this
+ * function.
+ *
+ * The default [vfunc@Purple.Connection.connect] is to call
+ * [vfunc@Purple.Protocol.login].
+ *
+ * Due to the asynchronous nature of network connections, the return value and
+ * @error are to be used to do some initial validation before a connection is
+ * actually attempted.
+ *
+ * Returns: %TRUE if the initial connection for @account was successful,
+ *          otherwise %FALSE with @error possibly set.
+ *
+ * Since: 3.0.0
+ */
+gboolean purple_connection_connect(PurpleConnection *connection, GError **error);
+
+/**
+ * purple_connection_disconnect:
+ * @connection: The instance.
+ * @error: (optional): An optional return address for a [type@GLib.GError].
+ *
+ * Tells the connection to disconnect. This is done by calling the
+ * [vfunc@Purple.Connection.disconnect] function. State is managed by this
+ * function.
+ *
+ * The default [vfunc@Purple.Connection.disconnect] is to call
+ * [vfunc@Purple.Protocol.close].
+ *
+ * Returns: %TRUE if the account was disconnected gracefully, otherwise %FALSE
+ *          with @error possibly set.
+ *
+ * Since: 3.0.0
+ */
+gboolean purple_connection_disconnect(PurpleConnection *connection, GError **error);
+
+/**
  * purple_connection_set_state:
  * @gc:    The connection.
  * @state: The connection state.
@@ -531,7 +576,6 @@
  */
 void *purple_connections_get_handle(void);
 
-
 G_END_DECLS
 
 #endif /* PURPLE_CONNECTION_H */
diff --git a/libpurple/connection.c b/libpurple/connection.c
--- a/libpurple/connection.c
+++ b/libpurple/connection.c
@@ -73,8 +73,6 @@
 	 */
 	gboolean wants_to_die;
 
-	gboolean is_finalizing;    /* The object is being destroyed. */
-
 	/* The connection error and its description if an error occurred. */
 	PurpleConnectionErrorInfo *error_info;
 
@@ -441,11 +439,36 @@
 		priv = purple_connection_get_instance_private(connection);
 
 		priv->disconnect_timeout = 0;
+
+		if(priv->state != PURPLE_CONNECTION_STATE_DISCONNECTED) {
+			/* If the connection is not disconnected, disconnect it. */
+			purple_account_disconnect(account);
+		} else {
+			/* Otherwise assume the connection was already disconnected or in
+			 * the process of being disconnected and we just need to finish our
+			 * cleanup.
+			 */
+			GError *error = NULL;
+
+			if(!purple_connection_disconnect(connection, &error)) {
+				const char *message = "unknown error";
+
+				if(error != NULL) {
+					message = error->message;
+				}
+
+				purple_debug_warning("connections",
+				                     "failed to disconnect %p : %s",
+				                     connection, message);
+			}
+
+			g_clear_error(&error);
+
+			purple_account_set_connection(account, NULL);
+		}
 	}
 
-	purple_account_disconnect(account);
-
-	return FALSE;
+	return G_SOURCE_REMOVE;
 }
 
 void
@@ -659,10 +682,52 @@
 	g_object_notify_by_pspec(G_OBJECT(connection), properties[PROP_ID]);
 }
 
+static void
+purple_connection_set_account(PurpleConnection *connection,
+                              PurpleAccount *account)
+{
+	PurpleConnectionPrivate *priv = NULL;
+
+	priv = purple_connection_get_instance_private(connection);
+
+	if(g_set_object(&priv->account, account)) {
+		g_object_notify_by_pspec(G_OBJECT(connection),
+		                         properties[PROP_ACCOUNT]);
+	}
+}
+
 /**************************************************************************
- * GObject code
+ * PurpleConnection Implementation
  **************************************************************************/
+static gboolean
+purple_connection_default_connect(PurpleConnection *connection,
+                                  G_GNUC_UNUSED GError **error)
+{
+	PurpleConnectionPrivate *priv = NULL;
 
+	priv = purple_connection_get_instance_private(connection);
+
+	purple_protocol_login(priv->protocol, priv->account);
+
+	return TRUE;
+}
+
+static gboolean
+purple_connection_default_disconnect(PurpleConnection *connection,
+                                     G_GNUC_UNUSED GError **error)
+{
+	PurpleConnectionPrivate *priv = NULL;
+
+	priv = purple_connection_get_instance_private(connection);
+
+	purple_protocol_close(priv->protocol, connection);
+
+	return TRUE;
+}
+
+/**************************************************************************
+ * GObject Implementation
+ **************************************************************************/
 static void
 purple_connection_set_property(GObject *obj, guint param_id,
                                const GValue *value, GParamSpec *pspec)
@@ -686,7 +751,8 @@
 			purple_connection_set_state(connection, g_value_get_enum(value));
 			break;
 		case PROP_ACCOUNT:
-			priv->account = g_value_get_object(value);
+			purple_connection_set_account(connection,
+			                              g_value_get_object(value));
 			break;
 		case PROP_PASSWORD:
 			purple_connection_set_password(connection,
@@ -763,78 +829,36 @@
 		g_free(uuid);
 	}
 
-	purple_account_set_connection(priv->account, connection);
-
 	purple_signal_emit(purple_connections_get_handle(), "signing-on",
 	                   connection);
 }
 
 static void
+purple_connection_dispose(GObject *obj) {
+	PurpleConnection *connection = PURPLE_CONNECTION(obj);
+	PurpleConnectionPrivate *priv = NULL;
+
+	priv = purple_connection_get_instance_private(connection);
+
+	g_clear_object(&priv->account);
+
+	if(priv->disconnect_timeout > 0) {
+		g_source_remove(priv->disconnect_timeout);
+		priv->disconnect_timeout = 0;
+	}
+
+	G_OBJECT_CLASS(purple_connection_parent_class)->dispose(obj);
+}
+
+static void
 purple_connection_finalize(GObject *object) {
 	PurpleConnection *connection = PURPLE_CONNECTION(object);
 	PurpleConnectionPrivate *priv = NULL;
-	GSList *buddies;
-	gboolean remove = FALSE;
-	gpointer handle;
 
 	priv = purple_connection_get_instance_private(connection);
 
-	priv->is_finalizing = TRUE;
-
-	handle = purple_connections_get_handle();
-
-	purple_debug_info("connection", "Disconnecting connection %p", connection);
-
-	if(priv->state != PURPLE_CONNECTION_STATE_CONNECTING) {
-		remove = TRUE;
-	}
-
-	purple_signal_emit(handle, "signing-off", connection);
-
-	g_slist_free_full(priv->active_chats,
-	                  (GDestroyNotify)purple_chat_conversation_leave);
-
-	update_keepalive(connection, FALSE);
-
-	purple_protocol_close(priv->protocol, connection);
-
-	/* Clear out the proto data that was freed in the protocol's close method */
-	buddies = purple_blist_find_buddies(priv->account, NULL);
-	while (buddies != NULL) {
-		PurpleBuddy *buddy = buddies->data;
-		purple_buddy_set_protocol_data(buddy, NULL);
-		buddies = g_slist_delete_link(buddies, buddies);
-	}
-
-	connections = g_list_remove(connections, connection);
-
-	purple_connection_set_state(connection, PURPLE_CONNECTION_STATE_DISCONNECTED);
-
-	if(remove) {
-		purple_blist_remove_account(priv->account);
-	}
-
-	purple_signal_emit(handle, "signed-off", connection);
-
-	purple_account_request_close_with_account(priv->account);
-	purple_request_close_with_handle(connection);
-	purple_notify_close_with_handle(connection);
-
-	connections_connected = g_list_remove(connections_connected, connection);
-	if(connections_connected == NULL) {
-		purple_signal_emit(handle, "offline");
-	}
-
-	purple_debug_info("connection", "Destroying connection %p", connection);
-
-	purple_account_set_connection(priv->account, NULL);
-
 	g_clear_pointer(&priv->error_info, purple_connection_error_info_free);
 
-	if(priv->disconnect_timeout > 0) {
-		g_source_remove(priv->disconnect_timeout);
-	}
-
 	purple_str_wipe(priv->password);
 	g_free(priv->display_name);
 	g_free(priv->id);
@@ -848,9 +872,13 @@
 
 	obj_class->get_property = purple_connection_get_property;
 	obj_class->set_property = purple_connection_set_property;
+	obj_class->dispose = purple_connection_dispose;
 	obj_class->finalize = purple_connection_finalize;
 	obj_class->constructed = purple_connection_constructed;
 
+	klass->connect = purple_connection_default_connect;
+	klass->disconnect = purple_connection_default_disconnect;
+
 	properties[PROP_ID] = g_param_spec_string(
 		"id", "id",
 		"The identifier of the account",
@@ -894,85 +922,10 @@
 }
 
 void
-_purple_connection_new(PurpleAccount *account, gboolean is_registration,
-                       const gchar *password)
-{
-	PurpleConnection *connection = NULL;
-	PurpleConnectionPrivate *priv = NULL;
-	PurpleProtocol *protocol = NULL;
-
-	g_return_if_fail(PURPLE_IS_ACCOUNT(account));
-
-	if(!purple_account_is_disconnected(account)) {
-		return;
-	}
-
-	protocol = purple_account_get_protocol(account);
-
-	if(protocol == NULL) {
-		gchar *message;
-
-		message = g_strdup_printf(_("Missing protocol for %s"),
-			purple_account_get_username(account));
-		purple_notify_error(NULL, is_registration ? _("Registration Error") :
-			_("Connection Error"), message, NULL,
-			purple_request_cpar_from_account(account));
-		g_free(message);
-
-		return;
-	}
-
-	if(is_registration) {
-		if(!PURPLE_PROTOCOL_IMPLEMENTS(protocol, SERVER, register_user)) {
-			return;
-		}
-	} else {
-		if(((password == NULL) || (*password == '\0')) &&
-		   !(purple_protocol_get_options(protocol) & OPT_PROTO_NO_PASSWORD) &&
-		   !(purple_protocol_get_options(protocol) & OPT_PROTO_PASSWORD_OPTIONAL))
-		{
-			purple_debug_error("connection", "Cannot connect to account %s "
-			                   "without a password.",
-			                   purple_account_get_username(account));
-
-			return;
-		}
-	}
-
-	connection = g_object_new(
-		PURPLE_TYPE_CONNECTION,
-		"protocol", protocol,
-		"account", account,
-		"password", password,
-		NULL);
-
-	g_return_if_fail(connection != NULL);
-
-	priv = purple_connection_get_instance_private(connection);
-
-	if(is_registration) {
-		purple_debug_info("connection", "Registering.  connection = %p",
-		                  connection);
-
-		/* set this so we don't auto-reconnect after registering */
-		priv->wants_to_die = TRUE;
-
-		purple_protocol_server_register_user(PURPLE_PROTOCOL_SERVER(protocol),
-		                                     account);
-	} else {
-		purple_debug_info("connection", "Connecting. connection = %p",
-		                  connection);
-
-		purple_protocol_login(protocol, account);
-	}
-}
-
-void
 _purple_connection_new_unregister(PurpleAccount *account, const char *password,
                                   PurpleAccountUnregistrationCb cb,
                                   gpointer user_data)
 {
-	/* Lots of copy/pasted code to avoid API changes. You might want to integrate that into the previous function when possible. */
 	PurpleConnection *gc;
 	PurpleProtocol *protocol;
 
@@ -984,9 +937,9 @@
 		gchar *message;
 
 		message = g_strdup_printf(_("Missing protocol for %s"),
-								  purple_account_get_username(account));
+		                          purple_account_get_username(account));
 		purple_notify_error(NULL, _("Unregistration Error"), message,
-			NULL, purple_request_cpar_from_account(account));
+		                    NULL, purple_request_cpar_from_account(account));
 		g_free(message);
 		return;
 	}
@@ -1001,8 +954,9 @@
 	   !(purple_protocol_get_options(protocol) & OPT_PROTO_NO_PASSWORD) &&
 	   !(purple_protocol_get_options(protocol) & OPT_PROTO_PASSWORD_OPTIONAL))
 	{
-		purple_debug_error("connection", "Cannot connect to account %s without "
-						   "a password.\n", purple_account_get_username(account));
+		purple_debug_error("connection", "Cannot connect to account %s "
+		                   "without a password.",
+		                   purple_account_get_username(account));
 
 		return;
 	}
@@ -1016,12 +970,131 @@
 
 	g_return_if_fail(gc != NULL);
 
-	purple_debug_info("connection", "Unregistering.  gc = %p\n", gc);
+	purple_debug_info("connection", "Unregistering.  gc = %p", gc);
 
 	purple_protocol_server_unregister_user(PURPLE_PROTOCOL_SERVER(protocol),
 	                                       account, cb, user_data);
 }
 
+gboolean
+purple_connection_connect(PurpleConnection *connection, GError **error) {
+	PurpleConnectionClass *klass = NULL;
+	PurpleConnectionPrivate *priv = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_CONNECTION(connection), FALSE);
+
+	priv = purple_connection_get_instance_private(connection);
+
+	if(!purple_account_is_disconnected(priv->account)) {
+		g_set_error(error, PURPLE_CONNECTION_ERROR, 0,
+		            "account %s is not disconnected",
+		            purple_account_get_username(priv->account));
+
+		return TRUE;
+	}
+
+	if(((priv->password == NULL) || (*priv->password == '\0')) &&
+	   !(purple_protocol_get_options(priv->protocol) & OPT_PROTO_NO_PASSWORD) &&
+	   !(purple_protocol_get_options(priv->protocol) & OPT_PROTO_PASSWORD_OPTIONAL))
+	{
+		g_set_error(error, PURPLE_CONNECTION_ERROR, 0,
+		            "Cannot connect to account %s without a password.",
+		            purple_account_get_username(priv->account));
+
+		return FALSE;
+	}
+
+	purple_debug_info("connection", "Connecting. connection = %p",
+	                  connection);
+
+	klass = PURPLE_CONNECTION_GET_CLASS(connection);
+	if(klass != NULL && klass->connect != NULL) {
+		return klass->connect(connection, error);
+	}
+
+	g_set_error(error, PURPLE_CONNECTION_ERROR, 0,
+	            "The connection for %s did not implement the connect method",
+	            purple_account_get_username(priv->account));
+
+	return FALSE;
+}
+
+gboolean
+purple_connection_disconnect(PurpleConnection *connection, GError **error) {
+	PurpleConnectionClass *klass = NULL;
+	PurpleConnectionPrivate *priv = NULL;
+	GSList *buddies = NULL;
+	gboolean remove = FALSE;
+	gboolean ret = TRUE;
+	gpointer handle = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_CONNECTION(connection), FALSE);
+
+	/* We don't check if the connection's state is connected as everything
+	 * should be idempotent when doing cleanup.
+	 */
+
+	priv = purple_connection_get_instance_private(connection);
+
+	/* If we're not connecting, we'll need to remove stuff from our contacts
+	 * from the buddy list.
+	 */
+	if(priv->state != PURPLE_CONNECTION_STATE_CONNECTING) {
+		remove = TRUE;
+	}
+
+	handle = purple_connections_get_handle();
+
+	purple_debug_info("connection", "Disconnecting connection %p", connection);
+	purple_connection_set_state(connection,
+	                            PURPLE_CONNECTION_STATE_DISCONNECTING);
+	purple_signal_emit(handle, "signing-off", connection);
+
+	g_slist_free_full(priv->active_chats,
+	                  (GDestroyNotify)purple_chat_conversation_leave);
+
+	update_keepalive(connection, FALSE);
+
+	/* Dispatch to the connection's disconnect method. */
+	klass = PURPLE_CONNECTION_GET_CLASS(connection);
+	if(klass != NULL && klass->disconnect != NULL) {
+		ret = klass->disconnect(connection, error);
+	}
+
+	/* Clear out the proto data that was freed in the protocol's close method */
+	buddies = purple_blist_find_buddies(priv->account, NULL);
+	while (buddies != NULL) {
+		PurpleBuddy *buddy = buddies->data;
+		purple_buddy_set_protocol_data(buddy, NULL);
+		buddies = g_slist_delete_link(buddies, buddies);
+	}
+
+	/* Do the rest of our cleanup. */
+	connections = g_list_remove(connections, connection);
+
+	purple_connection_set_state(connection,
+	                            PURPLE_CONNECTION_STATE_DISCONNECTED);
+
+	if(remove) {
+		purple_blist_remove_account(priv->account);
+	}
+
+	purple_signal_emit(handle, "signed-off", connection);
+
+	purple_account_request_close_with_account(priv->account);
+	purple_request_close_with_handle(connection);
+	purple_notify_close_with_handle(connection);
+
+	connections_connected = g_list_remove(connections_connected, connection);
+	if(connections_connected == NULL) {
+		purple_signal_emit(handle, "offline");
+	}
+
+	purple_debug_info("connection", "Destroying connection %p", connection);
+
+	return ret;
+}
+
 /**************************************************************************
  * Connections API
  **************************************************************************/
diff --git a/libpurple/purpleprivate.h b/libpurple/purpleprivate.h
--- a/libpurple/purpleprivate.h
+++ b/libpurple/purpleprivate.h
@@ -83,26 +83,6 @@
 _purple_buddy_icons_blist_loaded_cb(void);
 
 /**
- * _purple_connection_new:
- * @account:  The account the connection should be connecting to.
- * @is_registration: Whether we are registering a new account or just trying to
- *                   do a normal signon.
- * @password: The password to use.
- *
- * Creates a connection to the specified account and either connects
- * or attempts to register a new account.  If you are logging in,
- * the connection uses the current active status for this account.
- * So if you want to sign on as "away," for example, you need to
- * have called purple_account_set_status(account, "away").
- * (And this will call purple_account_connect() automatically).
- *
- * Note: This function should only be called by purple_account_connect()
- *       in account.c.  If you're trying to sign on an account, use that
- *       function instead.
- */
-void _purple_connection_new(PurpleAccount *account, gboolean is_registration,
-                            const gchar *password);
-/**
  * _purple_connection_new_unregister:
  * @account:  The account to unregister
  * @password: The password to use.
diff --git a/libpurple/purpleprotocol.h b/libpurple/purpleprotocol.h
--- a/libpurple/purpleprotocol.h
+++ b/libpurple/purpleprotocol.h
@@ -145,6 +145,8 @@
 
 	void (*close)(PurpleProtocol *protocol, PurpleConnection *connection);
 
+	PurpleConnection *(*create_connection)(PurpleProtocol *protocol, PurpleAccount *account, const char *password, GError **error);
+
 	GList *(*status_types)(PurpleProtocol *protocol, PurpleAccount *account);
 
 	/*< private >*/
@@ -289,6 +291,24 @@
 void purple_protocol_close(PurpleProtocol *protocol, PurpleConnection *connection);
 
 /**
+ * purple_protocol_create_connection:
+ * @protocol: The instance.
+ * @account: The [class@Purple.Account] for the connection.
+ * @password: The password for the account.
+ * @error: A return address for a [type@GLib.GError].
+ *
+ * Creates a [class@PurpleConnection] for @account.
+ *
+ * A protocol may indicate an error by setting @error and returning %NULL.
+ *
+ * Returns: (transfer full): The new connection or %NULL with @error possibly
+ *          set on error.
+ *
+ * Since: 3.0.0
+ */
+PurpleConnection *purple_protocol_create_connection(PurpleProtocol *protocol, PurpleAccount *account, const char *password, GError **error);
+
+/**
  * purple_protocol_get_status_types:
  * @protocol: The #PurpleProtocol instance.
  * @account: The #PurpleAccount instance.
diff --git a/libpurple/purpleprotocol.c b/libpurple/purpleprotocol.c
--- a/libpurple/purpleprotocol.c
+++ b/libpurple/purpleprotocol.c
@@ -165,6 +165,23 @@
 }
 
 /******************************************************************************
+ * PurpleProtocol Implementation
+ *****************************************************************************/
+static PurpleConnection *
+purple_protocol_default_create_connection(PurpleProtocol *protocol,
+                                          PurpleAccount *account,
+                                          const char *password,
+                                          G_GNUC_UNUSED GError **error)
+{
+	return g_object_new(
+		PURPLE_TYPE_CONNECTION,
+		"protocol", protocol,
+		"account", account,
+		"password", password,
+		NULL);
+}
+
+/******************************************************************************
  * GObject Implementation
  *****************************************************************************/
 static void
@@ -286,6 +303,8 @@
 	obj_class->set_property = purple_protocol_set_property;
 	obj_class->finalize = purple_protocol_finalize;
 
+	klass->create_connection = purple_protocol_default_create_connection;
+
 	/**
 	 * PurpleProtocol::id:
 	 *
@@ -548,6 +567,30 @@
 	}
 }
 
+
+PurpleConnection *
+purple_protocol_create_connection(PurpleProtocol *protocol,
+                                  PurpleAccount *account,
+                                  const char *password,
+                                  GError **error)
+{
+	PurpleProtocolClass *klass = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_PROTOCOL(protocol), NULL);
+	g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL);
+
+	klass = PURPLE_PROTOCOL_GET_CLASS(protocol);
+	if(klass != NULL && klass->create_connection != NULL) {
+		return klass->create_connection(protocol, account, password, error);
+	}
+
+	g_set_error(error, PURPLE_CONNECTION_ERROR, 0,
+	            "Protocol %s did not implement create_connection",
+	            purple_protocol_get_name(protocol));
+
+	return NULL;
+}
+
 GList *
 purple_protocol_get_status_types(PurpleProtocol *protocol,
                                  PurpleAccount *account)
