Tue, 03 Oct 2023 00:02:16 -0500
Add the ability to join channels to PurpleProtocolConversation
Testing Done:
Ran the unit tests under valgrind.
Reviewed at https://reviews.imfreedom.org/r/2619/
--- a/libpurple/purpleprotocolconversation.c Tue Oct 03 00:01:02 2023 -0500 +++ b/libpurple/purpleprotocolconversation.c Tue Oct 03 00:02:16 2023 -0500 @@ -73,11 +73,11 @@ iface = PURPLE_PROTOCOL_CONVERSATION_GET_IFACE(protocol); if(iface != NULL && iface->send_message_finish != NULL) { return iface->send_message_finish(protocol, result, error); - } else { - g_warning("%s does not implement send_message_finish", - G_OBJECT_TYPE_NAME(protocol)); } + g_warning("%s does not implement send_message_finish", + G_OBJECT_TYPE_NAME(protocol)); + return FALSE; } @@ -124,3 +124,68 @@ return FALSE; } + +PurpleChannelJoinDetails * +purple_protocol_conversation_get_channel_join_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_channel_join_details != NULL) { + return iface->get_channel_join_details(protocol, account); + } + + g_warning("%s does not implement get_channel_join_details", + G_OBJECT_TYPE_NAME(protocol)); + + return NULL; +} + +void +purple_protocol_conversation_join_channel_async(PurpleProtocolConversation *protocol, + PurpleAccount *account, + PurpleChannelJoinDetails *details, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data) +{ + 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_CHANNEL_JOIN_DETAILS(details)); + + iface = PURPLE_PROTOCOL_CONVERSATION_GET_IFACE(protocol); + if(iface != NULL && iface->join_channel_async != NULL) { + iface->join_channel_async(protocol, account, details, cancellable, + callback, data); + } else { + g_warning("%s does not implement join_channel_async", + G_OBJECT_TYPE_NAME(protocol)); + } +} + +gboolean +purple_protocol_conversation_join_channel_finish(PurpleProtocolConversation *protocol, + GAsyncResult *result, + GError **error) +{ + 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->join_channel_finish != NULL) { + return iface->join_channel_finish(protocol, result, error); + } + + g_warning("%s does not implement join_channel_finish", + G_OBJECT_TYPE_NAME(protocol)); + + return FALSE; +}
--- a/libpurple/purpleprotocolconversation.h Tue Oct 03 00:01:02 2023 -0500 +++ b/libpurple/purpleprotocolconversation.h Tue Oct 03 00:02:16 2023 -0500 @@ -30,6 +30,8 @@ #include <glib.h> #include <glib-object.h> +#include "account.h" +#include "purplechanneljoindetails.h" #include "purpleconversation.h" #include "purplemessage.h" #include "purpleprotocol.h" @@ -60,6 +62,10 @@ void (*set_topic_async)(PurpleProtocolConversation *protocol, PurpleConversation *conversation, const char *topic, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data); gboolean (*set_topic_finish)(PurpleProtocolConversation *protocol, GAsyncResult *result, GError **error); + PurpleChannelJoinDetails *(*get_channel_join_details)(PurpleProtocolConversation *protocol, PurpleAccount *account); + void (*join_channel_async)(PurpleProtocolConversation *protocol, PurpleAccount *account, PurpleChannelJoinDetails *details, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data); + gboolean (*join_channel_finish)(PurpleProtocolConversation *protocol, GAsyncResult *result, GError **error); + /*< private >*/ gpointer reserved[8]; }; @@ -140,6 +146,59 @@ */ gboolean purple_protocol_conversation_set_topic_finish(PurpleProtocolConversation *protocol, GAsyncResult *result, GError **error); +/** + * purple_protocol_conversation_get_channel_join_details: + * @protocol: The instance. + * @account: The account that will be joining a channel. + * + * User interfaces will use this function to get an instance of + * [class@ChannelJoinDetails] that can be presented to a user for them to edit. + * + * Returns: (transfer full): The new join channel details. + * + * Since: 3.0.0 + */ +PurpleChannelJoinDetails *purple_protocol_conversation_get_channel_join_details(PurpleProtocolConversation *protocol, PurpleAccount *account); + +/** + * purple_protocol_conversation_join_channel_async: + * @protocol: The instance. + * @account: The account that's joining the channel. + * @details: The details of the channel that's being joined. + * @cancellable: (nullable): optional GCancellable object, %NULL to ignore. + * @callback: (nullable) (scope async): The callback to call after the message + * has been sent. + * @data: (nullable): Optional user data to pass to @callback. + * + * Attempts to join the channel identified by @details using @account. + * + * If the channel is joined successfully, it is the responsibility of + * @protocol to add the conversation to the [class@ConversationManager] during + * this process. + * + * Since: 3.0.0 + */ +void purple_protocol_conversation_join_channel_async(PurpleProtocolConversation *protocol, PurpleAccount *account, PurpleChannelJoinDetails *details, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data); + +/** + * purple_protocol_conversation_join_channel_finish: + * @protocol: The instance. + * @result: The [iface@Gio.AsyncResult] from the previous + * [method@ProtocolConversation.join_channel_async] call. + * @error: Return address for a #GError, or %NULL. + * + * Finishes a previous call to + * [method@ProtocolConversation.join_channel_async]. This should be called from + * the callback of that function to get the result of whether or not the + * channel was joined successfully. + * + * Returns: %TRUE if the channel was joined successfully, otherwise %FALSE with + * @error possibly set. + * + * Since: 3.0.0 + */ +gboolean purple_protocol_conversation_join_channel_finish(PurpleProtocolConversation *protocol, GAsyncResult *result, GError **error); + G_END_DECLS #endif /* PURPLE_PROTOCOL_CONVERSATION_H */
--- a/libpurple/tests/test_protocol_conversation.c Tue Oct 03 00:01:02 2023 -0500 +++ b/libpurple/tests/test_protocol_conversation.c Tue Oct 03 00:02:16 2023 -0500 @@ -202,6 +202,82 @@ } +static void +test_purple_protocol_conversation_empty_get_channel_join_details(void) { + if(g_test_subprocess()) { + PurpleAccount *account = NULL; + PurpleChannelJoinDetails *result = NULL; + PurpleProtocolConversation *protocol = NULL; + + protocol = g_object_new(test_purple_protocol_conversation_empty_get_type(), + NULL); + account = purple_account_new("test", "test"); + + result = purple_protocol_conversation_get_channel_join_details(protocol, + account); + + g_assert_null(result); + + g_clear_object(&account); + g_clear_object(&protocol); + } + + g_test_trap_subprocess(NULL, 0, 0); + g_test_trap_assert_stderr("*Purple-WARNING*TestPurpleProtocolConversationEmpty*get_channel_join_details*"); +} + +static void +test_purple_protocol_conversation_empty_join_channel_async(void) { + if(g_test_subprocess()) { + PurpleAccount *account = NULL; + PurpleChannelJoinDetails *details = NULL; + PurpleProtocolConversation *protocol = NULL; + + protocol = g_object_new(test_purple_protocol_conversation_empty_get_type(), + NULL); + account = purple_account_new("test", "test"); + details = purple_channel_join_details_new(FALSE, FALSE); + + purple_protocol_conversation_join_channel_async(protocol, account, + details, NULL, NULL, + NULL); + + g_clear_object(&account); + g_clear_object(&details); + g_clear_object(&protocol); + } + + g_test_trap_subprocess(NULL, 0, 0); + g_test_trap_assert_stderr("*Purple-WARNING*TestPurpleProtocolConversationEmpty*join_channel_async*"); +} + +static void +test_purple_protocol_conversation_empty_join_channel_finish(void) { + if(g_test_subprocess()) { + PurpleProtocolConversation *protocol = NULL; + GError *error = NULL; + GTask *task = NULL; + gboolean result = FALSE; + + protocol = g_object_new(test_purple_protocol_conversation_empty_get_type(), + NULL); + + task = g_task_new(protocol, NULL, NULL, NULL); + + result = purple_protocol_conversation_join_channel_finish(protocol, + G_ASYNC_RESULT(task), + &error); + g_assert_no_error(error); + g_assert_false(result); + + g_clear_object(&task); + g_clear_object(&protocol); + } + + g_test_trap_subprocess(NULL, 0, 0); + g_test_trap_assert_stderr("*Purple-WARNING*TestPurpleProtocolConversationEmpty*join_channel_finish*"); +} + /****************************************************************************** * TestProtocolConversation Implementation *****************************************************************************/ @@ -219,6 +295,10 @@ guint set_topic_async; guint set_topic_finish; + + guint get_channel_join_details; + guint join_channel_async; + guint join_channel_finish; }; static void @@ -263,6 +343,63 @@ return g_task_propagate_boolean(G_TASK(result), error); } +static PurpleChannelJoinDetails * +test_purple_protocol_conversation_get_channel_join_details(PurpleProtocolConversation *protocol, + PurpleAccount *account) +{ + TestPurpleProtocolConversation *test_protocol = NULL; + + test_protocol = TEST_PURPLE_PROTOCOL_CONVERSATION(protocol); + test_protocol->get_channel_join_details += 1; + + g_assert_true(PURPLE_IS_PROTOCOL_CONVERSATION(protocol)); + g_assert_true(PURPLE_IS_ACCOUNT(account)); + + return purple_channel_join_details_new(TRUE, TRUE); +} + +static void +test_purple_protocol_conversation_join_channel_async(PurpleProtocolConversation *protocol, + PurpleAccount *account, + PurpleChannelJoinDetails *details, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data) +{ + TestPurpleProtocolConversation *test_protocol = NULL; + GTask *task = NULL; + + g_assert_true(PURPLE_IS_ACCOUNT(account)); + g_assert_true(PURPLE_IS_CHANNEL_JOIN_DETAILS(details)); + + test_protocol = TEST_PURPLE_PROTOCOL_CONVERSATION(protocol); + test_protocol->join_channel_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, + 0, "error"); + g_task_return_error(task, error); + } else { + g_task_return_boolean(task, TRUE); + } + + g_clear_object(&task); +} + +static gboolean +test_purple_protocol_conversation_join_channel_finish(PurpleProtocolConversation *protocol, + GAsyncResult *result, + GError **error) +{ + TestPurpleProtocolConversation *test_protocol = NULL; + + test_protocol = TEST_PURPLE_PROTOCOL_CONVERSATION(protocol); + test_protocol->join_channel_finish += 1; + + return g_task_propagate_boolean(G_TASK(result), error); +} + static void test_purple_protocol_conversation_set_topic_async(PurpleProtocolConversation *protocol, PurpleConversation *conversation, @@ -313,6 +450,10 @@ iface->set_topic_async = test_purple_protocol_conversation_set_topic_async; iface->set_topic_finish = test_purple_protocol_conversation_set_topic_finish; + + iface->get_channel_join_details = test_purple_protocol_conversation_get_channel_join_details; + iface->join_channel_async = test_purple_protocol_conversation_join_channel_async; + iface->join_channel_finish = test_purple_protocol_conversation_join_channel_finish; } G_DEFINE_TYPE_WITH_CODE(TestPurpleProtocolConversation, test_purple_protocol_conversation, @@ -328,6 +469,10 @@ protocol->set_topic_async = 0; protocol->set_topic_finish = 0; + + protocol->get_channel_join_details = 0; + protocol->join_channel_async = 0; + protocol->join_channel_finish = 0; } static void @@ -495,6 +640,104 @@ } /****************************************************************************** + * TestProtocolConversation Channel Join Tests + ****************************************************************************/ +static void +test_purple_protocol_conversation_get_channel_join_details_normal(void) { + TestPurpleProtocolConversation *test_protocol = NULL; + PurpleAccount *account = NULL; + PurpleChannelJoinDetails *details = NULL; + PurpleProtocolConversation *protocol = NULL; + + test_protocol = g_object_new(test_purple_protocol_conversation_get_type(), + NULL); + protocol = PURPLE_PROTOCOL_CONVERSATION(test_protocol); + + account = purple_account_new("test", "test"); + + details = purple_protocol_conversation_get_channel_join_details(protocol, + account); + + g_assert_true(PURPLE_IS_CHANNEL_JOIN_DETAILS(details)); + + g_assert_cmpuint(test_protocol->get_channel_join_details, ==, 1); + + g_clear_object(&details); + g_clear_object(&account); + g_clear_object(&test_protocol); +} + +static void +test_purple_protocol_conversation_join_channel_cb(GObject *obj, + GAsyncResult *res, + G_GNUC_UNUSED gpointer data) +{ + TestPurpleProtocolConversation *test_protocol = NULL; + PurpleProtocolConversation *protocol = NULL; + GError *error = NULL; + gboolean result = FALSE; + + protocol = PURPLE_PROTOCOL_CONVERSATION(obj); + test_protocol = TEST_PURPLE_PROTOCOL_CONVERSATION(obj); + + result = purple_protocol_conversation_join_channel_finish(protocol, res, + &error); + + if(test_protocol->should_error) { + g_assert_error(error, TEST_PURPLE_PROTOCOL_CONVERSATION_DOMAIN, 0); + g_clear_error(&error); + g_assert_false(result); + } else { + g_assert_no_error(error); + g_assert_true(result); + } + + g_main_loop_quit(loop); +} + +static gboolean +test_purple_protocol_conversation_join_channel_idle(gpointer data) { + PurpleAccount *account = NULL; + PurpleChannelJoinDetails *details = NULL; + PurpleProtocolConversation *protocol = data; + + account = purple_account_new("test", "test"); + g_object_set_data_full(G_OBJECT(protocol), "account", account, + g_object_unref); + + details = purple_channel_join_details_new(FALSE, FALSE); + g_object_set_data_full(G_OBJECT(protocol), "details", details, + g_object_unref); + + purple_protocol_conversation_join_channel_async(protocol, account, details, + NULL, + test_purple_protocol_conversation_join_channel_cb, + NULL); + + return G_SOURCE_REMOVE; +} + +static void +test_purple_protocol_conversation_join_channel_normal(gconstpointer data) { + TestPurpleProtocolConversation *protocol = NULL; + + protocol = g_object_new(test_purple_protocol_conversation_get_type(), + NULL); + protocol->should_error = GPOINTER_TO_INT(data); + + g_idle_add(test_purple_protocol_conversation_join_channel_idle, protocol); + g_timeout_add_seconds(10, test_purple_protocol_conversation_timeout_cb, + loop); + + g_main_loop_run(loop); + + g_assert_cmpuint(protocol->join_channel_async, ==, 1); + g_assert_cmpuint(protocol->join_channel_finish, ==, 1); + + g_clear_object(&protocol); +} + +/****************************************************************************** * Main *****************************************************************************/ gint @@ -507,6 +750,7 @@ loop = g_main_loop_new(NULL, FALSE); + /* Empty send message tests. */ g_test_add_func("/protocol-conversation/empty/send-message-async", test_purple_protocol_conversation_empty_send_message_async); g_test_add_func("/protocol-conversation/empty/send-message-finish", @@ -516,10 +760,19 @@ g_test_add_func("/protocol-conversation/empty/set-topic-finish", test_purple_protocol_conversation_empty_set_topic_finish); - g_test_add_data_func("/protocol-contacts/normal/send-message-normal", + /* Empty join channel tests. */ + g_test_add_func("/protocol-conversation/empty/get-channel-join-details", + test_purple_protocol_conversation_empty_get_channel_join_details); + g_test_add_func("/protocol-conversation/empty/join_channel_async", + test_purple_protocol_conversation_empty_join_channel_async); + g_test_add_func("/protocol-conversation/empty/join_channel_finish", + test_purple_protocol_conversation_empty_join_channel_finish); + + /* Normal send message tests. */ + g_test_add_data_func("/protocol-conversation/normal/send-message-normal", GINT_TO_POINTER(FALSE), test_purple_protocol_conversation_send_message_normal); - g_test_add_data_func("/protocol-contacts/normal/send-message-error", + g_test_add_data_func("/protocol-conversation/normal/send-message-error", GINT_TO_POINTER(TRUE), test_purple_protocol_conversation_send_message_normal); g_test_add_data_func("/protocol-contacts/normal/set-topic-normal", @@ -529,6 +782,16 @@ GINT_TO_POINTER(TRUE), test_purple_protocol_conversation_set_topic_normal); + /* Normal join channel tests. */ + g_test_add_func("/protocol-conversation/normal/get-channel-join-details", + test_purple_protocol_conversation_get_channel_join_details_normal); + g_test_add_data_func("/protocol-conversation/normal/join-channel-normal", + GINT_TO_POINTER(FALSE), + test_purple_protocol_conversation_join_channel_normal); + g_test_add_data_func("/protocol-conversation/normal/join-channel-error", + GINT_TO_POINTER(TRUE), + test_purple_protocol_conversation_join_channel_normal); + ret = g_test_run(); g_main_loop_unref(loop);