# HG changeset patch
# Node ID dc49c8e0974f
# Parent  8b4f02641aa9
diff --git a/libpurple/purpleconversation.c b/libpurple/purpleconversation.c
--- a/libpurple/purpleconversation.c
+++ b/libpurple/purpleconversation.c
@@ -677,6 +677,7 @@
 
 	purple_request_close_with_handle(conversation);
 
+	g_clear_object(&conversation->account);
 	g_clear_pointer(&conversation->id, g_free);
 	g_clear_object(&conversation->avatar);
 	g_clear_pointer(&conversation->name, g_free);
diff --git a/libpurple/purpleprotocolconversation.h b/libpurple/purpleprotocolconversation.h
--- a/libpurple/purpleprotocolconversation.h
+++ b/libpurple/purpleprotocolconversation.h
@@ -77,6 +77,9 @@
 	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 (*leave_conversation_async)(PurpleProtocolConversation *protocol, PurpleConversation *conversation, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data);
+	gboolean (*leave_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);
 
@@ -172,6 +175,59 @@
 PurpleConversation *purple_protocol_conversation_create_conversation_finish(PurpleProtocolConversation *protocol, GAsyncResult *result, GError **error);
 
 /**
+ * purple_protocol_conversation_implements_leave_conversation:
+ * @protocol: The instance.
+ *
+ * Checks if @protocol implements
+ * [vfunc@ProtocolConversation.leave_conversation_async] and
+ * [vfunc@ProtocolConversation.leave_conversation_finish].
+ *
+ * Returns: %TRUE if everything is implemented, otherwise %FALSE.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+gboolean purple_protocol_conversation_implements_leave_conversation(PurpleProtocolConversation *protocol);
+
+/**
+ * purple_protocol_conversation_leave_conversation_async:
+ * @protocol: The instance.
+ * @conversation: The conversation to leave.
+ * @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.
+ *
+ * Attempts to leave @conversation.
+ *
+ * This needs to support all conversation types that @protocol supports.
+ *
+ * Call [method@ProtocolConversation.leave_conversation_finish] from @callback
+ * to determine whether or not leaving was successful.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+void purple_protocol_conversation_leave_conversation_async(PurpleProtocolConversation *protocol, PurpleConversation *conversation, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data);
+
+/**
+ * purple_protocol_conversation_leave_conversation_finish:
+ * @protocol: The instance.
+ * @result: The result that was passed to the callback.
+ * @error: A return address for a #GError.
+ *
+ * Finishes a previous call to
+ * [method@ProtocolConversation.leave_conversation_async].
+ *
+ * Returns: %TRUE if the conversation was left successfully, otherwise %FALSE
+ *          with @error set.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+gboolean purple_protocol_conversation_leave_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.
diff --git a/libpurple/purpleprotocolconversation.c b/libpurple/purpleprotocolconversation.c
--- a/libpurple/purpleprotocolconversation.c
+++ b/libpurple/purpleprotocolconversation.c
@@ -148,6 +148,89 @@
 	return FALSE;
 }
 
+gboolean
+purple_protocol_conversation_implements_leave_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->leave_conversation_async == NULL) {
+		return FALSE;
+	}
+
+	if(iface->leave_conversation_finish == NULL) {
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+void
+purple_protocol_conversation_leave_conversation_async(PurpleProtocolConversation *protocol,
+                                                      PurpleConversation *conversation,
+                                                      GCancellable *cancellable,
+                                                      GAsyncReadyCallback callback,
+                                                      gpointer data)
+{
+	PurpleProtocolConversationInterface *iface = NULL;
+
+	g_return_if_fail(PURPLE_IS_PROTOCOL_CONVERSATION(protocol));
+	g_return_if_fail(PURPLE_IS_CONVERSATION(conversation));
+
+	iface = PURPLE_PROTOCOL_CONVERSATION_GET_IFACE(protocol);
+	if(iface != NULL && iface->leave_conversation_async != NULL) {
+		iface->leave_conversation_async(protocol, conversation, cancellable,
+		                                callback, data);
+	} else {
+		GTask *task = NULL;
+
+		task = g_task_new(protocol, cancellable, callback, data);
+		g_task_return_new_error(task, PURPLE_PROTOCOL_CONVERSATION_DOMAIN, 0,
+		                        "%s does not implement leave_conversation_async",
+		                        G_OBJECT_TYPE_NAME(protocol));
+		g_task_set_source_tag(task,
+		                      purple_protocol_conversation_leave_conversation_async);
+
+		g_clear_object(&task);
+	}
+}
+
+gboolean
+purple_protocol_conversation_leave_conversation_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->leave_conversation_finish != NULL) {
+		return iface->leave_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_leave_conversation_async) {
+			return g_task_propagate_boolean(task, error);
+		} else {
+			g_warning("%s does not implement leave_conversation_finish",
+			          G_OBJECT_TYPE_NAME(protocol));
+		}
+	}
+
+	return FALSE;
+}
+
 void
 purple_protocol_conversation_send_message_async(PurpleProtocolConversation *protocol,
                                                 PurpleConversation *conversation,
diff --git a/libpurple/tests/test_protocol_conversation.c b/libpurple/tests/test_protocol_conversation.c
--- a/libpurple/tests/test_protocol_conversation.c
+++ b/libpurple/tests/test_protocol_conversation.c
@@ -189,6 +189,81 @@
 	g_test_trap_assert_stderr("*Purple-WARNING*TestPurpleProtocolConversationEmpty*create_conversation_finish*");
 }
 
+/******************************************************************************
+ * Empty Leave Conversation Tests
+ *****************************************************************************/
+static void
+test_purple_protocol_conversation_empty_leave_conversation_async_cb(GObject *source,
+                                                                    GAsyncResult *result,
+                                                                    G_GNUC_UNUSED gpointer data)
+{
+	GError *error = NULL;
+	gboolean left = FALSE;
+
+	g_assert_true(PURPLE_IS_PROTOCOL_CONVERSATION(source));
+
+	left = purple_protocol_conversation_leave_conversation_finish(PURPLE_PROTOCOL_CONVERSATION(source),
+	                                                              result,
+	                                                              &error);
+	g_assert_error(error, PURPLE_PROTOCOL_CONVERSATION_DOMAIN, 0);
+	g_clear_error(&error);
+	g_assert_false(left);
+}
+
+static void
+test_purple_protocol_conversation_empty_leave_conversation_async(void) {
+	PurpleAccount *account = NULL;
+	PurpleConversation *conversation = NULL;
+	PurpleProtocolConversation *protocol = NULL;
+
+	protocol = g_object_new(test_purple_protocol_conversation_empty_get_type(),
+	                        NULL);
+	account = purple_account_new("test", "test");
+	conversation = g_object_new(
+		PURPLE_TYPE_CONVERSATION,
+		"account", account,
+		NULL);
+
+	purple_protocol_conversation_leave_conversation_async(protocol,
+	                                                      conversation,
+	                                                      NULL,
+                                                          test_purple_protocol_conversation_empty_leave_conversation_async_cb,
+	                                                      NULL);
+
+	g_main_context_iteration(NULL, FALSE);
+
+	g_assert_finalize_object(conversation);
+	g_assert_finalize_object(account);
+	g_assert_finalize_object(protocol);
+}
+
+static void
+test_purple_protocol_conversation_empty_leave_conversation_finish(void) {
+	if(g_test_subprocess()) {
+		PurpleProtocolConversation *protocol = NULL;
+		GError *error = NULL;
+		GTask *task = NULL;
+		gboolean left = FALSE;
+
+		protocol = g_object_new(test_purple_protocol_conversation_empty_get_type(),
+		                        NULL);
+
+		task = g_task_new(protocol, NULL, NULL, NULL);
+
+		left = purple_protocol_conversation_leave_conversation_finish(protocol,
+		                                                              G_ASYNC_RESULT(task),
+		                                                              &error);
+		g_assert_no_error(error);
+		g_assert_false(left);
+
+		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*leave_conversation_finish*");
+}
+
 static void
 test_purple_protocol_conversation_empty_send_message_async(void) {
 	if(g_test_subprocess()) {
@@ -479,6 +554,9 @@
 	guint create_conversation_async;
 	guint create_conversation_finish;
 
+	guint leave_conversation_async;
+	guint leave_conversation_finish;
+
 	guint send_message_async;
 	guint send_message_finish;
 
@@ -562,6 +640,46 @@
 }
 
 static void
+test_purple_protocol_conversation_leave_conversation_async(PurpleProtocolConversation *protocol,
+                                                           PurpleConversation *conversation,
+                                                           GCancellable *cancellable,
+                                                           GAsyncReadyCallback callback,
+                                                           gpointer data)
+{
+	TestPurpleProtocolConversation *test_protocol = NULL;
+	GTask *task = NULL;
+
+	g_assert_true(PURPLE_IS_CONVERSATION(conversation));
+
+	test_protocol = TEST_PURPLE_PROTOCOL_CONVERSATION(protocol);
+	test_protocol->leave_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,
+		                                    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_leave_conversation_finish(PurpleProtocolConversation *protocol,
+                                                            GAsyncResult *result,
+                                                            GError **error)
+{
+	TestPurpleProtocolConversation *test_protocol = NULL;
+
+	test_protocol = TEST_PURPLE_PROTOCOL_CONVERSATION(protocol);
+	test_protocol->leave_conversation_finish += 1;
+
+	return g_task_propagate_boolean(G_TASK(result), error);
+}
+
+static void
 test_purple_protocol_conversation_send_message_async(PurpleProtocolConversation *protocol,
                                                      PurpleConversation *conversation,
                                                      PurpleMessage *message,
@@ -763,6 +881,9 @@
 	iface->create_conversation_async = test_purple_protocol_conversation_create_conversation_async;
 	iface->create_conversation_finish = test_purple_protocol_conversation_create_conversation_finish;
 
+	iface->leave_conversation_async = test_purple_protocol_conversation_leave_conversation_async;
+	iface->leave_conversation_finish = test_purple_protocol_conversation_leave_conversation_finish;
+
 	iface->send_message_async = test_purple_protocol_conversation_send_message_async;
 	iface->send_message_finish = test_purple_protocol_conversation_send_message_finish;
 
@@ -792,6 +913,9 @@
 	protocol->create_conversation_async = 0;
 	protocol->create_conversation_finish = 0;
 
+	protocol->leave_conversation_async = 0;
+	protocol->leave_conversation_finish = 0;
+
 	protocol->send_message_async = 0;
 	protocol->send_message_finish = 0;
 
@@ -920,6 +1044,81 @@
 }
 
 /******************************************************************************
+ * TestProtocolConversation LeaveConversation Tests
+ *****************************************************************************/
+static void
+test_purple_protocol_conversation_implements_leave_conversation(void) {
+	PurpleProtocolConversation *protocol = NULL;
+
+	protocol = g_object_new(test_purple_protocol_conversation_get_type(),
+	                        NULL);
+
+	g_assert_true(purple_protocol_conversation_implements_leave_conversation(protocol));
+
+	g_assert_finalize_object(protocol);
+}
+
+static void
+test_purple_protocol_conversation_leave_conversation_cb(GObject *obj,
+                                                        GAsyncResult *result,
+                                                        G_GNUC_UNUSED gpointer data)
+{
+	TestPurpleProtocolConversation *test_protocol = NULL;
+	PurpleProtocolConversation *protocol = NULL;
+	GError *error = NULL;
+	gboolean left = FALSE;
+
+	protocol = PURPLE_PROTOCOL_CONVERSATION(obj);
+	test_protocol = TEST_PURPLE_PROTOCOL_CONVERSATION(obj);
+
+	left = purple_protocol_conversation_leave_conversation_finish(protocol,
+	                                                              result,
+	                                                              &error);
+
+	if(test_protocol->should_error) {
+		g_assert_error(error, TEST_PURPLE_PROTOCOL_CONVERSATION_DOMAIN, 0);
+		g_clear_error(&error);
+		g_assert_false(left);
+	} else {
+		g_assert_no_error(error);
+		g_assert_true(left);
+	}
+}
+
+static void
+test_purple_protocol_conversation_leave_conversation_normal(gconstpointer data)
+{
+	TestPurpleProtocolConversation *protocol = NULL;
+	PurpleAccount *account = NULL;
+	PurpleConversation *conversation = NULL;
+
+	protocol = g_object_new(test_purple_protocol_conversation_get_type(),
+	                        NULL);
+	protocol->should_error = GPOINTER_TO_INT(data);
+
+	account = purple_account_new("test", "test");
+	conversation = g_object_new(
+		PURPLE_TYPE_CONVERSATION,
+		"account", account,
+		NULL);
+
+	purple_protocol_conversation_leave_conversation_async(PURPLE_PROTOCOL_CONVERSATION(protocol),
+	                                                      conversation,
+	                                                      NULL,
+	                                                      test_purple_protocol_conversation_leave_conversation_cb,
+	                                                      NULL);
+
+	while(g_main_context_iteration(NULL, FALSE));
+
+	g_assert_cmpuint(protocol->leave_conversation_async, ==, 1);
+	g_assert_cmpuint(protocol->leave_conversation_finish, ==, 1);
+
+	g_assert_finalize_object(conversation);
+	g_assert_finalize_object(account);
+	g_assert_finalize_object(protocol);
+}
+
+/******************************************************************************
  * TestProtocolConversation SendMessage Tests
  *****************************************************************************/
 static void
@@ -1310,6 +1509,12 @@
 	g_test_add_func("/protocol-conversation/empty/create-conversation-finish",
 	                test_purple_protocol_conversation_empty_create_conversation_finish);
 
+	/* Empty leave conversation tests. */
+	g_test_add_func("/protocol-conversation/empty/leave-conversation-async",
+	                test_purple_protocol_conversation_empty_leave_conversation_async);
+	g_test_add_func("/protocol-conversation/empty/leave-conversation-finish",
+	                test_purple_protocol_conversation_empty_leave_conversation_finish);
+
 	/* Empty send message tests. */
 	g_test_add_func("/protocol-conversation/empty/send-message-async",
 	                test_purple_protocol_conversation_empty_send_message_async);
@@ -1348,6 +1553,16 @@
 	                     GINT_TO_POINTER(TRUE),
 	                     test_purple_protocol_conversation_create_conversation_normal);
 
+	/* Normal leave conversation tests. */
+	g_test_add_func("/protocol-conversation/normal/implements-leave-conversation",
+	                test_purple_protocol_conversation_implements_leave_conversation);
+	g_test_add_data_func("/protocol-conversation/normal/leave-conversation-normal",
+	                     GINT_TO_POINTER(FALSE),
+	                     test_purple_protocol_conversation_leave_conversation_normal);
+	g_test_add_data_func("/protocol-conversation/normal/leave-conversation-error",
+	                     GINT_TO_POINTER(TRUE),
+	                     test_purple_protocol_conversation_leave_conversation_normal);
+
 	/* Normal send message tests. */
 	g_test_add_data_func("/protocol-conversation/normal/send-message-normal",
 	                     GINT_TO_POINTER(FALSE),
