Sun, 10 Nov 2024 00:55:40 -0600
Add Purple.Account:protocol
This is construct only property which allows us to do more complicated testing.
This also caches lookups based on Purple.Account:protocol-id.
Testing Done:
* Opened in a devenv and verified my accounts worked.
* Started creating a new account and verified that changing the protocol didn't throw any warnings.
* Modified an existing account by changing its protocol without issue.
* Deprecated purple_account_get_protocol_name as there's no good reason for it.
* Called in the turtles.
Reviewed at https://reviews.imfreedom.org/r/3643/
--- a/libpurple/purpleaccount.c Mon Nov 04 23:42:22 2024 -0600 +++ b/libpurple/purpleaccount.c Sun Nov 10 00:55:40 2024 -0600 @@ -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
--- a/libpurple/purpleaccount.h Mon Nov 04 23:42:22 2024 -0600 +++ b/libpurple/purpleaccount.h Sun Nov 10 00:55:40 2024 -0600 @@ -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); /**
--- a/libpurple/tests/meson.build Mon Nov 04 23:42:22 2024 -0600 +++ b/libpurple/tests/meson.build Sun Nov 10 00:55:40 2024 -0600 @@ -1,4 +1,5 @@ PROGS = [ + 'account', 'account_option', 'account_manager', 'authorization_request',
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/tests/test_account.c Sun Nov 10 00:55:40 2024 -0600 @@ -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_no_error(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(); +}