--- a/libpurple/purpleprotocolconversation.c Wed Apr 10 00:17:04 2024 -0500
+++ b/libpurple/purpleprotocolconversation.c Wed Apr 10 00:37:20 2024 -0500
@@ -36,6 +36,118 @@
/******************************************************************************
*****************************************************************************/
+purple_protocol_conversation_implements_create_conversation(PurpleProtocolConversation *protocol) + PurpleProtocolConversation *protocol_conversation = NULL; + PurpleProtocolConversationInterface *iface = NULL; + g_return_val_if_fail(PURPLE_IS_PROTOCOL_CONVERSATION(protocol), FALSE); + protocol_conversation = PURPLE_PROTOCOL_CONVERSATION(protocol); + iface = PURPLE_PROTOCOL_CONVERSATION_GET_IFACE(protocol_conversation); + if(iface->get_create_conversation_details == NULL) { + if(iface->create_conversation_async == NULL) { + if(iface->create_conversation_finish == NULL) { +PurpleCreateConversationDetails * +purple_protocol_conversation_get_create_conversation_details(PurpleProtocolConversation *protocol, + PurpleAccount *account) + PurpleProtocolConversationInterface *iface = NULL; + g_return_val_if_fail(PURPLE_IS_PROTOCOL_CONVERSATION(protocol), NULL); + g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL); + iface = PURPLE_PROTOCOL_CONVERSATION_GET_IFACE(protocol); + if(iface != NULL && iface->get_create_conversation_details != NULL) { + return iface->get_create_conversation_details(protocol, account); + g_warning("%s does not implement get_create_conversation_details", + G_OBJECT_TYPE_NAME(protocol)); +purple_protocol_conversation_create_conversation_async(PurpleProtocolConversation *protocol, + PurpleAccount *account, + PurpleCreateConversationDetails *details, + GCancellable *cancellable, + GAsyncReadyCallback callback, + PurpleProtocolConversationInterface *iface = NULL; + g_return_if_fail(PURPLE_IS_PROTOCOL_CONVERSATION(protocol)); + g_return_if_fail(PURPLE_IS_ACCOUNT(account)); + g_return_if_fail(PURPLE_IS_CREATE_CONVERSATION_DETAILS(details)); + iface = PURPLE_PROTOCOL_CONVERSATION_GET_IFACE(protocol); + if(iface != NULL && iface->create_conversation_async != NULL) { + iface->create_conversation_async(protocol, account, details, + cancellable, callback, data); + task = g_task_new(protocol, cancellable, callback, data); + g_task_return_new_error(task, PURPLE_PROTOCOL_CONVERSATION_DOMAIN, 0, + "%s does not implement create_conversation_async", + G_OBJECT_TYPE_NAME(protocol)); + g_task_set_source_tag(task, + purple_protocol_conversation_create_conversation_async); + /* details is transfer full and has already been null checked. */ + g_object_unref(details); +purple_protocol_conversation_create_conversation_finish(PurpleProtocolConversation *protocol, + PurpleProtocolConversationInterface *iface = NULL; + g_return_val_if_fail(PURPLE_IS_PROTOCOL_CONVERSATION(protocol), FALSE); + g_return_val_if_fail(G_IS_ASYNC_RESULT(result), FALSE); + iface = PURPLE_PROTOCOL_CONVERSATION_GET_IFACE(protocol); + if(iface != NULL && iface->create_conversation_finish != NULL) { + return iface->create_conversation_finish(protocol, result, error); + if(G_IS_TASK(result)) { + GTask *task = G_TASK(result); + gpointer source = NULL; + source = g_task_get_source_tag(task); + if(source == purple_protocol_conversation_create_conversation_async) { + return g_task_propagate_pointer(task, error); + g_warning("%s does not implement create_conversation_finish", + G_OBJECT_TYPE_NAME(protocol)); purple_protocol_conversation_send_message_async(PurpleProtocolConversation *protocol,
PurpleConversation *conversation,
--- a/libpurple/purpleprotocolconversation.h Wed Apr 10 00:17:04 2024 -0500
+++ b/libpurple/purpleprotocolconversation.h Wed Apr 10 00:37:20 2024 -0500
@@ -34,6 +34,7 @@
#include "purpleavatar.h"
#include "purplechanneljoindetails.h"
#include "purpleconversation.h"
+#include "purplecreateconversationdetails.h" #include "purplemessage.h"
#include "purpleprotocol.h"
#include "purpletyping.h"
@@ -41,6 +42,17 @@
+ * PURPLE_PROTOCOL_CONVERSATION_DOMAIN: + * An error domain for [iface@ProtocolConversation] errors. +#define PURPLE_PROTOCOL_CONVERSATION_DOMAIN \ + (g_quark_from_static_string("purple-protocol-conversation")) \ + PURPLE_AVAILABLE_MACRO_IN_3_0 #define PURPLE_TYPE_PROTOCOL_CONVERSATION (purple_protocol_conversation_get_type())
@@ -61,6 +73,10 @@
+ PurpleCreateConversationDetails *(*get_create_conversation_details)(PurpleProtocolConversation *protocol, PurpleAccount *account); + void (*create_conversation_async)(PurpleProtocolConversation *protocol, PurpleAccount *account, PurpleCreateConversationDetails *details, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data); + PurpleConversation *(*create_conversation_finish)(PurpleProtocolConversation *protocol, GAsyncResult *result, GError **error); void (*send_message_async)(PurpleProtocolConversation *protocol, PurpleConversation *conversation, PurpleMessage *message, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data);
gboolean (*send_message_finish)(PurpleProtocolConversation *protocol, GAsyncResult *result, GError **error);
@@ -81,6 +97,81 @@
+ * purple_protocol_conversation_implements_create_conversation: + * @protocol: The instance. + * Checks if @protocol implements + * [vfunc@ProtocolConversation.get_create_conversation_details], + * [vfunc@ProtocolConversation.create_conversation_async], and + * [vfunc@ProtocolConversation.create_conversation_finish]. + * Returns: %TRUE if everything is implemented, otherwise %FALSE. +gboolean purple_protocol_conversation_implements_create_conversation(PurpleProtocolConversation *protocol); + * purple_protocol_conversation_get_create_conversation_details: + * @protocol: The instance. + * @account: The account. + * Gets a [class@CreateConversationDetails] from @protocol for @account. + * This can then be used in calls to + * [method@ProtocolConversation.create_conversation_async] to create the + * Returns: (transfer full): The details to create a conversation with +PurpleCreateConversationDetails *purple_protocol_conversation_get_create_conversation_details(PurpleProtocolConversation *protocol, PurpleAccount *account); + * purple_protocol_conversation_create_conversation_async: + * @protocol: The instance. + * @account: The account. + * @details: (transfer full): The create conversation details. + * @cancellable: (nullable): optional GCancellable object, %NULL to ignore. + * @callback: (nullable) (scope async): The callback to call after the + * conversation has been created. + * @data: (nullable): Optional user data to pass to @callback. + * Starts the process of creating a dm or group dm conversation on @account. +void purple_protocol_conversation_create_conversation_async(PurpleProtocolConversation *protocol, PurpleAccount *account, PurpleCreateConversationDetails *details, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data); + * purple_protocol_conversation_create_conversation_finish: + * @protocol: The instance. + * @result: The [iface@Gio.AsyncResult] from the previous + * [method@ProtocolConversation.create_conversation_async] call. + * @error: Return address for a #GError, or %NULL. + * Finishes a previous call to + * [method@ProtocolConversation.create_conversation_async]. This should be + * called from the callback of that function to get the result of whether or + * not the conversation was created successfully. + * It is the responsibility of the protocol to register the conversation with + * the default [class@ConversationManager]. + * Returns: (transfer full): The new conversation if successful, otherwise + * %NULL with @error possibly set. +PurpleConversation *purple_protocol_conversation_create_conversation_finish(PurpleProtocolConversation *protocol, GAsyncResult *result, GError **error); * purple_protocol_conversation_send_message_async:
* @protocol: The instance.
* @conversation: The conversation to send a message to.
--- a/libpurple/tests/test_protocol_conversation.c Wed Apr 10 00:17:04 2024 -0500
+++ b/libpurple/tests/test_protocol_conversation.c Wed Apr 10 00:37:20 2024 -0500
@@ -87,6 +87,109 @@
* TestProtocolConversationEmpty Tests
*****************************************************************************/
+test_purple_protocol_conversation_empty_implements_create_conversation(void) { + PurpleProtocolConversation *protocol = NULL; + protocol = g_object_new(test_purple_protocol_conversation_empty_get_type(), + g_assert_false(purple_protocol_conversation_implements_create_conversation(protocol)); + g_assert_finalize_object(protocol); +test_purple_protocol_conversation_empty_get_create_conversation_details(void) { + if(g_test_subprocess()) { + PurpleAccount *account = NULL; + PurpleCreateConversationDetails *details = NULL; + PurpleProtocolConversation *protocol = NULL; + protocol = g_object_new(test_purple_protocol_conversation_empty_get_type(), + account = purple_account_new("test", "test"); + details = purple_protocol_conversation_get_create_conversation_details(protocol, + g_assert_null(details); + g_assert_finalize_object(account); + g_assert_finalize_object(protocol); + g_test_trap_subprocess(NULL, 0, 0); + g_test_trap_assert_stderr("*Purple-WARNING*TestPurpleProtocolConversationEmpty*create_conversation_details*"); +test_purple_protocol_conversation_empty_create_conversation_async_cb(GObject *source, + G_GNUC_UNUSED gpointer data) + PurpleConversation *conversation = NULL; + g_assert_true(PURPLE_IS_PROTOCOL_CONVERSATION(source)); + conversation = purple_protocol_conversation_create_conversation_finish(PURPLE_PROTOCOL_CONVERSATION(source), + g_assert_error(error, PURPLE_PROTOCOL_CONVERSATION_DOMAIN, 0); + g_assert_null(conversation); +test_purple_protocol_conversation_empty_create_conversation_async(void) { + PurpleAccount *account = NULL; + PurpleCreateConversationDetails *details = NULL; + PurpleProtocolConversation *protocol = NULL; + protocol = g_object_new(test_purple_protocol_conversation_empty_get_type(), + account = purple_account_new("test", "test"); + details = purple_create_conversation_details_new(0); + purple_protocol_conversation_create_conversation_async(protocol, + test_purple_protocol_conversation_empty_create_conversation_async_cb, + g_main_context_iteration(NULL, FALSE); + g_assert_finalize_object(account); + g_assert_finalize_object(protocol); +test_purple_protocol_conversation_empty_create_conversation_finish(void) { + if(g_test_subprocess()) { + PurpleProtocolConversation *protocol = NULL; + PurpleConversation *conversation = NULL; + protocol = g_object_new(test_purple_protocol_conversation_empty_get_type(), + task = g_task_new(protocol, NULL, NULL, NULL); + conversation = purple_protocol_conversation_create_conversation_finish(protocol, + g_assert_no_error(error); + g_assert_null(conversation); + g_assert_finalize_object(task); + g_assert_finalize_object(protocol); + g_test_trap_subprocess(NULL, 0, 0); + g_test_trap_assert_stderr("*Purple-WARNING*TestPurpleProtocolConversationEmpty*create_conversation_finish*"); test_purple_protocol_conversation_empty_send_message_async(void) {
if(g_test_subprocess()) {
PurpleAccount *account = NULL;
@@ -376,6 +479,10 @@
+ guint get_create_conversation_details; + guint create_conversation_async; + guint create_conversation_finish; guint send_message_async;
guint send_message_finish;
@@ -392,6 +499,73 @@
+static PurpleCreateConversationDetails * +test_purple_protocol_conversation_get_create_conversation_details(PurpleProtocolConversation *protocol, + PurpleAccount *account) + TestPurpleProtocolConversation *test_protocol = NULL; + test_protocol = TEST_PURPLE_PROTOCOL_CONVERSATION(protocol); + test_protocol->get_create_conversation_details += 1; + g_assert_true(PURPLE_IS_PROTOCOL_CONVERSATION(protocol)); + g_assert_true(PURPLE_IS_ACCOUNT(account)); + return purple_create_conversation_details_new(10); +test_purple_protocol_conversation_create_conversation_async(PurpleProtocolConversation *protocol, + PurpleAccount *account, + PurpleCreateConversationDetails *details, + GCancellable *cancellable, + GAsyncReadyCallback callback, + TestPurpleProtocolConversation *test_protocol = NULL; + g_assert_true(PURPLE_IS_ACCOUNT(account)); + g_assert_true(PURPLE_IS_CREATE_CONVERSATION_DETAILS(details)); + /* details is transfer full and we're done with it. */ + g_object_unref(details); + test_protocol = TEST_PURPLE_PROTOCOL_CONVERSATION(protocol); + test_protocol->create_conversation_async += 1; + task = g_task_new(protocol, cancellable, callback, data); + if(test_protocol->should_error) { + GError *error = g_error_new_literal(TEST_PURPLE_PROTOCOL_CONVERSATION_DOMAIN, + g_task_return_error(task, error); + PurpleConversation *conversation = NULL; + conversation = g_object_new( + PURPLE_TYPE_CONVERSATION, + g_task_return_pointer(task, conversation, g_object_unref); +static PurpleConversation * +test_purple_protocol_conversation_create_conversation_finish(PurpleProtocolConversation *protocol, + TestPurpleProtocolConversation *test_protocol = NULL; + test_protocol = TEST_PURPLE_PROTOCOL_CONVERSATION(protocol); + test_protocol->create_conversation_finish += 1; + return g_task_propagate_pointer(G_TASK(result), error); test_purple_protocol_conversation_send_message_async(PurpleProtocolConversation *protocol,
PurpleConversation *conversation,
@@ -590,6 +764,10 @@
test_purple_protocol_conversation_iface_init(PurpleProtocolConversationInterface *iface) {
+ iface->get_create_conversation_details = test_purple_protocol_conversation_get_create_conversation_details; + iface->create_conversation_async = test_purple_protocol_conversation_create_conversation_async; + iface->create_conversation_finish = test_purple_protocol_conversation_create_conversation_finish; iface->send_message_async = test_purple_protocol_conversation_send_message_async;
iface->send_message_finish = test_purple_protocol_conversation_send_message_finish;
@@ -615,6 +793,10 @@
test_purple_protocol_conversation_init(TestPurpleProtocolConversation *protocol)
+ protocol->get_create_conversation_details = 0; + protocol->create_conversation_async = 0; + protocol->create_conversation_finish = 0; protocol->send_message_async = 0;
protocol->send_message_finish = 0;
@@ -637,6 +819,112 @@
/******************************************************************************
+ * TestProtocolConversation CreateConversation Tests + *****************************************************************************/ +test_purple_protocol_conversation_implements_create_conversation(void) { + PurpleProtocolConversation *protocol = NULL; + protocol = g_object_new(test_purple_protocol_conversation_get_type(), + g_assert_true(purple_protocol_conversation_implements_create_conversation(protocol)); + g_assert_finalize_object(protocol); +test_purple_protocol_conversation_get_create_conversation_details_normal(void) + TestPurpleProtocolConversation *test_protocol = NULL; + PurpleAccount *account = NULL; + PurpleCreateConversationDetails *details = NULL; + PurpleProtocolConversation *protocol = NULL; + test_protocol = g_object_new(test_purple_protocol_conversation_get_type(), + protocol = PURPLE_PROTOCOL_CONVERSATION(test_protocol); + account = purple_account_new("test", "test"); + details = purple_protocol_conversation_get_create_conversation_details(protocol, + g_assert_true(PURPLE_IS_CREATE_CONVERSATION_DETAILS(details)); + g_assert_cmpuint(test_protocol->get_create_conversation_details, ==, 1); + g_assert_finalize_object(details); + g_assert_finalize_object(account); + g_assert_finalize_object(test_protocol); +test_purple_protocol_conversation_create_conversation_cb(GObject *obj, + G_GNUC_UNUSED gpointer data) + TestPurpleProtocolConversation *test_protocol = NULL; + PurpleConversation *conversation = NULL; + PurpleProtocolConversation *protocol = NULL; + protocol = PURPLE_PROTOCOL_CONVERSATION(obj); + test_protocol = TEST_PURPLE_PROTOCOL_CONVERSATION(obj); + conversation = purple_protocol_conversation_create_conversation_finish(protocol, + if(test_protocol->should_error) { + g_assert_error(error, TEST_PURPLE_PROTOCOL_CONVERSATION_DOMAIN, 0); + g_assert_null(conversation); + g_assert_no_error(error); + g_assert_true(PURPLE_IS_CONVERSATION(conversation)); + g_clear_object(&conversation); +test_purple_protocol_conversation_create_conversation_normal(gconstpointer data) + TestPurpleProtocolConversation *protocol = NULL; + PurpleAccount *account = NULL; + PurpleCreateConversationDetails *details = NULL; + protocol = g_object_new(test_purple_protocol_conversation_get_type(), + protocol->should_error = GPOINTER_TO_INT(data); + account = purple_account_new("test", "test"); + details = purple_create_conversation_details_new(10); + purple_protocol_conversation_create_conversation_async(PURPLE_PROTOCOL_CONVERSATION(protocol), + test_purple_protocol_conversation_create_conversation_cb, + while(g_main_context_iteration(NULL, FALSE)); + g_assert_cmpuint(protocol->create_conversation_async, ==, 1); + g_assert_cmpuint(protocol->create_conversation_finish, ==, 1); + g_assert_finalize_object(protocol); + /* We can't use g_assert_finalize_object as current, PurpleAccount's + * presence is a PurpleAccountPresence that holds a reference to the + * account and we have no way to break that cycle. Trying to use dispose + * doesn't work because the account and the presence can't even reach a + * reference count of zero. + g_clear_object(&account); +/****************************************************************************** * TestProtocolConversation SendMessage Tests
*****************************************************************************/
@@ -1017,6 +1305,16 @@
loop = g_main_loop_new(NULL, FALSE);
+ /* Empty create conversation tests. */ + g_test_add_func("/protocol-conversation/empty/implements-create-conversation", + test_purple_protocol_conversation_empty_implements_create_conversation); + g_test_add_func("/protocol-conversation/empty/get-create-conversation-details", + test_purple_protocol_conversation_empty_get_create_conversation_details); + g_test_add_func("/protocol-conversation/empty/create-conversation-async", + test_purple_protocol_conversation_empty_create_conversation_async); + g_test_add_func("/protocol-conversation/empty/create-conversation-finish", + test_purple_protocol_conversation_empty_create_conversation_finish); /* Empty send message tests. */
g_test_add_func("/protocol-conversation/empty/send-message-async",
test_purple_protocol_conversation_empty_send_message_async);
@@ -1043,6 +1341,18 @@
g_test_add_func("/protocol-conversation/empty/send-typing",
test_purple_protocol_conversation_empty_send_typing);
+ /* Normal create conversation tests. */ + g_test_add_func("/protocol-conversation/normal/implements-create-conversation", + test_purple_protocol_conversation_implements_create_conversation); + g_test_add_func("/protocol-conversation/normal/get-create-conversation-details-normal", + test_purple_protocol_conversation_get_create_conversation_details_normal); + g_test_add_data_func("/protocol-conversation/normal/create-conversation-normal", + GINT_TO_POINTER(FALSE), + test_purple_protocol_conversation_create_conversation_normal); + g_test_add_data_func("/protocol-conversation/normal/create-conversation-error", + test_purple_protocol_conversation_create_conversation_normal); /* Normal send message tests. */
g_test_add_data_func("/protocol-conversation/normal/send-message-normal",