Add online and federated properties to PurpleConversation

Tue, 12 Sep 2023 01:42:13 -0500

author
Gary Kramlich <grim@reaperworld.com>
date
Tue, 12 Sep 2023 01:42:13 -0500
changeset 42318
3b05e7088b61
parent 42317
8a8e85e9e114
child 42319
98931e2d3ca2

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/

ChangeLog.API file | annotate | diff | comparison | revisions
libpurple/account.c file | annotate | diff | comparison | revisions
libpurple/purpleconversation.c file | annotate | diff | comparison | revisions
libpurple/purpleconversation.h file | annotate | diff | comparison | revisions
--- 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.
  *

mercurial