libpurple/connection.c

Tue, 15 Oct 2024 00:47:42 -0500

author
Elliott Sales de Andrade <quantum.analyst@gmail.com>
date
Tue, 15 Oct 2024 00:47:42 -0500
changeset 43011
ce3144e2bc33
parent 42999
5a506dee26d2
permissions
-rw-r--r--

Port prefs to AdwSwitchRow

Now that we depend on Adwaita 1.4, we can flip the switch on using these (pun intended).

This also simplifies some extra tracking we needed to do for activations and focus, since the Adwaita widgets do that for us.

Testing Done:
Opened prefs, confirmed all the switches were there, and toggled them all without any warnings.

Also used the mnemonics to toggle the switches from the keyboard.

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

/*
 * 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 "request.h"
#include "util.h"

typedef struct {
	GObject gparent;

	char *id;

	GCancellable *cancellable;

	PurpleProtocol *protocol;     /* The protocol.                     */

	PurpleConnectionState state;  /* The connection state.             */

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

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

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

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;

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

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

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 char *
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]);
}

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

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) {
		char *uuid = g_uuid_string_random();

		purple_connection_set_id(connection, uuid);

		g_free(uuid);
	}
}

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

	purple_str_wipe(priv->password);
	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", NULL, NULL,
		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", NULL, NULL,
		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", NULL, NULL,
		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", NULL, NULL,
		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", NULL, NULL,
		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", NULL, NULL,
		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;

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

	purple_debug_info("connection", "Disconnecting connection %p", connection);
	purple_connection_set_state(connection,
	                            PURPLE_CONNECTION_STATE_DISCONNECTING);

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

	purple_connection_set_state(connection,
	                            PURPLE_CONNECTION_STATE_DISCONNECTED);

	purple_account_request_close_with_account(priv->account);
	purple_request_close_with_handle(connection);

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

mercurial