Tue, 12 Sep 2023 01:42:13 -0500
Add online and federated properties to PurpleConversation
This will allow user interfaces to more gracefully handle conversations with
disconnected accounts and offline conversations.
I didn't create a `purple_conversation_new_federated` as we don't have a `purple_conversation_new` yet. I'm waiting on both of those until `PurpleConversation` is a final type.
Testing Done:
Compiled and ran the unit tests only. Writing unit tests for this right now would require a custom `PurpleProtocol` as well as a custom `PurpleConnection` and maybe more. So I skipped for the time being as this is pretty straight forward.
Reviewed at https://reviews.imfreedom.org/r/2609/
--- a/ChangeLog.API Tue Sep 05 01:08:41 2023 -0500 +++ b/ChangeLog.API Tue Sep 12 01:42:13 2023 -0500 @@ -499,6 +499,7 @@ * purple_conversation_get_remote_smileys * purple_conversation_get_smiley * purple_conversation_is_logging + * purple_conversation_set_account * purple_conversations_add. Use purple_conversation_manager_register instead. * purple_conversations_remove. Use
--- a/libpurple/account.c Tue Sep 05 01:08:41 2023 -0500 +++ b/libpurple/account.c Tue Sep 12 01:42:13 2023 -0500 @@ -807,26 +807,12 @@ static void purple_account_finalize(GObject *object) { - GList *l; PurpleAccount *account = PURPLE_ACCOUNT(object); - PurpleConversationManager *manager = NULL; purple_debug_info("account", "Destroying account %p", account); purple_account_free_notify_settings(account); - manager = purple_conversation_manager_get_default(); - l = purple_conversation_manager_get_all(manager); - while(l != NULL) { - PurpleConversation *conv = PURPLE_CONVERSATION(l->data); - - if (purple_conversation_get_account(conv) == account) { - purple_conversation_set_account(conv, NULL); - } - - l = g_list_delete_link(l, l); - } - purple_account_set_status_types(account, NULL); g_clear_object(&account->proxy_info);
--- a/libpurple/purpleconversation.c Tue Sep 05 01:08:41 2023 -0500 +++ b/libpurple/purpleconversation.c Tue Sep 12 01:42:13 2023 -0500 @@ -52,6 +52,8 @@ gboolean favorite; GDateTime *created_on; PurpleContactInfo *creator; + gboolean online; + gboolean federated; PurpleTags *tags; GListStore *members; @@ -75,6 +77,8 @@ PROP_FAVORITE, PROP_CREATED_ON, PROP_CREATOR, + PROP_ONLINE, + PROP_FEDERATED, PROP_TAGS, PROP_MEMBERS, PROP_MESSAGES, @@ -92,6 +96,10 @@ G_DEFINE_TYPE_WITH_PRIVATE(PurpleConversation, purple_conversation, G_TYPE_OBJECT); +static void purple_conversation_account_connected_cb(GObject *obj, + GParamSpec *pspec, + gpointer data); + /************************************************************************** * Helpers **************************************************************************/ @@ -111,6 +119,45 @@ } } +static void +purple_conversation_set_account(PurpleConversation *conv, + PurpleAccount *account) +{ + PurpleConversationPrivate *priv = NULL; + + g_return_if_fail(PURPLE_IS_CONVERSATION(conv)); + priv = purple_conversation_get_instance_private(conv); + + if(g_set_object(&priv->account, account)) { + if(PURPLE_IS_ACCOUNT(priv->account)) { + g_signal_connect_object(account, "notify::connected", + G_CALLBACK(purple_conversation_account_connected_cb), + conv, 0); + } + + purple_conversation_update(conv, PURPLE_CONVERSATION_UPDATE_ACCOUNT); + + g_object_notify_by_pspec(G_OBJECT(conv), properties[PROP_ACCOUNT]); + } +} + +static void +purple_conversation_set_federated(PurpleConversation *conversation, + gboolean federated) +{ + PurpleConversationPrivate *priv = NULL; + + g_return_if_fail(PURPLE_IS_CONVERSATION(conversation)); + + priv = purple_conversation_get_instance_private(conversation); + if(priv->federated != federated) { + priv->federated = federated; + + g_object_notify_by_pspec(G_OBJECT(conversation), + properties[PROP_FEDERATED]); + } +} + static gboolean purple_conversation_check_member_equal(gconstpointer a, gconstpointer b) { PurpleConversationMember *member_a = (PurpleConversationMember *)a; @@ -262,6 +309,33 @@ } /************************************************************************** + * Callbacks + **************************************************************************/ +static void +purple_conversation_account_connected_cb(GObject *obj, + G_GNUC_UNUSED GParamSpec *pspec, + gpointer data) +{ + PurpleConversation *conversation = data; + PurpleConversationPrivate *priv = NULL; + gboolean connected = purple_account_is_connected(PURPLE_ACCOUNT(obj)); + + priv = purple_conversation_get_instance_private(conversation); + + if(priv->federated) { + /* If the account changed to connected and the conversation is + * federated we do nothing. But if the account went offline, we can + * safely set the conversation to offline. + */ + if(!connected) { + purple_conversation_set_online(conversation, FALSE); + } + } else { + purple_conversation_set_online(conversation, connected); + } +} + +/************************************************************************** * GObject Implementation **************************************************************************/ static void @@ -325,6 +399,13 @@ case PROP_CREATOR: purple_conversation_set_creator(conv, g_value_get_object(value)); break; + case PROP_ONLINE: + purple_conversation_set_online(conv, g_value_get_boolean(value)); + break; + case PROP_FEDERATED: + purple_conversation_set_federated(conv, + g_value_get_boolean(value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); break; @@ -389,6 +470,13 @@ case PROP_CREATOR: g_value_set_object(value, purple_conversation_get_creator(conv)); break; + case PROP_ONLINE: + g_value_set_boolean(value, purple_conversation_get_online(conv)); + break; + case PROP_FEDERATED: + g_value_set_boolean(value, + purple_conversation_get_federated(conv)); + break; case PROP_TAGS: g_value_set_object(value, purple_conversation_get_tags(conv)); break; @@ -550,7 +638,7 @@ "account", "Account", "The account for the conversation.", PURPLE_TYPE_ACCOUNT, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); properties[PROP_NAME] = g_param_spec_string( "name", "Name", @@ -711,6 +799,50 @@ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** + * PurpleConversation:online: + * + * Whether or not the conversation is able to send and receive messages. + * + * This is typically tied to whether or not the account that this + * conversation belongs is online or not. + * + * However, if a protocol supports federated conversation, it is possible + * for a conversation to be offline if the server it is on is currently + * unreachable. + * + * See Also: [property@Conversation:federated]. + * + * Since: 3.0.0 + */ + properties[PROP_ONLINE] = g_param_spec_boolean( + "online", "online", + "Whether or not the conversation can send and receive messages.", + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * PurpleConversation:federated: + * + * Whether or this conversation is federated. + * + * This should only be set by protocols that support federated + * conversations. + * + * When this is %TRUE the [property@Conversation:online] property will not + * be automatically set to match the [property@Account:connected] property + * of the account that this conversation belongs to. It is the + * responsibility of the protocol to manage the online property in this + * case. + * + * Since: 3.0.0 + */ + properties[PROP_FEDERATED] = g_param_spec_boolean( + "federated", "federated", + "Whether or not this conversation is federated.", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + /** * PurpleConversation:tags: * * [class@Tags] for the conversation. @@ -917,22 +1049,6 @@ } } -void -purple_conversation_set_account(PurpleConversation *conv, - PurpleAccount *account) -{ - PurpleConversationPrivate *priv = NULL; - - g_return_if_fail(PURPLE_IS_CONVERSATION(conv)); - priv = purple_conversation_get_instance_private(conv); - - if(g_set_object(&priv->account, account)) { - g_object_notify_by_pspec(G_OBJECT(conv), properties[PROP_ACCOUNT]); - - purple_conversation_update(conv, PURPLE_CONVERSATION_UPDATE_ACCOUNT); - } -} - PurpleAccount * purple_conversation_get_account(PurpleConversation *conv) { PurpleConversationPrivate *priv = NULL; @@ -1582,6 +1698,45 @@ } } +gboolean +purple_conversation_get_online(PurpleConversation *conversation) { + PurpleConversationPrivate *priv = NULL; + + g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), FALSE); + + priv = purple_conversation_get_instance_private(conversation); + + return priv->online; +} + +void +purple_conversation_set_online(PurpleConversation *conversation, + gboolean online) +{ + PurpleConversationPrivate *priv = NULL; + + g_return_if_fail(PURPLE_IS_CONVERSATION(conversation)); + + priv = purple_conversation_get_instance_private(conversation); + if(priv->online != online) { + priv->online = online; + + g_object_notify_by_pspec(G_OBJECT(conversation), + properties[PROP_ONLINE]); + } +} + +gboolean +purple_conversation_get_federated(PurpleConversation *conversation) { + PurpleConversationPrivate *priv = NULL; + + g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), FALSE); + + priv = purple_conversation_get_instance_private(conversation); + + return priv->federated; +} + PurpleTags * purple_conversation_get_tags(PurpleConversation *conversation) { PurpleConversationPrivate *priv = NULL;
--- a/libpurple/purpleconversation.h Tue Sep 05 01:08:41 2023 -0500 +++ b/libpurple/purpleconversation.h Tue Sep 12 01:42:13 2023 -0500 @@ -216,18 +216,6 @@ void purple_conversation_set_conversation_type(PurpleConversation *conversation, PurpleConversationType type); /** - * purple_conversation_set_account: - * @conv: The conversation. - * @account: The purple_account. - * - * Sets the specified conversation's purple_account. - * - * This purple_account represents the user using purple, not the person the user - * is having a conversation/chat/flame with. - */ -void purple_conversation_set_account(PurpleConversation *conv, PurpleAccount *account); - -/** * purple_conversation_get_account: * @conv: The conversation. * @@ -649,6 +637,50 @@ void purple_conversation_set_creator(PurpleConversation *conversation, PurpleContactInfo *creator); /** + * purple_conversation_get_online: + * @conversation: The instance. + * + * Gets whether or not @conversation is online. + * + * A conversation is considered offline when messages cannot be sent or + * received. + * + * Returns: %TRUE if messages can be sent and received from @conversation. + * + * Since: 3.0.0 + */ +gboolean purple_conversation_get_online(PurpleConversation *conversation); + +/** + * purple_conversation_set_online: + * @conversation: The instance. + * @online: Whether or not the conversation is online. + * + * Sets whether or not the conversation is online, or able to send and receive + * messages. + * + * Since: 3.0.0 + */ +void purple_conversation_set_online(PurpleConversation *conversation, gboolean online); + +/** + * purple_conversation_get_federated: + * @conversation: The instance. + * + * Gets whether or not @conversation is federated. + * + * Depending on the underlying protocol, conversations can be federated, which + * means they can be running on a different server. When a protocol creates a + * federated conversation, it takes the responsibility of managing the + * [property@Conversation:online] state of the conversation. + * + * Returns: %TRUE if @conversation is federated otherwise %FALSE. + * + * Since: 3.0.0 + */ +gboolean purple_conversation_get_federated(PurpleConversation *conversation); + +/** * purple_conversation_get_tags: * @conversation: The instance. *