libpurple/connection.c

Tue, 10 Sep 2024 02:03:43 -0500

author
Gary Kramlich <grim@reaperworld.com>
date
Tue, 10 Sep 2024 02:03:43 -0500
changeset 42931
9e8b9ee3cfbb
parent 42857
5c3c60c07e0e
permissions
-rw-r--r--

Update libpurple for the Purple.Notification deprecations

Testing Done:
Compiled and verified there were no more deprecation warnings for notifications in libpurple.

Bugs closed: PIDGIN-17941

Reviewed at https://reviews.imfreedom.org/r/3478/

/*
 * Purple - Internet Messaging Library
 * Copyright (C) Pidgin Developers <devel@pidgin.im>
 *
 * Purple is the legal property of its developers, whose names are too numerous
 * to list here. Please refer to the COPYRIGHT file distributed with this
 * source distribution.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this library; if not, see <https://www.gnu.org/licenses/>.
 */

#include <glib/gi18n-lib.h>

#include "connection.h"
#include "debug.h"
#include "prefs.h"
#include "proxy.h"
#include "purpleaccount.h"
#include "purpleenums.h"
#include "purpleprivate.h"
#include "purpleprotocolserver.h"
#include "request.h"
#include "signals.h"
#include "util.h"

/**
 * PurpleConnection:
 *
 * Represents an active connection on an account.
 */
typedef struct {
	GObject gparent;

	gchar *id;

	GCancellable *cancellable;

	PurpleProtocol *protocol;     /* The protocol.                     */

	PurpleConnectionState state;  /* The connection state.             */

	PurpleAccount *account;       /* The account being connected to.   */
	char *password;               /* The password used.                */

	char *display_name;           /* How you appear to other people.   */

	/* Wants to Die state.  This is set when the user chooses to log out, or
	 * when the protocol is disconnected and should not be automatically
	 * reconnected (incorrect password, etc.).  Protocols should rely on
	 * purple_connection_error() to set this for them rather than
	 * setting it themselves.
	 * See purple_connection_error_is_fatal()
	 */
	gboolean wants_to_die;

	/* The connection error and its description if an error occurred. */
	PurpleConnectionErrorInfo *error_info;

	guint disconnect_timeout;  /* Timer used for nasty stack tricks. */
} PurpleConnectionPrivate;

enum {
	PROP_0,
	PROP_ID,
	PROP_CANCELLABLE,
	PROP_PROTOCOL,
	PROP_STATE,
	PROP_ACCOUNT,
	PROP_PASSWORD,
	PROP_DISPLAY_NAME,
	N_PROPERTIES,
};

static GParamSpec *properties[N_PROPERTIES] = {NULL, };

static GList *connections = NULL;
static GList *connections_connected = NULL;

static int connections_handle;

G_DEFINE_TYPE_WITH_PRIVATE(PurpleConnection, purple_connection, G_TYPE_OBJECT)

/**************************************************************************
 * Connection API
 **************************************************************************/

/*
 * d:)->-<
 *
 * d:O-\-<
 *
 * d:D-/-<
 *
 * d8D->-< DANCE!
 */

void
purple_connection_set_state(PurpleConnection *connection,
                            PurpleConnectionState state)
{
	PurpleConnectionPrivate *priv = NULL;

	g_return_if_fail(PURPLE_IS_CONNECTION(connection));

	priv = purple_connection_get_instance_private(connection);

	if(priv->state == state) {
		return;
	}

	priv->state = state;

	if(priv->state == PURPLE_CONNECTION_STATE_CONNECTED) {
		PurplePresence *presence;
		GDateTime *timestamp = NULL;
		gboolean emit_online = FALSE;
		gpointer handle = NULL;

		presence = purple_account_get_presence(priv->account);

		/* Set the time the account came online */
		timestamp = g_date_time_new_now_utc();
		purple_presence_set_login_time(presence, timestamp);
		g_date_time_unref(timestamp);

		handle = purple_connections_get_handle();
		purple_signal_emit(handle, "signed-on", connection);
		purple_signal_emit_return_1(handle, "autojoin", connection);

		/* check if connections_connected is NULL, if so we need to emit the
		 * online signal.
		 */
		if(connections_connected == NULL) {
			emit_online = TRUE;
		}

		connections_connected = g_list_append(connections_connected,
		                                      connection);

		if(emit_online) {
			purple_signal_emit(handle, "online");
		}
	}

	g_object_notify_by_pspec(G_OBJECT(connection), properties[PROP_STATE]);
}

void
purple_connection_set_display_name(PurpleConnection *connection,
                                   const gchar *name)
{
	PurpleConnectionPrivate *priv = NULL;

	g_return_if_fail(PURPLE_IS_CONNECTION(connection));

	priv = purple_connection_get_instance_private(connection);

	if(g_set_str(&priv->display_name, name)) {
		g_object_notify_by_pspec(G_OBJECT(connection),
		                         properties[PROP_DISPLAY_NAME]);
	}
}

PurpleConnectionState
purple_connection_get_state(PurpleConnection *connection) {
	PurpleConnectionPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONNECTION(connection),
	                     PURPLE_CONNECTION_STATE_DISCONNECTED);

	priv = purple_connection_get_instance_private(connection);

	return priv->state;
}

PurpleAccount *
purple_connection_get_account(PurpleConnection *connection) {
	PurpleConnectionPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONNECTION(connection), NULL);

	priv = purple_connection_get_instance_private(connection);

	return priv->account;
}

const gchar *
purple_connection_get_id(PurpleConnection *connection) {
	PurpleConnectionPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONNECTION(connection), NULL);

	priv = purple_connection_get_instance_private(connection);

	return priv->id;
}

PurpleProtocol *
purple_connection_get_protocol(PurpleConnection *connection) {
	PurpleConnectionPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONNECTION(connection), NULL);

	priv = purple_connection_get_instance_private(connection);

	return priv->protocol;
}

const char *
purple_connection_get_password(PurpleConnection *connection) {
	PurpleConnectionPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONNECTION(connection), NULL);

	priv = purple_connection_get_instance_private(connection);

	return priv->password;
}

void
purple_connection_set_password(PurpleConnection *connection,
                               const char *password)
{
	PurpleConnectionPrivate *priv = NULL;

	g_return_if_fail(PURPLE_IS_CONNECTION(connection));

	priv = purple_connection_get_instance_private(connection);

	purple_str_wipe(priv->password);
	priv->password = g_strdup(password);

	g_object_notify_by_pspec(G_OBJECT(connection), properties[PROP_PASSWORD]);
}

const char *
purple_connection_get_display_name(PurpleConnection *connection) {
	PurpleConnectionPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONNECTION(connection), NULL);

	priv = purple_connection_get_instance_private(connection);

	return priv->display_name;
}

gboolean
_purple_connection_wants_to_die(PurpleConnection *connection) {
	PurpleConnectionPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONNECTION(connection), FALSE);

	priv = purple_connection_get_instance_private(connection);

	return priv->wants_to_die;
}

static gboolean
purple_connection_disconnect_cb(gpointer data) {
	PurpleAccount *account = data;
	PurpleConnection *connection;

	connection = purple_account_get_connection(account);

	if(PURPLE_IS_CONNECTION(connection)) {
		PurpleConnectionPrivate *priv = NULL;

		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);
		}
	}

	return G_SOURCE_REMOVE;
}

void
purple_connection_error(PurpleConnection *connection,
                        PurpleConnectionError reason,
                        const char *description)
{
	PurpleConnectionPrivate *priv = NULL;

	g_return_if_fail(PURPLE_IS_CONNECTION(connection));

	priv = purple_connection_get_instance_private(connection);

	/* This sanity check relies on PURPLE_CONNECTION_ERROR_OTHER_ERROR
	 * being the last member of the PurpleConnectionError enum in
	 * connection.h; if other reasons are added after it, this check should
	 * be updated.
	 */
	if(reason > PURPLE_CONNECTION_ERROR_OTHER_ERROR) {
		purple_debug_error("connection",
			"purple_connection_error: reason %u isn't a "
			"valid reason\n", reason);
		reason = PURPLE_CONNECTION_ERROR_OTHER_ERROR;
	}

	if(description == NULL) {
		purple_debug_error("connection", "purple_connection_error called with NULL description\n");
		description = _("Unknown error");
	}

	/* If we've already got one error, we don't need any more */
	if(priv->error_info != NULL) {
		return;
	}

	priv->wants_to_die = purple_connection_error_is_fatal(reason);

	purple_debug_info("connection",
	                  "Connection error on %p (reason: %u description: %s)\n",
	                  connection, reason, description);

	priv->error_info = purple_connection_error_info_new(reason, description);

	purple_signal_emit(purple_connections_get_handle(), "connection-error",
	                   connection, reason, description);

	priv->disconnect_timeout = g_timeout_add(0, purple_connection_disconnect_cb,
	                                         priv->account);
}

PurpleConnectionErrorInfo *
purple_connection_get_error_info(PurpleConnection *connection) {
	PurpleConnectionPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONNECTION(connection), NULL);

	priv = purple_connection_get_instance_private(connection);

	return priv->error_info;
}

void
purple_connection_g_error(PurpleConnection *connection, const GError *error) {
	PurpleConnectionError reason;

	if(g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
		/* Not a connection error. Ignore. */
		return;
	}

	if(error->domain == G_TLS_ERROR) {
		switch (error->code) {
			case G_TLS_ERROR_UNAVAILABLE:
				reason = PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT;
				break;
			case G_TLS_ERROR_NOT_TLS:
			case G_TLS_ERROR_HANDSHAKE:
				reason = PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR;
				break;
			case G_TLS_ERROR_BAD_CERTIFICATE:
			case G_TLS_ERROR_CERTIFICATE_REQUIRED:
				reason = PURPLE_CONNECTION_ERROR_CERT_OTHER_ERROR;
				break;
			case G_TLS_ERROR_EOF:
			case G_TLS_ERROR_MISC:
			default:
				reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
		}
	} else if (error->domain == G_IO_ERROR) {
		reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
	} else if (error->domain == PURPLE_CONNECTION_ERROR) {
		reason = error->code;
	} else {
		reason = PURPLE_CONNECTION_ERROR_OTHER_ERROR;
	}

	purple_connection_error(connection, reason, error->message);
}

void
purple_connection_take_error(PurpleConnection *connection, GError *error) {
	purple_connection_g_error(connection, error);
	g_error_free(error);
}

gboolean
purple_connection_error_is_fatal(PurpleConnectionError reason) {
	switch (reason) {
		case PURPLE_CONNECTION_ERROR_NETWORK_ERROR:
		case PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR:
		case PURPLE_CONNECTION_ERROR_CUSTOM_TEMPORARY:
			return FALSE;
		case PURPLE_CONNECTION_ERROR_INVALID_USERNAME:
		case PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED:
		case PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE:
		case PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT:
		case PURPLE_CONNECTION_ERROR_NAME_IN_USE:
		case PURPLE_CONNECTION_ERROR_INVALID_SETTINGS:
		case PURPLE_CONNECTION_ERROR_CERT_NOT_PROVIDED:
		case PURPLE_CONNECTION_ERROR_CERT_UNTRUSTED:
		case PURPLE_CONNECTION_ERROR_CERT_EXPIRED:
		case PURPLE_CONNECTION_ERROR_CERT_NOT_ACTIVATED:
		case PURPLE_CONNECTION_ERROR_CERT_HOSTNAME_MISMATCH:
		case PURPLE_CONNECTION_ERROR_CERT_FINGERPRINT_MISMATCH:
		case PURPLE_CONNECTION_ERROR_CERT_SELF_SIGNED:
		case PURPLE_CONNECTION_ERROR_CERT_OTHER_ERROR:
		case PURPLE_CONNECTION_ERROR_CUSTOM_FATAL:
		case PURPLE_CONNECTION_ERROR_OTHER_ERROR:
			return TRUE;
		default:
			g_return_val_if_reached(TRUE);
	}
}

/**************************************************************************
 * Helpers
 **************************************************************************/
static void
purple_connection_set_id(PurpleConnection *connection, const gchar *id) {
	PurpleConnectionPrivate *priv = NULL;

	priv = purple_connection_get_instance_private(connection);

	if(g_set_str(&priv->id, id)) {
		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 Implementation
 **************************************************************************/
static void
purple_connection_set_property(GObject *obj, guint param_id,
                               const GValue *value, GParamSpec *pspec)
{
	PurpleConnection *connection = PURPLE_CONNECTION(obj);
	PurpleConnectionPrivate *priv = NULL;

	priv = purple_connection_get_instance_private(connection);

	switch (param_id) {
		case PROP_ID:
			purple_connection_set_id(connection, g_value_get_string(value));
			break;
		case PROP_PROTOCOL:
			priv->protocol = g_value_get_object(value);
			break;
		case PROP_STATE:
			purple_connection_set_state(connection, g_value_get_enum(value));
			break;
		case PROP_ACCOUNT:
			purple_connection_set_account(connection,
			                              g_value_get_object(value));
			break;
		case PROP_PASSWORD:
			purple_connection_set_password(connection,
			                               g_value_get_string(value));
			break;
		case PROP_DISPLAY_NAME:
			purple_connection_set_display_name(connection,
			                                   g_value_get_string(value));
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
			break;
	}
}

static void
purple_connection_get_property(GObject *obj, guint param_id, GValue *value,
                               GParamSpec *pspec)
{
	PurpleConnection *connection = PURPLE_CONNECTION(obj);

	switch (param_id) {
		case PROP_ID:
			g_value_set_string(value, purple_connection_get_id(connection));
			break;
		case PROP_CANCELLABLE:
			g_value_set_object(value,
			                   purple_connection_get_cancellable(connection));
			break;
		case PROP_PROTOCOL:
			g_value_set_object(value,
			                   purple_connection_get_protocol(connection));
			break;
		case PROP_STATE:
			g_value_set_enum(value, purple_connection_get_state(connection));
			break;
		case PROP_ACCOUNT:
			g_value_set_object(value,
			                   purple_connection_get_account(connection));
			break;
		case PROP_PASSWORD:
			g_value_set_string(value,
			                   purple_connection_get_password(connection));
			break;
		case PROP_DISPLAY_NAME:
			g_value_set_string(value,
			                   purple_connection_get_display_name(connection));
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
			break;
	}
}

static void
purple_connection_init(PurpleConnection *connection) {
	PurpleConnectionPrivate *priv = NULL;

	priv = purple_connection_get_instance_private(connection);

	priv->cancellable = g_cancellable_new();

	purple_connection_set_state(connection, PURPLE_CONNECTION_STATE_CONNECTING);
	connections = g_list_append(connections, connection);
}

static void
purple_connection_constructed(GObject *object) {
	PurpleConnection *connection = PURPLE_CONNECTION(object);
	PurpleConnectionPrivate *priv = NULL;

	G_OBJECT_CLASS(purple_connection_parent_class)->constructed(object);

	priv = purple_connection_get_instance_private(connection);

	if(priv->id == NULL) {
		gchar *uuid = g_uuid_string_random();

		purple_connection_set_id(connection, uuid);

		g_free(uuid);
	}

	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);

	g_clear_handle_id(&priv->disconnect_timeout, g_source_remove);

	G_OBJECT_CLASS(purple_connection_parent_class)->dispose(obj);
}

static void
purple_connection_finalize(GObject *object) {
	PurpleConnection *connection = PURPLE_CONNECTION(object);
	PurpleConnectionPrivate *priv = NULL;

	priv = purple_connection_get_instance_private(connection);

	g_clear_pointer(&priv->error_info, purple_connection_error_info_free);

	purple_str_wipe(priv->password);
	g_free(priv->display_name);
	g_free(priv->id);

	g_clear_object(&priv->cancellable);

	G_OBJECT_CLASS(purple_connection_parent_class)->finalize(object);
}

static void
purple_connection_class_init(PurpleConnectionClass *klass) {
	GObjectClass *obj_class = G_OBJECT_CLASS(klass);

	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;

	/**
	 * PurpleConnection:id:
	 *
	 * The unique identifier for the connection.
	 *
	 * Since: 3.0
	 */
	properties[PROP_ID] = g_param_spec_string(
		"id", "id",
		"The identifier of the connection",
		NULL,
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleConnection:cancellable:
	 *
	 * A [class@Gio.Cancellable] to be used with the connection.
	 *
	 * This can be passed function that require a cancellable for the
	 * connection.
	 *
	 * Since: 3.0
	 */
	properties[PROP_CANCELLABLE] = g_param_spec_object(
		"cancellable", "cancellable",
		"A cancellable for the connection.",
		G_TYPE_CANCELLABLE,
		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleConnection:protocol:
	 *
	 * The protocol that this connection is for.
	 *
	 * Since: 3.0
	 */
	properties[PROP_PROTOCOL] = g_param_spec_object(
		"protocol", "Protocol",
		"The protocol that the connection is using.",
		PURPLE_TYPE_PROTOCOL,
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleConnection:state:
	 *
	 * The state of the connection.
	 *
	 * Since: 3.0
	 */
	properties[PROP_STATE] = g_param_spec_enum(
		"state", "Connection state",
		"The current state of the connection.",
		PURPLE_TYPE_CONNECTION_STATE, PURPLE_CONNECTION_STATE_DISCONNECTED,
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleConnection:account:
	 *
	 * The account this connection belongs to.
	 *
	 * Since: 3.0
	 */
	properties[PROP_ACCOUNT] = g_param_spec_object(
		"account", "Account",
		"The account using the connection.", PURPLE_TYPE_ACCOUNT,
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleConnection:password:
	 *
	 * The password for this connection.
	 *
	 * This is only stored for reconnections and may go away in the future.
	 *
	 * Since: 3.0
	 */
	properties[PROP_PASSWORD] = g_param_spec_string(
		"password", "Password",
		"The password used for connection.", NULL,
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);

	/**
	 * PurpleConnection:display-name:
	 *
	 * The display name for the account.
	 *
	 * Since: 3.0
	 */
	properties[PROP_DISPLAY_NAME] = g_param_spec_string(
		"display-name", "Display name",
		"Your name that appears to other people.", NULL,
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);

	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
}

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;
	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);

	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);

	/* Dispatch to the connection's disconnect method. */
	klass = PURPLE_CONNECTION_GET_CLASS(connection);
	if(klass != NULL && klass->disconnect != NULL) {
		ret = klass->disconnect(connection, error);
	}

	/* Do the rest of our cleanup. */
	connections = g_list_remove(connections, connection);

	purple_connection_set_state(connection,
	                            PURPLE_CONNECTION_STATE_DISCONNECTED);

	purple_signal_emit(handle, "signed-off", connection);

	purple_account_request_close_with_account(priv->account);
	purple_request_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;
}

GCancellable *
purple_connection_get_cancellable(PurpleConnection *connection) {
	PurpleConnectionPrivate *priv = NULL;

	g_return_val_if_fail(PURPLE_IS_CONNECTION(connection), NULL);

	priv = purple_connection_get_instance_private(connection);

	return priv->cancellable;
}

/**************************************************************************
 * Connections API
 **************************************************************************/

void
purple_connections_disconnect_all(void) {
	GList *l;

	while((l = purple_connections_get_all()) != NULL) {
		PurpleConnection *connection = l->data;
		PurpleConnectionPrivate *priv = NULL;

		priv = purple_connection_get_instance_private(connection);

		priv->wants_to_die = TRUE;

		purple_account_disconnect(priv->account);
	}
}

GList *
purple_connections_get_all(void) {
	return connections;
}

gboolean
purple_connections_is_online(void) {
	return (connections_connected != NULL);
}

void
purple_connections_init(void) {
	void *handle = purple_connections_get_handle();

	purple_signal_register(handle, "online", purple_marshal_VOID, G_TYPE_NONE,
	                       0);

	purple_signal_register(handle, "offline", purple_marshal_VOID, G_TYPE_NONE,
	                       0);

	purple_signal_register(handle, "signing-on",
						 purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
						 PURPLE_TYPE_CONNECTION);

	purple_signal_register(handle, "signed-on",
						 purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
						 PURPLE_TYPE_CONNECTION);

	purple_signal_register(handle, "signing-off",
						 purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
						 PURPLE_TYPE_CONNECTION);

	purple_signal_register(handle, "signed-off",
						 purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
						 PURPLE_TYPE_CONNECTION);

	purple_signal_register(handle, "connection-error",
	                       purple_marshal_VOID__POINTER_INT_POINTER,
	                       G_TYPE_NONE, 3, PURPLE_TYPE_CONNECTION,
	                       PURPLE_TYPE_CONNECTION_ERROR, G_TYPE_STRING);

	purple_signal_register(handle, "autojoin",
	                       purple_marshal_BOOLEAN__POINTER, G_TYPE_NONE, 1,
	                       PURPLE_TYPE_CONNECTION);
}

void
purple_connections_uninit(void) {
	purple_signals_unregister_by_instance(purple_connections_get_handle());
}

void *
purple_connections_get_handle(void) {
	return &connections_handle;
}

mercurial