Fri, 11 Jul 2025 02:46:28 -0500
Load conversations back in from the database
This just handles the conversations and most of their properties including
their tags.
Testing Done:
Verified that conversations were created by starting pidgin 3 with `-n` and that their properties were correct. Then started pidgin 3 normally and verified that all the conversations were brought back online and worked properly.
Also called in the turtles.
Bugs closed: PIDGIN-17989
Reviewed at https://reviews.imfreedom.org/r/4051/
--- a/libpurple/purpleconversationmanager.c Fri Jul 11 01:55:31 2025 -0500 +++ b/libpurple/purpleconversationmanager.c Fri Jul 11 02:46:28 2025 -0500 @@ -26,10 +26,17 @@ #include "purpleconversationmanagerprivate.h" #include "core.h" +#include "purpleaccount.h" +#include "purpleaccountmanager.h" #include "purplecontact.h" #include "purpleui.h" #include "util.h" +#ifdef G_LOG_DOMAIN +#undef G_LOG_DOMAIN +#endif /* G_LOG_DOMAIN */ +#define G_LOG_DOMAIN "PurpleConversationManager" + enum { PROP_0, PROP_FILENAME, @@ -58,6 +65,8 @@ SeagullStatement *delete_tags; SeagullStatement *insert_properties; SeagullStatement *insert_tags; + SeagullStatement *query_properties; + SeagullStatement *query_tags; GPtrArray *conversations; }; @@ -145,6 +154,30 @@ } manager->insert_tags = stmt; + stmt = seagull_statement_new_from_resource(manager->db, + RESOURCE_PATH "/statements/query-properties.sql", + &error); + if(error != NULL) { + g_warning("failed to loaded query-properties statement: %s", + error->message); + g_clear_error(&error); + + return; + } + manager->query_properties = stmt; + + stmt = seagull_statement_new_from_resource(manager->db, + RESOURCE_PATH "/statements/query-tags.sql", + &error); + if(error != NULL) { + g_warning("failed to loaded query-tags statement: %s", + error->message); + g_clear_error(&error); + + return; + } + manager->query_tags = stmt; + manager->database_initialized = TRUE; } @@ -155,10 +188,18 @@ g_return_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager)); if(g_set_str(&manager->filename, filename)) { - purple_conversation_manager_initialize_database(manager); - g_object_notify_by_pspec(G_OBJECT(manager), properties[PROP_FILENAME]); } + + /* The filename property is construct-only and nullable, so if it is set as + * null, the g_set_str above won't return TRUE, but and we have to + * initialize the database in any case so we just do it separately. + * + * Also, GObject will emit a Critical message if a construct only property + * is set more than once and not call it again, so we don't need to + * protect against that either. + */ + purple_conversation_manager_initialize_database(manager); } static gboolean @@ -482,6 +523,193 @@ } } +static void +purple_conversation_manager_load_conversation_tags(PurpleConversationManager *manager, + PurpleConversation *conversation, + const char *account_id, + const char *conversation_id, + GError **error) +{ + PurpleTags *tags = NULL; + GError *local_error = NULL; + + tags = purple_conversation_get_tags(conversation); + + /* Conversations have some default tags when created, but we need to + * replace everything so we clear those. + */ + purple_tags_remove_all(tags); + + /* Reset the statement. */ + seagull_statement_reset(manager->query_tags, &local_error); + if(local_error != NULL) { + g_propagate_error(error, local_error); + + return; + } + + /* Bind the account id. */ + seagull_statement_bind_text(manager->query_tags, ":account_id", account_id, + -1, NULL, &local_error); + if(local_error != NULL) { + g_propagate_error(error, local_error); + + return; + } + + /* Bind the conversation id. */ + seagull_statement_bind_text(manager->query_tags, ":conversation_id", + conversation_id, -1, NULL, &local_error); + if(local_error != NULL) { + g_propagate_error(error, local_error); + + return; + } + + while(seagull_statement_step(manager->query_tags, &local_error)) { + const char *tag = NULL; + + tag = seagull_statement_column_text(manager->query_tags, "tag", + &local_error); + + if(local_error != NULL) { + g_propagate_error(error, local_error); + + return; + } + + purple_tags_add(tags, tag); + } + + if(local_error != NULL) { + g_propagate_error(error, local_error); + } +} + +static void +purple_conversation_manager_load_conversations(PurpleConversationManager *manager) +{ + PurpleAccountManager *account_manager = NULL; + GError *error = NULL; + + seagull_statement_reset(manager->query_properties, &error); + if(error != NULL) { + g_warning("failed to request query properties statement: %s", + error->message); + g_clear_error(&error); + + return; + } + + account_manager = purple_account_manager_get_default(); + + while(seagull_statement_step(manager->query_properties, &error)) { + PurpleAccount *account = NULL; + PurpleConversation *conversation = NULL; + const char *account_id = NULL; + const char *conversation_id = NULL; + gint type = PURPLE_CONVERSATION_TYPE_UNSET; + gboolean federated = FALSE; + + /* Grab the account id and attempt to find the account. */ + account_id = seagull_statement_column_text(manager->query_properties, + "account_id", &error); + if(error != NULL) { + g_warning("failed to read account id: %s", error->message); + g_clear_error(&error); + + continue; + } + + account = purple_account_manager_find_by_id(account_manager, + account_id); + if(!PURPLE_IS_ACCOUNT(account)) { + g_warning("failed to find account for id %s", account_id); + + continue; + } + + conversation_id = seagull_statement_column_text(manager->query_properties, + "conversation_id", + &error); + if(error != NULL) { + g_warning("failed to read conversation id: %s", error->message); + g_clear_error(&error); + + continue; + } + + type = seagull_statement_column_enum(manager->query_properties, "type", + &error); + if(error != NULL) { + g_warning("failed to read type: %s", error->message); + g_clear_error(&error); + + continue; + } + + federated = seagull_statement_column_int(manager->query_properties, + "federated", &error); + if(error != NULL) { + g_warning("failed to read federated: %s", error->message); + g_clear_error(&error); + + continue; + } + + /* We have all the construct only properties, so let's create the + * instance. + */ + conversation = g_object_new( + PURPLE_TYPE_CONVERSATION, + "account", account, + "federated", federated, + "id", conversation_id, + "type", type, + NULL); + + /* Now set the rest of the properties. */ + seagull_statement_column_object(manager->query_properties, "conv_", + G_OBJECT(conversation), &error); + + if(error != NULL) { + g_warning("failed to read object properties: %s", error->message); + g_clear_error(&error); + + g_clear_object(&conversation); + + continue; + } + + purple_conversation_manager_load_conversation_tags(manager, + conversation, + account_id, + conversation_id, + &error); + if(error != NULL) { + g_warning("failed to load tags for conversation %s: %s", + conversation_id, error->message); + + g_clear_error(&error); + g_clear_object(&conversation); + + continue; + } + + /* Finally, add the conversation to the manager. */ + if(!purple_conversation_manager_add(manager, conversation)) { + g_warning("failed to add conversation: %p", conversation); + } + + g_clear_object(&conversation); + } + + if(error != NULL) { + g_warning("failed querying properties: %s", error->message); + g_clear_error(&error); + } +} + /****************************************************************************** * Callbacks *****************************************************************************/ @@ -549,9 +777,6 @@ G_IMPLEMENT_INTERFACE(G_TYPE_LIST_MODEL, pidgin_conversation_manager_list_model_iface_init)) -/****************************************************************************** - * GObject Implementation - *****************************************************************************/ static void purple_conversation_manager_finalize(GObject *obj) { PurpleConversationManager *manager = PURPLE_CONVERSATION_MANAGER(obj); @@ -562,6 +787,8 @@ 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); if(manager->db != NULL) { GError *error = NULL; @@ -782,6 +1009,9 @@ (gpointer *)&default_manager); } } + + /* Load conversations. */ + purple_conversation_manager_load_conversations(default_manager); } void
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/resources/conversationmanager/statements/query-properties.sql Fri Jul 11 02:46:28 2025 -0500 @@ -0,0 +1,21 @@ +SELECT + account_id, -- construct only + age_restricted AS conv_age_restricted, + alias AS conv_alias, + conversation_id, -- construct only + created_on AS conv_created_on, + creator_id, + description AS conv_description, + drafting AS conv_drafting, + favorite AS conv_favorite, + federated, -- construct only + logging AS conv_logging, + needs_attention AS conv_needs_attention, + title AS conv_title, + topic AS conv_topic, + topic_author_id, + topic_updated AS conv_topic_updated, + type, -- construct only + url AS conv_url, + user_nickname AS conv_nickname +FROM conversations
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/resources/conversationmanager/statements/query-tags.sql Fri Jul 11 02:46:28 2025 -0500 @@ -0,0 +1,7 @@ +SELECT + tag +FROM conversation_tags +WHERE + account_id = :account_id +AND + conversation_id = :conversation_id
--- a/libpurple/resources/libpurple.gresource.xml Fri Jul 11 01:55:31 2025 -0500 +++ b/libpurple/resources/libpurple.gresource.xml Fri Jul 11 02:46:28 2025 -0500 @@ -5,6 +5,8 @@ <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> + <file compressed="true">conversationmanager/statements/query-properties.sql</file> + <file compressed="true">conversationmanager/statements/query-tags.sql</file> <file compressed="true">sqlitehistoryadapter/01-schema.sql</file> </gresource> </gresources>
--- a/libpurple/tests/test_conversation_manager.c Fri Jul 11 01:55:31 2025 -0500 +++ b/libpurple/tests/test_conversation_manager.c Fri Jul 11 02:46:28 2025 -0500 @@ -104,6 +104,7 @@ conversation = g_object_new( PURPLE_TYPE_CONVERSATION, "account", account, + "id", "test-conversation", NULL); /* Add the conversation to the manager. */ @@ -174,6 +175,7 @@ conversation = g_object_new( PURPLE_TYPE_CONVERSATION, "account", account, + "id", "test-conversation", NULL); purple_conversation_manager_add(manager, conversation); @@ -223,6 +225,7 @@ conversation = g_object_new( PURPLE_TYPE_CONVERSATION, "account", account, + "id", "test-conversation", "type", PURPLE_CONVERSATION_TYPE_DM, NULL); @@ -283,6 +286,7 @@ conversation1 = g_object_new( PURPLE_TYPE_CONVERSATION, "account", account, + "id", "test-conversation", "type", PURPLE_CONVERSATION_TYPE_DM, NULL); purple_conversation_manager_add(manager, conversation1); @@ -337,6 +341,7 @@ conversation1 = g_object_new( PURPLE_TYPE_CONVERSATION, "account", account1, + "id", "test-conversation1", "type", PURPLE_CONVERSATION_TYPE_CHANNEL, NULL); purple_conversation_manager_add(manager, conversation1); @@ -347,6 +352,7 @@ conversation2 = g_object_new( PURPLE_TYPE_CONVERSATION, "account", account1, + "id", "test-conversation2", "type", PURPLE_CONVERSATION_TYPE_DM, NULL); purple_conversation_manager_add(manager, conversation2); @@ -355,6 +361,7 @@ conversation3 = g_object_new( PURPLE_TYPE_CONVERSATION, "account", account2, + "id", "test-conversation3", "type", PURPLE_CONVERSATION_TYPE_CHANNEL, NULL); purple_conversation_manager_add(manager, conversation3);