--- 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 @@
*****************************************************************************/
+purple_account_real_connect(PurpleAccount *account, const char *password) { + PurpleConnection *connection = NULL; + PurpleProtocol *protocol = NULL; + protocol = purple_account_get_protocol(account); + connection = purple_protocol_create_connection(protocol, account, password, + purple_debug_warning("failed to create connection for %s: %s", + purple_account_get_username(account), + 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, + /* Finally remove our reference to the connection. */ + g_object_unref(connection); purple_account_register_got_password_cb(GObject *obj, GAsyncResult *res,
@@ -148,14 +188,17 @@
password = purple_credential_manager_read_password_finish(manager, res,
+ /* 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 purple_debug_warning("account", "failed to read password: %s",
- _purple_connection_new(account, TRUE, password);
+ purple_account_real_connect(account, password); @@ -255,7 +298,7 @@
error != NULL ? error->message : "unknown error");
- _purple_connection_new(account, FALSE, password);
+ purple_account_real_connect(account, password); @@ -294,7 +337,7 @@
request_password_write_cb,
- _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);
- _purple_connection_new(account, FALSE, password);
+ purple_account_real_connect(account, 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); @@ -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 @@
purple_account_disconnect(PurpleAccount *account)
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"); purple_account_set_connection(account, NULL);
account->disconnecting = FALSE;
@@ -1416,14 +1469,13 @@
-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);
- 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]); --- 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 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); + /* Otherwise assume the connection was already disconnected or in + * the process of being disconnected and we just need to finish our + if(!purple_connection_disconnect(connection, &error)) { + const char *message = "unknown error"; + message = error->message; + purple_debug_warning("connections", + "failed to disconnect %p : %s", + purple_account_set_connection(account, NULL); - purple_account_disconnect(account);
+ return G_SOURCE_REMOVE; @@ -659,10 +682,52 @@
g_object_notify_by_pspec(G_OBJECT(connection), properties[PROP_ID]);
+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]); /**************************************************************************
+ * PurpleConnection Implementation **************************************************************************/
+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); +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); +/************************************************************************** + * GObject Implementation + **************************************************************************/ 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));
- priv->account = g_value_get_object(value);
+ purple_connection_set_account(connection, + g_value_get_object(value)); purple_connection_set_password(connection,
@@ -763,78 +829,36 @@
- purple_account_set_connection(priv->account, connection);
purple_signal_emit(purple_connections_get_handle(), "signing-on",
+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); purple_connection_finalize(GObject *object) {
PurpleConnection *connection = PURPLE_CONNECTION(object);
PurpleConnectionPrivate *priv = NULL;
- gboolean remove = FALSE;
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) {
- 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);
- 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);
@@ -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(
"The identifier of the account",
@@ -894,85 +922,10 @@
-_purple_connection_new(PurpleAccount *account, gboolean is_registration,
- PurpleConnection *connection = NULL;
- PurpleConnectionPrivate *priv = NULL;
- PurpleProtocol *protocol = NULL;
- g_return_if_fail(PURPLE_IS_ACCOUNT(account));
- if(!purple_account_is_disconnected(account)) {
- protocol = purple_account_get_protocol(account);
- 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));
- if(!PURPLE_PROTOCOL_IMPLEMENTS(protocol, SERVER, register_user)) {
- 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 "
- purple_account_get_username(account));
- connection = g_object_new(
- PURPLE_TYPE_CONNECTION,
- g_return_if_fail(connection != NULL);
- priv = purple_connection_get_instance_private(connection);
- purple_debug_info("connection", "Registering. connection = %p",
- /* set this so we don't auto-reconnect after registering */
- priv->wants_to_die = TRUE;
- purple_protocol_server_register_user(PURPLE_PROTOCOL_SERVER(protocol),
- purple_debug_info("connection", "Connecting. connection = %p",
- purple_protocol_login(protocol, account);
_purple_connection_new_unregister(PurpleAccount *account, const char *password,
PurpleAccountUnregistrationCb cb,
- /* Lots of copy/pasted code to avoid API changes. You might want to integrate that into the previous function when possible. */
PurpleProtocol *protocol;
@@ -984,9 +937,9 @@
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)); @@ -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 " + purple_account_get_username(account)); @@ -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),
+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)); + 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)); + purple_debug_info("connection", "Connecting. connection = %p", + 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)); +purple_connection_disconnect(PurpleConnection *connection, GError **error) { + PurpleConnectionClass *klass = NULL; + PurpleConnectionPrivate *priv = NULL; + GSList *buddies = NULL; + gboolean remove = FALSE; + 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 + if(priv->state != PURPLE_CONNECTION_STATE_CONNECTING) { + 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); + 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); /**************************************************************************
**************************************************************************/
--- 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 @@
+ gboolean (*connect)(PurpleConnection *connection, GError **error); + gboolean (*disconnect)(PurpleConnection *connection, GError **error); @@ -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 + * 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 + * Returns: %TRUE if the initial connection for @account was successful, + * otherwise %FALSE with @error possibly set. +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 + * 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. +gboolean purple_connection_disconnect(PurpleConnection *connection, GError **error); * purple_connection_set_state:
* @state: The connection state.
@@ -531,7 +576,6 @@
void *purple_connections_get_handle(void);
#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
- * @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
-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, + G_GNUC_UNUSED GError **error) + PURPLE_TYPE_CONNECTION, +/****************************************************************************** *****************************************************************************/
@@ -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; @@ -548,6 +567,30 @@
+purple_protocol_create_connection(PurpleProtocol *protocol, + PurpleAccount *account, + 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)); purple_protocol_get_status_types(PurpleProtocol *protocol,
--- 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);
@@ -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 +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.