Delete conversations from the database when the user leaves them

Wed, 16 Jul 2025 01:08:36 -0500

author
Gary Kramlich <grim@reaperworld.com>
date
Wed, 16 Jul 2025 01:08:36 -0500
changeset 43289
b39dbed64dc0
parent 43288
2865cd340992
child 43290
01edcfbfebaa

Delete conversations from the database when the user leaves them

Testing Done:
Left some remembered IRC channels and restarted and verified that they weren't restored. Also changed the topic in an existing room to verify that deleting tags in a save worked fine.

And of coursed, called in the turtles.

Bugs closed: PIDGIN-17989

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

libpurple/purpleconversationmanager.c file | annotate | diff | comparison | revisions
libpurple/resources/conversationmanager/statements/delete-properties.sql file | annotate | diff | comparison | revisions
libpurple/resources/libpurple.gresource.xml file | annotate | diff | comparison | revisions
--- a/libpurple/purpleconversationmanager.c	Tue Jul 15 03:10:33 2025 -0500
+++ b/libpurple/purpleconversationmanager.c	Wed Jul 16 01:08:36 2025 -0500
@@ -62,6 +62,7 @@
 	SeagullSqlite3 *db;
 	gboolean database_initialized;
 
+	SeagullStatement *delete_properties;
 	SeagullStatement *delete_tags;
 	SeagullStatement *insert_properties;
 	SeagullStatement *insert_tags;
@@ -119,10 +120,22 @@
 	}
 
 	stmt = seagull_statement_new_from_resource(manager->db,
+	                                           RESOURCE_PATH "/statements/delete-properties.sql",
+	                                           &error);
+	if(error != NULL) {
+		g_warning("failed to load delete-properties statement: %s",
+		          error->message);
+		g_clear_error(&error);
+
+		return;
+	}
+	manager->delete_properties = stmt;
+
+	stmt = seagull_statement_new_from_resource(manager->db,
 	                                           RESOURCE_PATH "/statements/delete-tags.sql",
 	                                           &error);
 	if(error != NULL) {
-		g_warning("failed to loaded delete-tags statement: %s",
+		g_warning("failed to load delete-tags statement: %s",
 		          error->message);
 		g_clear_error(&error);
 
@@ -134,7 +147,7 @@
 	                                           RESOURCE_PATH "/statements/insert-properties.sql",
 	                                           &error);
 	if(error != NULL) {
-		g_warning("failed to loaded insert-properties statement: %s",
+		g_warning("failed to load insert-properties statement: %s",
 		          error->message);
 		g_clear_error(&error);
 
@@ -146,7 +159,7 @@
 	                                           RESOURCE_PATH "/statements/insert-tags.sql",
 	                                           &error);
 	if(error != NULL) {
-		g_warning("failed to loaded insert-tags statement: %s",
+		g_warning("failed to load insert-tags statement: %s",
 		          error->message);
 		g_clear_error(&error);
 
@@ -158,7 +171,7 @@
 	                                           RESOURCE_PATH "/statements/query-properties.sql",
 	                                           &error);
 	if(error != NULL) {
-		g_warning("failed to loaded query-properties statement: %s",
+		g_warning("failed to load query-properties statement: %s",
 		          error->message);
 		g_clear_error(&error);
 
@@ -170,7 +183,7 @@
 	                                           RESOURCE_PATH "/statements/query-tags.sql",
 	                                           &error);
 	if(error != NULL) {
-		g_warning("failed to loaded query-tags statement: %s",
+		g_warning("failed to load query-tags statement: %s",
 		          error->message);
 		g_clear_error(&error);
 
@@ -203,6 +216,200 @@
 }
 
 static gboolean
+purple_conversation_manager_delete_conversation_tags(PurpleConversationManager *manager,
+                                                     const char *conversation_id,
+                                                     const char *account_id,
+                                                     GError **error)
+{
+	GError *local_error = NULL;
+	gboolean success = FALSE;
+
+	g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), FALSE);
+	g_return_val_if_fail(!purple_strempty(conversation_id), FALSE);
+	g_return_val_if_fail(!purple_strempty(account_id), FALSE);
+
+	/* Clear any existing usage. */
+	if(!seagull_statement_clear_bindings(manager->delete_tags, error)) {
+		return FALSE;
+	}
+
+	if(!seagull_statement_reset(manager->delete_tags, error)) {
+		return FALSE;
+	}
+
+	/* Bind the conversation id to the delete statement. */
+	success = seagull_statement_bind_text(manager->delete_tags,
+	                                      ":conversation_id",
+	                                      conversation_id,
+	                                      -1,
+	                                      NULL,
+	                                      error);
+	if(!success) {
+		return FALSE;
+	}
+
+	/* Bind the account id to the delete statement. */
+	success = seagull_statement_bind_text(manager->delete_tags,
+	                                      ":account_id",
+	                                      account_id,
+	                                      -1,
+	                                      NULL,
+	                                      error);
+	if(!success) {
+		return FALSE;
+	}
+
+	seagull_statement_step(manager->delete_tags, &local_error);
+	if(local_error != NULL) {
+		g_propagate_error(error, local_error);
+
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gboolean
+purple_conversation_manager_delete_conversation_properties(PurpleConversationManager *manager,
+                                                           const char *conversation_id,
+                                                           const char *account_id,
+                                                           GError **error)
+{
+	GError *local_error = NULL;
+	gboolean success = FALSE;
+
+	g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), FALSE);
+	g_return_val_if_fail(!purple_strempty(conversation_id), FALSE);
+	g_return_val_if_fail(!purple_strempty(account_id), FALSE);
+
+	/* Clear any existing usage. */
+	if(!seagull_statement_clear_bindings(manager->delete_properties, error)) {
+		return FALSE;
+	}
+
+	if(!seagull_statement_reset(manager->delete_properties, error)) {
+		return FALSE;
+	}
+
+	/* Bind the conversation id to the delete statement. */
+	success = seagull_statement_bind_text(manager->delete_properties,
+	                                      ":conversation_id",
+	                                      conversation_id,
+	                                      -1,
+	                                      NULL,
+	                                      error);
+	if(!success) {
+		return FALSE;
+	}
+
+	/* Bind the account id to the delete statement. */
+	success = seagull_statement_bind_text(manager->delete_properties,
+	                                      ":account_id",
+	                                      account_id,
+	                                      -1,
+	                                      NULL,
+	                                      error);
+	if(!success) {
+		return FALSE;
+	}
+
+	seagull_statement_step(manager->delete_properties, &local_error);
+	if(local_error != NULL) {
+		g_propagate_error(error, local_error);
+
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static void
+purple_conversation_manager_delete_conversation(PurpleConversationManager *manager,
+                                                PurpleConversation *conversation)
+{
+	PurpleAccount *account = NULL;
+	GError *error = NULL;
+	const char *conversation_id = NULL;
+	const char *account_id = NULL;
+	gboolean success = FALSE;
+
+	if(!manager->database_initialized) {
+		return;
+	}
+
+	/* Start a transaction so that everything is atomic. */
+	if(!seagull_transaction_begin(manager->db, &error)) {
+		g_warning("failed to begin transaction to delete conversation: %s",
+		          error->message);
+		g_clear_error(&error);
+
+		return;
+	}
+
+	/* Grab the conversation and account ids as we need them for everything. */
+	conversation_id = purple_conversation_get_id(conversation);
+	account = purple_conversation_get_account(conversation);
+	account_id = purple_account_get_id(account);
+
+	/* Delete the properties. We do this first, because it is used to restore
+	 * conversations. So if it fails to delete, we might be able to salvage a
+	 * rejoin as the other data still exists. This would then allow the user
+	 * to attempt to leave again, and hopefully it'll work that time.
+	 */
+	success = purple_conversation_manager_delete_conversation_properties(manager,
+	                                                                     conversation_id,
+	                                                                     account_id,
+	                                                                     &error);
+	if(error != NULL || !success) {
+		const char *message = "unknown reason";
+
+		if(error != NULL) {
+			message = error->message;
+		}
+
+		g_warning("failed to delete properties: %s", message);
+		g_clear_error(&error);
+
+		if(!seagull_transaction_rollback(manager->db, &error)) {
+			g_warning("failed to rollback transaction: %s", error->message);
+			g_clear_error(&error);
+		}
+
+		return;
+	}
+
+	/* Delete the tags. */
+	success = purple_conversation_manager_delete_conversation_tags(manager,
+	                                                               conversation_id,
+	                                                               account_id,
+	                                                               &error);
+	if(error != NULL || !success) {
+		const char *message = "unknown reason";
+
+		if(error != NULL) {
+			message = error->message;
+		}
+
+		g_warning("failed to delete tags: %s", message);
+		g_clear_error(&error);
+
+		if(!seagull_transaction_rollback(manager->db, &error)) {
+			g_warning("failed to rollback transaction: %s", error->message);
+			g_clear_error(&error);
+		}
+
+		return;
+	}
+
+	/* Commit the transaction. */
+	if(!seagull_transaction_commit(manager->db, &error)) {
+		g_warning("failed to commit transaction to delete conversation: %s",
+		          error->message);
+		g_clear_error(&error);
+	}
+}
+
+static gboolean
 purple_conversation_manager_save_conversation_properties(PurpleConversationManager *manager,
                                                          PurpleConversation *conversation)
 {
@@ -315,41 +522,20 @@
 	account = purple_conversation_get_account(conversation);
 	account_id = purple_account_get_id(account);
 
-	/* Bind the conversation id to the delete statement. */
-	success = seagull_statement_bind_text(manager->delete_tags,
-	                                      ":conversation_id",
-	                                      conversation_id,
-	                                      -1,
-	                                      NULL,
-	                                      &error);
-	if(!success) {
-		g_warning("failed to bind conversation_id to delete tags: %s",
-		          error->message);
+	/* Delete the existing tags. */
+	success = purple_conversation_manager_delete_conversation_tags(manager,
+	                                                               conversation_id,
+	                                                               account_id,
+	                                                               &error);
+	if(error != NULL) {
+		g_warning("failed to delete tags: %s", error->message);
 		g_clear_error(&error);
 
 		return FALSE;
 	}
 
-	/* Bind the account id to the delete statement. */
-	success = seagull_statement_bind_text(manager->delete_tags,
-	                                      ":account_id",
-	                                      account_id,
-	                                      -1,
-	                                      NULL,
-	                                      &error);
 	if(!success) {
-		g_warning("failed to bind account_id to delete tags: %s",
-		          error->message);
-		g_clear_error(&error);
-
-		return FALSE;
-	}
-
-	/* Delete the existing tags, since we just replace everything. */
-	seagull_statement_step(manager->delete_tags, &error);
-	if(error != NULL) {
-		g_warning("failed to delete tags: %s", error->message);
-		g_clear_error(&error);
+		g_warning("failed to delete tags: unknown reason");
 
 		return FALSE;
 	}
@@ -476,21 +662,6 @@
 	/* Save the conversation tags. */
 	success = purple_conversation_manager_save_conversation_tags(manager,
 	                                                             conversation);
-	if(!seagull_statement_clear_bindings(manager->delete_tags,
-	                                     &error))
-	{
-		g_warning("failed to clear bindings on the delete tags statement: "
-		          "%s",
-		          error->message);
-		g_clear_error(&error);
-	}
-
-	if(!seagull_statement_reset(manager->delete_tags, &error)) {
-		g_warning("failed to reset the delete tags statement: %s",
-		          error->message);
-		g_clear_error(&error);
-	}
-
 	if(!seagull_statement_clear_bindings(manager->insert_tags,
 	                                     &error))
 	{
@@ -784,9 +955,10 @@
 	g_clear_pointer(&manager->conversations, g_ptr_array_unref);
 	g_clear_pointer(&manager->filename, g_free);
 
+	g_clear_object(&manager->delete_properties);
+	g_clear_object(&manager->delete_tags);
 	g_clear_object(&manager->insert_properties);
 	g_clear_object(&manager->insert_tags);
-	g_clear_object(&manager->delete_tags);
 	g_clear_object(&manager->query_properties);
 	g_clear_object(&manager->query_tags);
 
@@ -1203,6 +1375,9 @@
 	g_list_model_items_changed(G_LIST_MODEL(manager), position, 1, 0);
 	g_object_notify_by_pspec(G_OBJECT(manager), properties[PROP_N_ITEMS]);
 
+	/* Finally remove it from the database. */
+	purple_conversation_manager_delete_conversation(manager, conversation);
+
 	g_object_unref(conversation);
 
 	return TRUE;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/resources/conversationmanager/statements/delete-properties.sql	Wed Jul 16 01:08:36 2025 -0500
@@ -0,0 +1,5 @@
+DELETE FROM conversations
+WHERE
+	conversation_id = :conversation_id
+AND
+	account_id = :account_id
--- a/libpurple/resources/libpurple.gresource.xml	Tue Jul 15 03:10:33 2025 -0500
+++ b/libpurple/resources/libpurple.gresource.xml	Wed Jul 16 01:08:36 2025 -0500
@@ -2,6 +2,7 @@
 <gresources>
   <gresource prefix="/im/pidgin/libpurple/">
     <file compressed="true">conversationmanager/migrations/01-initial.sql</file>
+    <file compressed="true">conversationmanager/statements/delete-properties.sql</file>
     <file compressed="true">conversationmanager/statements/delete-tags.sql</file>
     <file compressed="true">conversationmanager/statements/insert-properties.sql</file>
     <file compressed="true">conversationmanager/statements/insert-tags.sql</file>

mercurial