pidgin/pidgin

Parents e69cbf7921ec
Children 7668d023ae05
Change the way accounts connect while keeping backwards compatibility

This adds a new method to `PurpleProtocol` named `create_connection` which allows
a protocol to return a subclassed `PurpleConnection` so it can avoid using
`purple_connection_[gs]et_protocol_data`. The default implementation for this
method, just creates a `PurpleConnection` with the given protocol, account,
and password.

This also adds two new methods to `PurpleConnection`; `connect` and
`disconnect`. The default implementation for these two just call
`purple_protocol_login` and `purple_protocol_close` respectively.

The long term intent of this, is to have all protocol plugins subclass
`PurpleConnection` and then remove the `login` and `close` methods from
`PurpleProtocol` and also remove `purple_connection_[gs]et_protocol_data`.

Testing Done:
Connected the demo account and used the account actions to test reconnecting.
Connected an XMPP account after running and at startup.
Verified that the infinite loop bug on error due to connection failure is now fixed as well.

Reviewed at https://reviews.imfreedom.org/r/1967/
--- a/libpurple/account.c Mon Oct 31 22:50:20 2022 -0500
+++ b/libpurple/account.c Mon Oct 31 22:52:24 2022 -0500
@@ -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
--- a/libpurple/connection.c Mon Oct 31 22:50:20 2022 -0500
+++ b/libpurple/connection.c Mon Oct 31 22:52:24 2022 -0500
@@ -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
**************************************************************************/
--- a/libpurple/connection.h Mon Oct 31 22:50:20 2022 -0500
+++ b/libpurple/connection.h Mon Oct 31 22:52:24 2022 -0500
@@ -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 */
--- a/libpurple/purpleprivate.h Mon Oct 31 22:50:20 2022 -0500
+++ b/libpurple/purpleprivate.h Mon Oct 31 22:52:24 2022 -0500
@@ -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.
--- a/libpurple/purpleprotocol.c Mon Oct 31 22:50:20 2022 -0500
+++ b/libpurple/purpleprotocol.c Mon Oct 31 22:52:24 2022 -0500
@@ -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)
--- a/libpurple/purpleprotocol.h Mon Oct 31 22:50:20 2022 -0500
+++ b/libpurple/purpleprotocol.h Mon Oct 31 22:52:24 2022 -0500
@@ -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.