--- 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