# HG changeset patch
# Node ID 296d3a51007c02b2289d500da6fe8af452f5f1a0
# Parent  ccd071e7cd830b2f34c510ce58612ea985b39ea5
diff --git a/libpurple/purpleaccount.c b/libpurple/purpleaccount.c
--- a/libpurple/purpleaccount.c
+++ b/libpurple/purpleaccount.c
@@ -66,6 +66,7 @@
 	gboolean remember_pass;
 
 	char *protocol_id;
+	PurpleProtocol *protocol;
 
 	PurpleConnection *gc;
 	gboolean disconnecting;
@@ -95,6 +96,7 @@
 	PROP_ENABLED,
 	PROP_CONNECTION_STATE,
 	PROP_CONNECTION,
+	PROP_PROTOCOL,
 	PROP_PROTOCOL_ID,
 	PROP_USER_INFO,
 	PROP_BUDDY_ICON_PATH,
@@ -143,6 +145,27 @@
 }
 
 static void
+purple_account_set_protocol(PurpleAccount *account, PurpleProtocol *protocol) {
+	g_return_if_fail(PURPLE_IS_ACCOUNT(account));
+
+	if(g_set_object(&account->protocol, protocol)) {
+		GObject *obj = G_OBJECT(account);
+		const char *protocol_id = NULL;
+
+		if(PURPLE_IS_PROTOCOL(protocol)) {
+			protocol_id = purple_protocol_get_id(protocol);
+		}
+
+		g_set_str(&account->protocol_id, protocol_id);
+
+		g_object_freeze_notify(obj);
+		g_object_notify_by_pspec(obj, properties[PROP_PROTOCOL]);
+		g_object_notify_by_pspec(obj, properties[PROP_PROTOCOL_ID]);
+		g_object_thaw_notify(obj);
+	}
+}
+
+static void
 purple_account_free_notify_settings(PurpleAccount *account) {
 	g_return_if_fail(PURPLE_IS_ACCOUNT(account));
 
@@ -620,6 +643,9 @@
 	case PROP_CONNECTION:
 		purple_account_set_connection(account, g_value_get_object(value));
 		break;
+	case PROP_PROTOCOL:
+		purple_account_set_protocol(account, g_value_get_object(value));
+		break;
 	case PROP_PROTOCOL_ID:
 		purple_account_set_protocol_id(account, g_value_get_string(value));
 		break;
@@ -674,6 +700,9 @@
 	case PROP_CONNECTION:
 		g_value_set_object(value, purple_account_get_connection(account));
 		break;
+	case PROP_PROTOCOL:
+		g_value_set_object(value, purple_account_get_protocol(account));
+		break;
 	case PROP_PROTOCOL_ID:
 		g_value_set_string(value, purple_account_get_protocol_id(account));
 		break;
@@ -706,28 +735,6 @@
 purple_account_init(PurpleAccount *account) {
 	account->settings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
 	                                          delete_setting);
-}
-
-static void
-purple_account_constructed(GObject *object) {
-	PurpleAccount *account = PURPLE_ACCOUNT(object);
-
-	G_OBJECT_CLASS(purple_account_parent_class)->constructed(object);
-
-	/* If we didn't get an id, checksum the protocol id and the username. */
-	if(purple_strempty(account->id)) {
-		GChecksum *checksum = NULL;
-
-		checksum = g_checksum_new(G_CHECKSUM_SHA256);
-
-		g_checksum_update(checksum, (const guchar *)account->protocol_id, -1);
-		g_checksum_update(checksum, (const guchar *)account->username, -1);
-
-		purple_account_set_id(account, g_checksum_get_string(checksum));
-
-		g_checksum_free(checksum);
-	}
-
 	account->contact_info = purple_contact_info_new(NULL);
 	account->presence = purple_presence_new();
 }
@@ -765,7 +772,9 @@
 
 	g_free(account->user_info);
 	g_free(account->buddy_icon_path);
+
 	g_free(account->protocol_id);
+	g_clear_object(&account->protocol);
 
 	g_hash_table_destroy(account->settings);
 
@@ -776,7 +785,6 @@
 purple_account_class_init(PurpleAccountClass *klass) {
 	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
 
-	obj_class->constructed = purple_account_constructed;
 	obj_class->dispose = purple_account_dispose;
 	obj_class->finalize = purple_account_finalize;
 	obj_class->get_property = purple_account_get_property;
@@ -912,16 +920,37 @@
 		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
 
 	/**
+	 * PurpleAccount:protocol:
+	 *
+	 * The protocol that this account is using.
+	 *
+	 * This will set [property@Account:protocol-id] to the
+	 * [property@Protocol:id] of the new protocol.
+	 *
+	 * If [property@Account:protocol-id] is set first, this will be lazy
+	 * initialized by the first call to [method@Account.get_protocol].
+	 *
+	 * 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);
+
+	/**
 	 * PurpleAccount:protocol-id:
 	 *
 	 * The identifier of the protocol that this account is using.
 	 *
+	 * If [property@Account:protocol] was set before this changes, it will be
+	 * cleared.
+	 *
 	 * Since: 3.0
 	 */
 	properties[PROP_PROTOCOL_ID] = g_param_spec_string(
 		"protocol-id", NULL, NULL,
 		NULL,
-		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
 
 	/**
 	 * PurpleAccount:proxy-info:
@@ -1078,6 +1107,21 @@
 purple_account_get_id(PurpleAccount *account) {
 	g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL);
 
+	/* If we weren't given an ID during construction, generate one on the fly.
+	 */
+	if(purple_strempty(account->id)) {
+		GChecksum *checksum = NULL;
+
+		checksum = g_checksum_new(G_CHECKSUM_SHA256);
+
+		g_checksum_update(checksum, (const guchar *)account->protocol_id, -1);
+		g_checksum_update(checksum, (const guchar *)account->username, -1);
+
+		purple_account_set_id(account, g_checksum_get_string(checksum));
+
+		g_checksum_free(checksum);
+	}
+
 	return account->id;
 }
 
@@ -1270,10 +1314,18 @@
 {
 	g_return_if_fail(PURPLE_IS_ACCOUNT(account));
 	g_return_if_fail(protocol_id != NULL);
+	g_return_if_fail(purple_account_is_disconnected(account));
 
 	if(g_set_str(&account->protocol_id, protocol_id)) {
-		g_object_notify_by_pspec(G_OBJECT(account),
-		                         properties[PROP_PROTOCOL_ID]);
+		GObject *obj = G_OBJECT(account);
+
+		/* Setting this directly clears any cached protocol. */
+		g_clear_object(&account->protocol);
+
+		g_object_freeze_notify(obj);
+		g_object_notify_by_pspec(obj, properties[PROP_PROTOCOL_ID]);
+		g_object_notify_by_pspec(obj, properties[PROP_PROTOCOL]);
+		g_object_thaw_notify(obj);
 
 		purple_accounts_schedule_save();
 	}
@@ -1472,24 +1524,36 @@
 
 	g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL);
 
-	manager = purple_protocol_manager_get_default();
-	if(manager != NULL) {
-		return purple_protocol_manager_find(manager, account->protocol_id);
+	if(!PURPLE_IS_PROTOCOL(account->protocol)) {
+		manager = purple_protocol_manager_get_default();
+		if(manager != NULL) {
+			PurpleProtocol *protocol = NULL;
+
+			protocol = purple_protocol_manager_find(manager,
+			                                        account->protocol_id);
+
+			purple_account_set_protocol(account, protocol);
+		}
 	}
 
-	return NULL;
+	return account->protocol;
 }
 
 const char *
 purple_account_get_protocol_name(PurpleAccount *account) {
-	PurpleProtocol *p;
+	const char *name = NULL;
 
 	g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL);
 
-	p = purple_account_get_protocol(account);
+	if(PURPLE_IS_PROTOCOL(account->protocol)) {
+		name = purple_protocol_get_name(account->protocol);
+	}
 
-	return (p && purple_protocol_get_name(p) ?
-	        _(purple_protocol_get_name(p)) : _("Unknown"));
+	if(purple_strempty(name)) {
+		name = _("Unknown");
+	}
+
+	return name;
 }
 
 PurpleConnectionState
diff --git a/libpurple/purpleaccount.h b/libpurple/purpleaccount.h
--- a/libpurple/purpleaccount.h
+++ b/libpurple/purpleaccount.h
@@ -455,8 +455,10 @@
  * Returns: The protocol name.
  *
  * Since: 2.0
+ *
+ * Deprecated: 3.0
  */
-PURPLE_AVAILABLE_IN_ALL
+PURPLE_DEPRECATED
 const char *purple_account_get_protocol_name(PurpleAccount *account);
 
 /**
diff --git a/libpurple/tests/meson.build b/libpurple/tests/meson.build
--- a/libpurple/tests/meson.build
+++ b/libpurple/tests/meson.build
@@ -1,4 +1,5 @@
 PROGS = [
+    'account',
     'account_option',
     'account_manager',
     'authorization_request',
diff --git a/libpurple/tests/test_account.c b/libpurple/tests/test_account.c
new file mode 100644
--- /dev/null
+++ b/libpurple/tests/test_account.c
@@ -0,0 +1,239 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+
+#include <purple.h>
+
+/******************************************************************************
+ * Test Protocol
+ *****************************************************************************/
+G_DECLARE_FINAL_TYPE(TestPurpleAccountProtocol, test_purple_account_protocol,
+                     TEST_PURPLE, ACCOUNT_PROTOCOL, PurpleProtocol)
+
+struct _TestPurpleAccountProtocol {
+	PurpleProtocol parent;
+};
+
+G_DEFINE_FINAL_TYPE(TestPurpleAccountProtocol, test_purple_account_protocol,
+                    PURPLE_TYPE_PROTOCOL);
+
+static void
+test_purple_account_protocol_init(G_GNUC_UNUSED TestPurpleAccountProtocol *protocol)
+{
+}
+
+static void
+test_purple_account_protocol_class_init(G_GNUC_UNUSED TestPurpleAccountProtocolClass *klass)
+{
+}
+
+static PurpleProtocol *
+test_purple_account_protocol_new(void) {
+	return g_object_new(
+		test_purple_account_protocol_get_type(),
+		"id", "account-test",
+		"name", "account-test",
+		NULL);
+}
+
+/******************************************************************************
+ * Tests
+ *****************************************************************************/
+static void
+test_purple_account_new(void) {
+	PurpleAccount *account = NULL;
+	PurpleProtocol *protocol = NULL;
+
+	account = purple_account_new("username", "protocol-id");
+	g_assert_true(PURPLE_IS_ACCOUNT(account));
+
+	protocol = purple_account_get_protocol(account);
+	g_assert_null(protocol);
+
+	g_assert_finalize_object(account);
+}
+
+static void
+test_purple_account_new_with_protocol(void) {
+	PurpleAccount *account = NULL;
+	PurpleProtocol *protocol = NULL;
+	PurpleProtocol *protocol1 = NULL;
+	char *protocol_id = NULL;
+	char *username = NULL;
+
+	protocol = test_purple_account_protocol_new();
+
+	account = g_object_new(
+		PURPLE_TYPE_ACCOUNT,
+		"protocol", protocol,
+		"username", "username1",
+		NULL);
+
+	g_assert_true(PURPLE_IS_ACCOUNT(account));
+
+	g_object_get(
+		G_OBJECT(account),
+		"protocol-id", &protocol_id,
+		"protocol", &protocol1,
+		"username", &username,
+		NULL);
+
+	g_assert_cmpstr(protocol_id, ==, "account-test");
+	g_clear_pointer(&protocol_id, g_free);
+
+	g_assert_true(protocol1 == protocol);
+	g_clear_object(&protocol1);
+
+	g_assert_cmpstr(username, ==, "username1");
+	g_clear_pointer(&username, g_free);
+
+	g_assert_finalize_object(account);
+	g_assert_finalize_object(protocol);
+}
+
+static void
+test_purple_account_properties(void) {
+	PurpleAccount *account = NULL;
+	PurpleConnectionState connection_state = PURPLE_CONNECTION_STATE_DISCONNECTED;
+	PurpleContactInfo *contact_info = NULL;
+	PurpleProxyInfo *proxy_info = NULL;
+	PurpleProxyInfo *proxy_info1 = NULL;
+	char *id = NULL;
+	char *username = NULL;
+	char *user_info;
+	char *buddy_icon_path;
+	char *protocol_id;
+	gboolean connected = FALSE;
+	gboolean enabled;
+	gboolean remember_password;
+	gboolean require_password;
+
+	proxy_info = purple_proxy_info_new();
+
+	account = g_object_new(
+		PURPLE_TYPE_ACCOUNT,
+		"buddy-icon-path", "buddy-icon-path1",
+		"enabled", TRUE,
+		"id", "id1",
+		"protocol-id", "protocol-id1",
+		"proxy-info", proxy_info,
+		"remember-password", TRUE,
+		"require-password", TRUE,
+		"user-info", "user-info1",
+		"username", "username1",
+		NULL);
+
+	g_assert_true(PURPLE_IS_ACCOUNT(account));
+
+	g_object_get(
+		G_OBJECT(account),
+		"buddy-icon-path", &buddy_icon_path,
+		"connected", &connected,
+		"connection-state", &connection_state,
+		"contact-info", &contact_info,
+		"enabled", &enabled,
+		"id", &id,
+		"protocol-id", &protocol_id,
+		"proxy-info", &proxy_info1,
+		"remember-password", &remember_password,
+		"require-password", &require_password,
+		"user-info", &user_info,
+		"username", &username,
+		NULL);
+
+	g_assert_cmpstr(buddy_icon_path, ==, "buddy-icon-path1");
+	g_clear_pointer(&buddy_icon_path, g_free);
+
+	g_assert_false(connected);
+
+	g_assert_cmpuint(connection_state, ==, PURPLE_CONNECTION_STATE_DISCONNECTED);
+
+	g_assert_true(PURPLE_IS_CONTACT_INFO(contact_info));
+	g_clear_object(&contact_info);
+
+	g_assert_true(enabled);
+
+	g_assert_cmpstr(id, ==, "id1");
+	g_clear_pointer(&id, g_free);
+
+	g_assert_cmpstr(protocol_id, ==, "protocol-id1");
+	g_clear_pointer(&protocol_id, g_free);
+
+	g_assert_true(proxy_info1 == proxy_info);
+	g_clear_object(&proxy_info1);
+
+	g_assert_true(remember_password);
+
+	g_assert_true(require_password);
+
+	g_assert_cmpstr(user_info, ==, "user-info1");
+	g_clear_pointer(&user_info, g_free);
+
+	g_assert_cmpstr(username, ==, "username1");
+	g_clear_pointer(&username, g_free);
+
+	g_assert_finalize_object(account);
+	g_assert_finalize_object(proxy_info);
+}
+
+static void
+test_purple_account_error(void) {
+	PurpleAccount *account = NULL;
+	GError *error = NULL;
+	GError *error1 = NULL;
+
+	account = purple_account_new("test", "test");
+
+	error1 = purple_account_get_error(account);
+	g_assert_null(error1);
+
+	error = g_error_new_literal(PURPLE_CONNECTION_ERROR, 0, "failed");
+	purple_account_set_error(account, error);
+
+	error1 = purple_account_get_error(account);
+	g_assert_error(error1, PURPLE_CONNECTION_ERROR, 0);
+
+	/* Accounts create a PurpleNotification when their error property is set
+	 * which has a reference cycle to the account. So we need to clear that
+	 * error, to make sure the account gets destroyed properly.
+	 */
+	purple_account_set_error(account, NULL);
+
+	error1 = purple_account_get_error(account);
+	g_assert_no_error(error1);
+
+	g_assert_finalize_object(account);
+}
+
+/******************************************************************************
+ * Main
+ *****************************************************************************/
+int
+main(int argc, char *argv[]) {
+	g_test_init(&argc, &argv, NULL);
+	g_test_set_nonfatal_assertions();
+
+	g_test_add_func("/account/new", test_purple_account_new);
+	g_test_add_func("/account/new-with-protocol",
+	                test_purple_account_new_with_protocol);
+	g_test_add_func("/account/properties", test_purple_account_properties);
+	g_test_add_func("/account/error", test_purple_account_error);
+
+	return g_test_run();
+}
