pidgin/pidgin

Fix several leaks in tests

14 months ago, Elliott Sales de Andrade
4e1bf25f5575
Fix several leaks in tests

All of these are specific to tests, not the library code.

For the moment, `protocol_xfer` still leaks connections (and anything they hold on to) because it is very difficult to disentangle them from the connection manager in the partially implemented state they are in.

This fixes leaks of options in the account option test (these two leaks occur for every test since they all leak the option):
```
61 (48 direct, 13 indirect) bytes in 1 blocks are definitely lost in loss record 133 of 276
at 0x4848464: calloc (vg_replace_malloc.c:1340)
by 0x49F75F0: g_malloc0 (gmem.c:163)
by 0x48C3B2E: purple_account_option_new (purpleaccountoption.c:78)
by 0x4014AF: test_purple_account_option_copy_int (test_account_option.c:67)
by 0x4A1CC7D: UnknownInlinedFun (gtestutils.c:2933)
by 0x4A1CC7D: g_test_run_suite_internal (gtestutils.c:3021)
by 0x4A1C9E4: g_test_run_suite_internal (gtestutils.c:3038)
by 0x4A1C9E4: g_test_run_suite_internal (gtestutils.c:3038)
by 0x4A1D181: g_test_run_suite (gtestutils.c:3115)
by 0x4A156EC: UnknownInlinedFun (gtestutils.c:2234)
by 0x4A156EC: g_test_run (gtestutils.c:2221)
by 0x401721: main (test_account_option.c:157)

61 (48 direct, 13 indirect) bytes in 1 blocks are definitely lost in loss record 134 of 276
at 0x4848464: calloc (vg_replace_malloc.c:1340)
by 0x49F75F0: g_malloc0 (gmem.c:163)
by 0x48C3BC7: purple_account_option_copy (purpleaccountoption.c:93)
by 0x4014BF: test_purple_account_option_copy_int (test_account_option.c:68)
by 0x4A1CC7D: UnknownInlinedFun (gtestutils.c:2933)
by 0x4A1CC7D: g_test_run_suite_internal (gtestutils.c:3021)
by 0x4A1C9E4: g_test_run_suite_internal (gtestutils.c:3038)
by 0x4A1C9E4: g_test_run_suite_internal (gtestutils.c:3038)
by 0x4A1D181: g_test_run_suite (gtestutils.c:3115)
by 0x4A156EC: UnknownInlinedFun (gtestutils.c:2234)
by 0x4A156EC: g_test_run (gtestutils.c:2221)
by 0x401721: main (test_account_option.c:157)
```
leaks in the credential manager test (times 3 for read/write/cancel tests):
```
69 (16 direct, 53 indirect) bytes in 1 blocks are definitely lost in loss record 2,427 of 3,503
at 0x484386F: malloc (vg_replace_malloc.c:393)
by 0x4A58168: g_malloc (gmem.c:130)
by 0x4A6FAB5: g_slice_alloc (gslice.c:1074)
by 0x4A700EC: g_slice_alloc0 (gslice.c:1100)
by 0x4A3BECB: g_error_allocate (gerror.c:710)
by 0x4A3C93F: UnknownInlinedFun (gerror.c:724)
by 0x4A3C93F: g_error_new_valist (gerror.c:766)
by 0x4BEE558: g_task_return_new_error (gtask.c:1941)
by 0x48D82C0: purple_credential_manager_read_password_async (purplecredentialmanager.c:492)
by 0x403634: test_purple_credential_manager_no_provider_read_password_idle (test_credential_manager.c:329)
by 0x4A4ECB1: g_idle_dispatch (gmain.c:6124)
by 0x4A4FCBE: UnknownInlinedFun (gmain.c:3444)
by 0x4A4FCBE: g_main_context_dispatch (gmain.c:4162)
by 0x4AA5597: g_main_context_iterate.constprop.0 (gmain.c:4238)
by 0x4A4F28E: g_main_loop_run (gmain.c:4438)
by 0x40369F: test_purple_credential_manager_no_provider_read_password_async (test_credential_manager.c:345)
by 0x4A7DC7D: UnknownInlinedFun (gtestutils.c:2933)
by 0x4A7DC7D: g_test_run_suite_internal (gtestutils.c:3021)
by 0x4A7D9E4: g_test_run_suite_internal (gtestutils.c:3038)
by 0x4A7D9E4: g_test_run_suite_internal (gtestutils.c:3038)
by 0x4A7E181: g_test_run_suite (gtestutils.c:3115)
by 0x4A766EC: UnknownInlinedFun (gtestutils.c:2234)
by 0x4A766EC: g_test_run (gtestutils.c:2221)
by 0x4048F6: main (test_credential_manager.c:695)
```
a leak in the image test:
```
161 bytes in 1 blocks are definitely lost in loss record 260 of 274
at 0x484386F: malloc (vg_replace_malloc.c:393)
by 0x4A55363: g_try_malloc (gmem.c:286)
by 0x4A3D630: UnknownInlinedFun (gfileutils.c:819)
by 0x4A3D630: UnknownInlinedFun (gfileutils.c:924)
by 0x4A3D630: g_file_get_contents (gfileutils.c:1027)
by 0x401890: test_image_new_from_file (test_image.c:144)
by 0x4A7DC7D: UnknownInlinedFun (gtestutils.c:2933)
by 0x4A7DC7D: g_test_run_suite_internal (gtestutils.c:3021)
by 0x4A7D9E4: g_test_run_suite_internal (gtestutils.c:3038)
by 0x4A7E181: g_test_run_suite (gtestutils.c:3115)
by 0x4A766EC: UnknownInlinedFun (gtestutils.c:2234)
by 0x4A766EC: g_test_run (gtestutils.c:2221)
by 0x40195D: main (test_image.c:172)
```
a leak in queued output stream test:
```
72 (40 direct, 32 indirect) bytes in 1 blocks are definitely lost in loss record 219 of 396
at 0x49D51EF: g_type_create_instance (gtype.c:1909)
by 0x49BAC1F: g_object_new_internal (gobject.c:2228)
by 0x49BC247: g_object_new_with_properties (gobject.c:2391)
by 0x49BCFF0: g_object_new (gobject.c:2037)
by 0x402003: test_queued_output_stream_push_bytes_async_error (test_queued_output_stream.c:219)
by 0x4A7DC7D: UnknownInlinedFun (gtestutils.c:2933)
by 0x4A7DC7D: g_test_run_suite_internal (gtestutils.c:3021)
by 0x4A7D9E4: g_test_run_suite_internal (gtestutils.c:3038)
by 0x4A7E181: g_test_run_suite (gtestutils.c:3115)
by 0x4A766EC: UnknownInlinedFun (gtestutils.c:2234)
by 0x4A766EC: g_test_run (gtestutils.c:2221)
by 0x402429: main (test_queued_output_stream.c:280)
```
and protocol xfer tests (times 3 for each test that creates a test protocol object):
```
112 (48 direct, 64 indirect) bytes in 1 blocks are definitely lost in loss record 3,430 of 3,698
at 0x49D51EF: g_type_create_instance (gtype.c:1909)
by 0x49BAC1F: g_object_new_internal (gobject.c:2228)
by 0x49BC247: g_object_new_with_properties (gobject.c:2391)
by 0x49BCFF0: g_object_new (gobject.c:2037)
by 0x40291C: test_purple_protocol_xfer_send_file_func (test_protocol_xfer.c:146)
by 0x4A7DC7D: UnknownInlinedFun (gtestutils.c:2933)
by 0x4A7DC7D: g_test_run_suite_internal (gtestutils.c:3021)
by 0x4A7D9E4: g_test_run_suite_internal (gtestutils.c:3038)
by 0x4A7E181: g_test_run_suite (gtestutils.c:3115)
by 0x4A766EC: UnknownInlinedFun (gtestutils.c:2234)
by 0x4A766EC: g_test_run (gtestutils.c:2221)
by 0x402B64: main (test_protocol_xfer.c:195)
```
and util tests (times 3 for each call to `purple_text_strip_mnemonic` in the test):
```
5 bytes in 1 blocks are definitely lost in loss record 5 of 247
at 0x484386F: malloc (vg_replace_malloc.c:393)
by 0x49F7168: g_malloc (gmem.c:130)
by 0x491975B: purple_text_strip_mnemonic (util.c:895)
by 0x4015B0: test_util_text_strip_mnemonic (test_util.c:49)
by 0x4A1CC7D: UnknownInlinedFun (gtestutils.c:2933)
by 0x4A1CC7D: g_test_run_suite_internal (gtestutils.c:3021)
by 0x4A1C9E4: g_test_run_suite_internal (gtestutils.c:3038)
by 0x4A1C9E4: g_test_run_suite_internal (gtestutils.c:3038)
by 0x4A1D181: g_test_run_suite (gtestutils.c:3115)
by 0x4A156EC: UnknownInlinedFun (gtestutils.c:2234)
by 0x4A156EC: g_test_run (gtestutils.c:2221)
by 0x401901: main (test_util.c:224)
```
and these leaks in any test that initializes the test UI:
```
4,104 bytes in 1 blocks are possibly lost in loss record 3,451 of 3,457
at 0x484386F: malloc (vg_replace_malloc.c:393)
by 0x5235B67: sqlite3MemMalloc.lto_priv.0 (sqlite3.c:25493)
by 0x5232797: UnknownInlinedFun (sqlite3.c:29181)
by 0x5232797: UnknownInlinedFun (sqlite3.c:29227)
by 0x5232797: sqlite3Malloc.lto_priv.0 (sqlite3.c:29221)
by 0x523BD8B: pcache1Alloc.lto_priv.0 (sqlite3.c:53546)
by 0x5249A8B: UnknownInlinedFun (sqlite3.c:53675)
by 0x5249A8B: allocateTempSpace (sqlite3.c:70848)
by 0x52625A6: sqlite3VdbeExec.lto_priv.0 (sqlite3.c:93857)
by 0x525CBEE: UnknownInlinedFun (sqlite3.c:87995)
by 0x525CBEE: UnknownInlinedFun (sqlite3.c:88056)
by 0x525CBEE: sqlite3_step (sqlite3.c:88045)
by 0x529B324: sqlite3_exec (sqlite3.c:131002)
by 0x48FD558: purple_sqlite3_run_migration (purplesqlite3.c:37)
by 0x48FDBB4: purple_sqlite3_run_migrations_from_resources (purplesqlite3.c:195)
by 0x48FDED9: purple_sqlite_history_adapter_run_migrations (purplesqlitehistoryadapter.c:69)
by 0x48FE7F0: purple_sqlite_history_adapter_activate (purplesqlitehistoryadapter.c:287)
by 0x48DB656: purple_history_adapter_activate (purplehistoryadapter.c:181)
by 0x48DC9BC: purple_history_manager_set_active (purplehistorymanager.c:308)
by 0x402BA8: test_ui_init_history (test_ui.c:132)
by 0x402C80: test_ui_purple_init (test_ui.c:167)
by 0x4027BB: main (test_contact.c:88)

4,368 bytes in 1 blocks are possibly lost in loss record 3,453 of 3,457
at 0x484386F: malloc (vg_replace_malloc.c:393)
by 0x5235B67: sqlite3MemMalloc.lto_priv.0 (sqlite3.c:25493)
by 0x5232797: UnknownInlinedFun (sqlite3.c:29181)
by 0x5232797: UnknownInlinedFun (sqlite3.c:29227)
by 0x5232797: sqlite3Malloc.lto_priv.0 (sqlite3.c:29221)
by 0x523BD8B: pcache1Alloc.lto_priv.0 (sqlite3.c:53546)
by 0x5240077: UnknownInlinedFun (sqlite3.c:53634)
by 0x5240077: pcache1FetchStage2 (sqlite3.c:54104)
by 0x5243E9C: UnknownInlinedFun (sqlite3.c:52671)
by 0x5243E9C: getPageNormal.lto_priv.0 (sqlite3.c:60628)
by 0x524A510: UnknownInlinedFun (sqlite3.c:60805)
by 0x524A510: btreeGetPage.lto_priv.0 (sqlite3.c:70289)
by 0x524C2F6: UnknownInlinedFun (sqlite3.c:71257)
by 0x524C2F6: sqlite3BtreeBeginTrans.lto_priv.0 (sqlite3.c:71647)
by 0x5266B3A: sqlite3VdbeExec.lto_priv.0 (sqlite3.c:93532)
by 0x525CBEE: UnknownInlinedFun (sqlite3.c:87995)
by 0x525CBEE: UnknownInlinedFun (sqlite3.c:88056)
by 0x525CBEE: sqlite3_step (sqlite3.c:88045)
by 0x48FD715: purple_sqlite3_get_schema_version (purplesqlite3.c:79)
by 0x48FD9DD: purple_sqlite3_run_migrations_from_resources (purplesqlite3.c:146)
by 0x48FDED9: purple_sqlite_history_adapter_run_migrations (purplesqlitehistoryadapter.c:69)
by 0x48FE7F0: purple_sqlite_history_adapter_activate (purplesqlitehistoryadapter.c:287)
by 0x48DB656: purple_history_adapter_activate (purplehistoryadapter.c:181)
by 0x48DC9BC: purple_history_manager_set_active (purplehistorymanager.c:308)
by 0x402BA8: test_ui_init_history (test_ui.c:132)
by 0x402C80: test_ui_purple_init (test_ui.c:167)
by 0x4027BB: main (test_contact.c:88)

4,368 bytes in 1 blocks are possibly lost in loss record 3,454 of 3,457
at 0x484386F: malloc (vg_replace_malloc.c:393)
by 0x5235B67: sqlite3MemMalloc.lto_priv.0 (sqlite3.c:25493)
by 0x5232797: UnknownInlinedFun (sqlite3.c:29181)
by 0x5232797: UnknownInlinedFun (sqlite3.c:29227)
by 0x5232797: sqlite3Malloc.lto_priv.0 (sqlite3.c:29221)
by 0x523BD8B: pcache1Alloc.lto_priv.0 (sqlite3.c:53546)
by 0x5240077: UnknownInlinedFun (sqlite3.c:53634)
by 0x5240077: pcache1FetchStage2 (sqlite3.c:54104)
by 0x5243E9C: UnknownInlinedFun (sqlite3.c:52671)
by 0x5243E9C: getPageNormal.lto_priv.0 (sqlite3.c:60628)
by 0x52499BC: UnknownInlinedFun (sqlite3.c:60805)
by 0x52499BC: UnknownInlinedFun (sqlite3.c:70289)
by 0x52499BC: btreeGetUnusedPage (sqlite3.c:70432)
by 0x524F504: allocateBtreePage.lto_priv.0 (sqlite3.c:74604)
by 0x5256209: btreeCreateTable.lto_priv.0 (sqlite3.c:77830)
by 0x5265EB2: UnknownInlinedFun (sqlite3.c:77849)
by 0x5265EB2: sqlite3VdbeExec.lto_priv.0 (sqlite3.c:96382)
by 0x525CBEE: UnknownInlinedFun (sqlite3.c:87995)
by 0x525CBEE: UnknownInlinedFun (sqlite3.c:88056)
by 0x525CBEE: sqlite3_step (sqlite3.c:88045)
by 0x529B324: sqlite3_exec (sqlite3.c:131002)
by 0x48FD558: purple_sqlite3_run_migration (purplesqlite3.c:37)
by 0x48FDBB4: purple_sqlite3_run_migrations_from_resources (purplesqlite3.c:195)
by 0x48FDED9: purple_sqlite_history_adapter_run_migrations (purplesqlitehistoryadapter.c:69)
by 0x48FE7F0: purple_sqlite_history_adapter_activate (purplesqlitehistoryadapter.c:287)
by 0x48DB656: purple_history_adapter_activate (purplehistoryadapter.c:181)
by 0x48DC9BC: purple_history_manager_set_active (purplehistorymanager.c:308)
by 0x402BA8: test_ui_init_history (test_ui.c:132)
by 0x402C80: test_ui_purple_init (test_ui.c:167)
by 0x4027BB: main (test_contact.c:88)
```

Testing Done:
Ran tests in valgrind, and all above leaks were gone except for noted `protocol_xfer` issues, and a bunch of leaks of the `PurpleBuddy`-`PurpleContact` compatibility bindings, which will go away in their entirety eventually.

Reviewed at https://reviews.imfreedom.org/r/2385/
/*
* Purple - Internet Messaging Library
* Copyright (C) Pidgin Developers <devel@pidgin.im>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <https://www.gnu.org/licenses/>.
*/
#include <glib/gi18n-lib.h>
#include "purpleconversation.h"
#include "conversations.h"
#include "debug.h"
#include "purpleconversationmanager.h"
#include "purpleconversationmember.h"
#include "purpleenums.h"
#include "purplehistorymanager.h"
#include "purplemarkup.h"
#include "purpleprivate.h"
typedef struct {
PurpleAccount *account;
char *name;
char *title;
PurpleConversationUiOps *ui_ops;
PurpleConnectionFlags features;
GListStore *members;
} PurpleConversationPrivate;
enum {
PROP_0,
PROP_ACCOUNT,
PROP_NAME,
PROP_TITLE,
PROP_FEATURES,
PROP_MEMBERS,
N_PROPERTIES
};
static GParamSpec *properties[N_PROPERTIES] = { NULL, };
enum {
SIG_MEMBER_ADDED,
SIG_MEMBER_REMOVED,
N_SIGNALS,
};
static guint signals[N_SIGNALS] = {0, };
G_DEFINE_TYPE_WITH_PRIVATE(PurpleConversation, purple_conversation,
G_TYPE_OBJECT);
/**************************************************************************
* Helpers
**************************************************************************/
static gboolean
purple_conversation_check_member_equal(gconstpointer a, gconstpointer b) {
PurpleConversationMember *member_a = (PurpleConversationMember *)a;
PurpleConversationMember *member_b = (PurpleConversationMember *)b;
PurpleContactInfo *info_a = NULL;
PurpleContactInfo *info_b = NULL;
info_a = purple_conversation_member_get_contact_info(member_a);
info_b = purple_conversation_member_get_contact_info(member_b);
return (purple_contact_info_compare(info_a, info_b) == 0);
}
static void
common_send(PurpleConversation *conv, const gchar *message,
PurpleMessageFlags msgflags)
{
PurpleAccount *account;
PurpleConnection *gc;
PurpleConversationPrivate *priv = NULL;
gchar *displayed = NULL;
const gchar *sent, *me;
gint err = 0;
gpointer handle = NULL;
if(*message == '\0') {
return;
}
priv = purple_conversation_get_instance_private(conv);
account = purple_conversation_get_account(conv);
g_return_if_fail(PURPLE_IS_ACCOUNT(account));
gc = purple_account_get_connection(account);
g_return_if_fail(PURPLE_IS_CONNECTION(gc));
me = purple_contact_info_get_name_for_display(PURPLE_CONTACT_INFO(account));
/* Always linkify the text for display, unless we're explicitly asked to do
* otherwise. */
if(!(msgflags & PURPLE_MESSAGE_INVISIBLE)) {
if(msgflags & PURPLE_MESSAGE_NO_LINKIFY) {
displayed = g_strdup(message);
} else {
displayed = purple_markup_linkify(message);
}
}
if(displayed && (priv->features & PURPLE_CONNECTION_FLAG_HTML) &&
!(msgflags & PURPLE_MESSAGE_RAW))
{
sent = displayed;
} else {
sent = message;
}
msgflags |= PURPLE_MESSAGE_SEND;
handle = purple_conversations_get_handle();
if(PURPLE_IS_IM_CONVERSATION(conv)) {
const gchar *name = NULL;
PurpleMessage *msg = NULL;
name = purple_conversation_get_name(conv);
msg = purple_message_new_outgoing(priv->account, me, name, sent,
msgflags);
purple_signal_emit(handle, "sending-im-msg", account, msg);
if(!purple_message_is_empty(msg)) {
err = purple_serv_send_im(gc, msg);
if((err > 0) && (displayed != NULL)) {
/* revert the contents in case sending-im-msg changed it */
purple_message_set_contents(msg, displayed);
purple_conversation_write_message(conv, msg);
}
purple_signal_emit(handle, "sent-im-msg", account, msg);
}
g_object_unref(G_OBJECT(msg));
} else if(PURPLE_IS_CHAT_CONVERSATION(conv)) {
PurpleMessage *msg;
gint id = purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv));
msg = purple_message_new_outgoing(priv->account, me, NULL, sent,
msgflags);
purple_signal_emit(handle, "sending-chat-msg", account, msg, id);
if(!purple_message_is_empty(msg)) {
err = purple_serv_chat_send(gc, id, msg);
purple_signal_emit(handle, "sent-chat-msg", account, msg, id);
}
g_object_unref(G_OBJECT(msg));
}
if(err < 0) {
const gchar *who;
const gchar *msg;
who = purple_conversation_get_name(conv);
if(err == -E2BIG) {
msg = _("Unable to send message: The message is too large.");
if(!purple_conversation_present_error(who, account, msg)) {
gchar *msg2 = g_strdup_printf(_("Unable to send message to %s."),
who);
purple_notify_error(gc, NULL, msg2,
_("The message is too large."),
purple_request_cpar_from_connection(gc));
g_free(msg2);
}
} else if(err == -ENOTCONN) {
purple_debug_error("conversation", "Not yet connected.");
} else {
msg = _("Unable to send message.");
if(!purple_conversation_present_error(who, account, msg)) {
gchar *msg2 = g_strdup_printf(_("Unable to send message to %s."),
who);
purple_notify_error(gc, NULL, msg2, NULL,
purple_request_cpar_from_connection(gc));
g_free(msg2);
}
}
}
g_free(displayed);
}
static void
purple_conversation_send_confirm_cb(gpointer *data) {
PurpleConversation *conv = data[0];
gchar *message = data[1];
g_free(data);
if(!PURPLE_IS_CONVERSATION(conv)) {
/* Maybe it was closed before this callback was called. */
return;
}
common_send(conv, message, 0);
}
/**************************************************************************
* GObject Implementation
**************************************************************************/
static void
purple_conversation_set_property(GObject *obj, guint param_id,
const GValue *value, GParamSpec *pspec)
{
PurpleConversation *conv = PURPLE_CONVERSATION(obj);
PurpleConversationPrivate *priv = NULL;
priv = purple_conversation_get_instance_private(conv);
switch (param_id) {
case PROP_ACCOUNT:
purple_conversation_set_account(conv, g_value_get_object(value));
break;
case PROP_NAME:
g_free(priv->name);
priv->name = g_value_dup_string(value);
break;
case PROP_TITLE:
g_free(priv->title);
priv->title = g_value_dup_string(value);
break;
case PROP_FEATURES:
purple_conversation_set_features(conv, g_value_get_flags(value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
break;
}
}
static void
purple_conversation_get_property(GObject *obj, guint param_id, GValue *value,
GParamSpec *pspec)
{
PurpleConversation *conv = PURPLE_CONVERSATION(obj);
switch(param_id) {
case PROP_ACCOUNT:
g_value_set_object(value, purple_conversation_get_account(conv));
break;
case PROP_NAME:
g_value_set_string(value, purple_conversation_get_name(conv));
break;
case PROP_TITLE:
g_value_set_string(value, purple_conversation_get_title(conv));
break;
case PROP_FEATURES:
g_value_set_flags(value, purple_conversation_get_features(conv));
break;
case PROP_MEMBERS:
g_value_set_object(value, purple_conversation_get_members(conv));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
break;
}
}
static void
purple_conversation_init(PurpleConversation *conv) {
PurpleConversationPrivate *priv = NULL;
priv = purple_conversation_get_instance_private(conv);
priv->members = g_list_store_new(PURPLE_TYPE_CONVERSATION_MEMBER);
}
static void
purple_conversation_constructed(GObject *object) {
PurpleConversation *conv = PURPLE_CONVERSATION(object);
PurpleAccount *account;
PurpleConnection *gc;
PurpleConversationManager *manager;
PurpleConversationUiOps *ops;
G_OBJECT_CLASS(purple_conversation_parent_class)->constructed(object);
g_object_get(object, "account", &account, NULL);
gc = purple_account_get_connection(account);
/* Check if we have a connection before we use it. The unit tests are one
* case where we will not have a connection.
*/
if(PURPLE_IS_CONNECTION(gc)) {
purple_conversation_set_features(conv,
purple_connection_get_flags(gc));
}
/* add the conversation to the appropriate lists */
manager = purple_conversation_manager_get_default();
purple_conversation_manager_register(manager, conv);
/* Auto-set the title. */
purple_conversation_autoset_title(conv);
/* Don't move this.. it needs to be one of the last things done otherwise
* it causes mysterious crashes on my system.
* -- Gary
*/
ops = purple_conversations_get_ui_ops();
purple_conversation_set_ui_ops(conv, ops);
if(ops != NULL && ops->create_conversation != NULL) {
ops->create_conversation(conv);
}
purple_signal_emit(purple_conversations_get_handle(),
"conversation-created", conv);
g_object_unref(account);
}
static void
purple_conversation_dispose(GObject *obj) {
g_object_set_data(obj, "is-finalizing", GINT_TO_POINTER(TRUE));
}
static void
purple_conversation_finalize(GObject *object) {
PurpleConversation *conv = PURPLE_CONVERSATION(object);
PurpleConversationManager *manager;
PurpleConversationPrivate *priv =
purple_conversation_get_instance_private(conv);
PurpleConversationUiOps *ops = purple_conversation_get_ui_ops(conv);
purple_request_close_with_handle(conv);
/* remove from conversations and im/chats lists prior to emit */
manager = purple_conversation_manager_get_default();
purple_conversation_manager_unregister(manager, conv);
purple_signal_emit(purple_conversations_get_handle(),
"deleting-conversation", conv);
if(ops != NULL && ops->destroy_conversation != NULL) {
ops->destroy_conversation(conv);
}
g_clear_pointer(&priv->name, g_free);
g_clear_pointer(&priv->title, g_free);
g_clear_object(&priv->members);
G_OBJECT_CLASS(purple_conversation_parent_class)->finalize(object);
}
static void
purple_conversation_class_init(PurpleConversationClass *klass) {
GObjectClass *obj_class = G_OBJECT_CLASS(klass);
obj_class->constructed = purple_conversation_constructed;
obj_class->dispose = purple_conversation_dispose;
obj_class->finalize = purple_conversation_finalize;
obj_class->get_property = purple_conversation_get_property;
obj_class->set_property = purple_conversation_set_property;
properties[PROP_ACCOUNT] = g_param_spec_object(
"account", "Account",
"The account for the conversation.",
PURPLE_TYPE_ACCOUNT,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
properties[PROP_NAME] = g_param_spec_string(
"name", "Name",
"The name of the conversation.",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
properties[PROP_TITLE] = g_param_spec_string(
"title", "Title",
"The title of the conversation.",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
properties[PROP_FEATURES] = g_param_spec_flags(
"features", "Connection features",
"The connection features of the conversation.",
PURPLE_TYPE_CONNECTION_FLAGS,
0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
/**
* PurpleConversation:members:
*
* The members that are currently in this conversation.
*
* Since: 3.0.0
*/
properties[PROP_MEMBERS] = g_param_spec_object(
"members", "members",
"The members that are currently in this conversation",
G_TYPE_LIST_MODEL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
/**
* PurpleConversation::member-added:
* @conversation: The instance.
* @member: The [class@Purple.ConversationMember] instance.
* @announce: Whether or not this addition should be announced.
* @message: (nullable): An optional message to use in the announcement.
*
* Emitted when a new member is added to this conversation.
*
* Since: 3.0.0
*/
signals[SIG_MEMBER_ADDED] = g_signal_new_class_handler(
"member-added",
G_OBJECT_CLASS_TYPE(klass),
G_SIGNAL_RUN_LAST,
NULL,
NULL,
NULL,
NULL,
G_TYPE_NONE,
3,
PURPLE_TYPE_CONVERSATION_MEMBER,
G_TYPE_BOOLEAN,
G_TYPE_STRING);
/**
* PurpleConversation::member-removed:
* @conversation: The instance.
* @member: The [class@Purple.ConversationMember] instance.
* @announce: Whether or not this removal should be announced.
* @message: (nullable): An optional message to use in the announcement.
*
* Emitted when member is removed from this conversation.
*
* Since: 3.0.0
*/
signals[SIG_MEMBER_REMOVED] = g_signal_new_class_handler(
"member-removed",
G_OBJECT_CLASS_TYPE(klass),
G_SIGNAL_RUN_LAST,
NULL,
NULL,
NULL,
NULL,
G_TYPE_NONE,
3,
PURPLE_TYPE_CONVERSATION_MEMBER,
G_TYPE_BOOLEAN,
G_TYPE_STRING);
}
/******************************************************************************
* Public API
*****************************************************************************/
void
purple_conversation_present(PurpleConversation *conv) {
PurpleConversationUiOps *ops;
g_return_if_fail(PURPLE_IS_CONVERSATION(conv));
ops = purple_conversation_get_ui_ops(conv);
if(ops && ops->present) {
ops->present(conv);
}
}
void
purple_conversation_set_features(PurpleConversation *conv,
PurpleConnectionFlags features)
{
PurpleConversationPrivate *priv = NULL;
g_return_if_fail(PURPLE_IS_CONVERSATION(conv));
priv = purple_conversation_get_instance_private(conv);
priv->features = features;
g_object_notify_by_pspec(G_OBJECT(conv), properties[PROP_FEATURES]);
purple_conversation_update(conv, PURPLE_CONVERSATION_UPDATE_FEATURES);
}
PurpleConnectionFlags
purple_conversation_get_features(PurpleConversation *conv) {
PurpleConversationPrivate *priv = NULL;
g_return_val_if_fail(PURPLE_IS_CONVERSATION(conv), 0);
priv = purple_conversation_get_instance_private(conv);
return priv->features;
}
void
purple_conversation_set_ui_ops(PurpleConversation *conv,
PurpleConversationUiOps *ops)
{
PurpleConversationPrivate *priv = NULL;
g_return_if_fail(PURPLE_IS_CONVERSATION(conv));
priv = purple_conversation_get_instance_private(conv);
if(priv->ui_ops == ops) {
return;
}
if(priv->ui_ops != NULL && priv->ui_ops->destroy_conversation != NULL) {
priv->ui_ops->destroy_conversation(conv);
}
priv->ui_ops = ops;
}
PurpleConversationUiOps *
purple_conversation_get_ui_ops(PurpleConversation *conv) {
PurpleConversationPrivate *priv = NULL;
g_return_val_if_fail(PURPLE_IS_CONVERSATION(conv), NULL);
priv = purple_conversation_get_instance_private(conv);
return priv->ui_ops;
}
void
purple_conversation_set_account(PurpleConversation *conv,
PurpleAccount *account)
{
PurpleConversationPrivate *priv = NULL;
g_return_if_fail(PURPLE_IS_CONVERSATION(conv));
priv = purple_conversation_get_instance_private(conv);
if(g_set_object(&priv->account, account)) {
g_object_notify_by_pspec(G_OBJECT(conv), properties[PROP_ACCOUNT]);
purple_conversation_update(conv, PURPLE_CONVERSATION_UPDATE_ACCOUNT);
}
}
PurpleAccount *
purple_conversation_get_account(PurpleConversation *conv) {
PurpleConversationPrivate *priv = NULL;
g_return_val_if_fail(PURPLE_IS_CONVERSATION(conv), NULL);
priv = purple_conversation_get_instance_private(conv);
return priv->account;
}
PurpleConnection *
purple_conversation_get_connection(PurpleConversation *conv) {
PurpleAccount *account;
g_return_val_if_fail(PURPLE_IS_CONVERSATION(conv), NULL);
account = purple_conversation_get_account(conv);
if(account == NULL) {
return NULL;
}
return purple_account_get_connection(account);
}
void
purple_conversation_set_title(PurpleConversation *conv, const gchar *title) {
PurpleConversationPrivate *priv = NULL;
g_return_if_fail(PURPLE_IS_CONVERSATION(conv));
g_return_if_fail(title != NULL);
priv = purple_conversation_get_instance_private(conv);
g_free(priv->title);
priv->title = g_strdup(title);
if(!g_object_get_data(G_OBJECT(conv), "is-finalizing")) {
g_object_notify_by_pspec(G_OBJECT(conv), properties[PROP_TITLE]);
}
purple_conversation_update(conv, PURPLE_CONVERSATION_UPDATE_TITLE);
}
const gchar *
purple_conversation_get_title(PurpleConversation *conv) {
PurpleConversationPrivate *priv = NULL;
g_return_val_if_fail(PURPLE_IS_CONVERSATION(conv), NULL);
priv = purple_conversation_get_instance_private(conv);
return priv->title;
}
void
purple_conversation_autoset_title(PurpleConversation *conv) {
PurpleAccount *account;
PurpleBuddy *b;
PurpleChat *chat;
const gchar *text = NULL, *name;
g_return_if_fail(PURPLE_IS_CONVERSATION(conv));
account = purple_conversation_get_account(conv);
name = purple_conversation_get_name(conv);
if(PURPLE_IS_IM_CONVERSATION(conv)) {
if(account && ((b = purple_blist_find_buddy(account, name)) != NULL)) {
text = purple_buddy_get_contact_alias(b);
}
} else if(PURPLE_IS_CHAT_CONVERSATION(conv)) {
if(account && ((chat = purple_blist_find_chat(account, name)) != NULL)) {
text = purple_chat_get_name(chat);
}
}
if(text == NULL) {
text = name;
}
purple_conversation_set_title(conv, text);
}
void
purple_conversation_set_name(PurpleConversation *conv, const gchar *name) {
PurpleConversationPrivate *priv = NULL;
g_return_if_fail(PURPLE_IS_CONVERSATION(conv));
priv = purple_conversation_get_instance_private(conv);
g_free(priv->name);
priv->name = g_strdup(name);
g_object_notify_by_pspec(G_OBJECT(conv), properties[PROP_NAME]);
purple_conversation_autoset_title(conv);
purple_conversation_update(conv, PURPLE_CONVERSATION_UPDATE_NAME);
}
const gchar *
purple_conversation_get_name(PurpleConversation *conv) {
PurpleConversationPrivate *priv = NULL;
g_return_val_if_fail(PURPLE_IS_CONVERSATION(conv), NULL);
priv = purple_conversation_get_instance_private(conv);
return priv->name;
}
void
_purple_conversation_write_common(PurpleConversation *conv,
PurpleMessage *pmsg)
{
PurpleProtocol *protocol = NULL;
PurpleConnection *gc = NULL;
PurpleAccount *account;
PurpleConversationUiOps *ops;
PurpleBuddy *b;
gint plugin_return;
/* int logging_font_options = 0; */
g_return_if_fail(PURPLE_IS_CONVERSATION(conv));
g_return_if_fail(pmsg != NULL);
ops = purple_conversation_get_ui_ops(conv);
account = purple_conversation_get_account(conv);
if(account != NULL) {
gc = purple_account_get_connection(account);
}
if(PURPLE_IS_CHAT_CONVERSATION(conv) && gc != NULL) {
if(!g_slist_find(purple_connection_get_active_chats(gc), conv)) {
return;
}
} else if(PURPLE_IS_IM_CONVERSATION(conv)) {
PurpleConversationManager *manager = NULL;
manager = purple_conversation_manager_get_default();
if(!purple_conversation_manager_is_registered(manager, conv)) {
return;
}
}
plugin_return = GPOINTER_TO_INT(purple_signal_emit_return_1(
purple_conversations_get_handle(),
(PURPLE_IS_IM_CONVERSATION(conv) ? "writing-im-msg" : "writing-chat-msg"),
conv, pmsg));
if(purple_message_is_empty(pmsg)) {
return;
}
if(plugin_return) {
return;
}
if(account != NULL) {
protocol = purple_account_get_protocol(account);
if(PURPLE_IS_IM_CONVERSATION(conv) ||
!(purple_protocol_get_options(protocol) & OPT_PROTO_UNIQUE_CHATNAME))
{
if(purple_message_get_flags(pmsg) & PURPLE_MESSAGE_SEND) {
PurpleContactInfo *info = PURPLE_CONTACT_INFO(account);
const gchar *alias;
alias = purple_contact_info_get_name_for_display(info);
purple_message_set_author_alias(pmsg, alias);
} else if (purple_message_get_flags(pmsg) & PURPLE_MESSAGE_RECV) {
/* TODO: PurpleDude - folks not on the buddy list */
b = purple_blist_find_buddy(account,
purple_message_get_author(pmsg));
if(b != NULL) {
purple_message_set_author_alias(pmsg,
purple_buddy_get_contact_alias(b));
}
}
}
}
if(!(purple_message_get_flags(pmsg) & PURPLE_MESSAGE_NO_LOG))
{
GError *error = NULL;
PurpleHistoryManager *manager = NULL;
manager = purple_history_manager_get_default();
/* We should probably handle this error somehow, but I don't think that
* spamming purple_debug_warning is necessarily the right call.
*/
if(!purple_history_manager_write(manager, conv, pmsg, &error)){
purple_debug_info("conversation", "history manager write returned error: %s", error->message);
g_clear_error(&error);
}
}
if(ops) {
if (PURPLE_IS_CHAT_CONVERSATION(conv) && ops->write_chat) {
ops->write_chat(PURPLE_CHAT_CONVERSATION(conv), pmsg);
} else if (PURPLE_IS_IM_CONVERSATION(conv) && ops->write_im) {
ops->write_im(PURPLE_IM_CONVERSATION(conv), pmsg);
} else if (ops->write_conv) {
ops->write_conv(conv, pmsg);
}
}
purple_signal_emit(purple_conversations_get_handle(),
(PURPLE_IS_IM_CONVERSATION(conv) ? "wrote-im-msg" : "wrote-chat-msg"),
conv, pmsg);
}
void
purple_conversation_write_message(PurpleConversation *conv,
PurpleMessage *msg)
{
PurpleConversationClass *klass = NULL;
g_return_if_fail(PURPLE_IS_CONVERSATION(conv));
klass = PURPLE_CONVERSATION_GET_CLASS(conv);
if(klass && klass->write_message) {
klass->write_message(conv, msg);
}
}
void
purple_conversation_write_system_message(PurpleConversation *conv,
const gchar *message,
PurpleMessageFlags flags)
{
PurpleConversationPrivate *priv = NULL;
g_return_if_fail(PURPLE_IS_CONVERSATION(conv));
priv = purple_conversation_get_instance_private(conv);
_purple_conversation_write_common(conv,
purple_message_new_system(priv->account,
message,
flags));
}
void
purple_conversation_send(PurpleConversation *conv, const gchar *message) {
purple_conversation_send_with_flags(conv, message, 0);
}
void
purple_conversation_send_with_flags(PurpleConversation *conv,
const gchar *message,
PurpleMessageFlags flags)
{
g_return_if_fail(PURPLE_IS_CONVERSATION(conv));
g_return_if_fail(message != NULL);
common_send(conv, message, flags);
}
gboolean
purple_conversation_has_focus(PurpleConversation *conv) {
gboolean ret = FALSE;
PurpleConversationUiOps *ops;
g_return_val_if_fail(PURPLE_IS_CONVERSATION(conv), FALSE);
ops = purple_conversation_get_ui_ops(conv);
if(ops != NULL && ops->has_focus != NULL) {
ret = ops->has_focus(conv);
}
return ret;
}
/*
* TODO: Need to make sure calls to this function happen in the core
* instead of the UI. That way UIs have less work to do, and the
* core/UI split is cleaner. Also need to make sure this is called
* when chats are added/removed from the blist.
*/
void
purple_conversation_update(PurpleConversation *conv,
PurpleConversationUpdateType type)
{
g_return_if_fail(PURPLE_IS_CONVERSATION(conv));
purple_signal_emit(purple_conversations_get_handle(),
"conversation-updated", conv, type);
}
gboolean
purple_conversation_present_error(const gchar *who, PurpleAccount *account,
const gchar *what)
{
PurpleConversation *conv;
PurpleConversationManager *manager;
g_return_val_if_fail(who != NULL, FALSE);
g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), FALSE);
g_return_val_if_fail(what != NULL, FALSE);
manager = purple_conversation_manager_get_default();
conv = purple_conversation_manager_find(manager, account, who);
if(PURPLE_IS_CONVERSATION(conv)) {
purple_conversation_write_system_message(conv, what,
PURPLE_MESSAGE_ERROR);
return TRUE;
}
return FALSE;
}
void
purple_conversation_send_confirm(PurpleConversation *conv,
const gchar *message)
{
PurpleConversationPrivate *priv = NULL;
gchar *text;
gpointer *data;
g_return_if_fail(PURPLE_IS_CONVERSATION(conv));
g_return_if_fail(message != NULL);
priv = purple_conversation_get_instance_private(conv);
if(priv->ui_ops != NULL && priv->ui_ops->send_confirm != NULL) {
priv->ui_ops->send_confirm(conv, message);
return;
}
text = g_strdup_printf("You are about to send the following message:\n%s",
message);
data = g_new0(gpointer, 2);
data[0] = conv;
data[1] = (gpointer)message;
purple_request_action(conv, NULL, _("Send Message"), text, 0,
purple_request_cpar_from_account(
purple_conversation_get_account(conv)),
data, 2, _("_Send Message"),
G_CALLBACK(purple_conversation_send_confirm_cb), _("Cancel"), NULL);
}
GList *
purple_conversation_get_extended_menu(PurpleConversation *conv) {
GList *menu = NULL;
g_return_val_if_fail(PURPLE_IS_CONVERSATION(conv), NULL);
purple_signal_emit(purple_conversations_get_handle(),
"conversation-extended-menu", conv, &menu);
return menu;
}
GListModel *
purple_conversation_get_members(PurpleConversation *conversation) {
PurpleConversationPrivate *priv = NULL;
g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), NULL);
priv = purple_conversation_get_instance_private(conversation);
return G_LIST_MODEL(priv->members);
}
gboolean
purple_conversation_has_member(PurpleConversation *conversation,
PurpleContactInfo *info, guint *position)
{
PurpleConversationPrivate *priv = NULL;
PurpleConversationMember *needle = NULL;
gboolean found = FALSE;
g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), FALSE);
g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), FALSE);
priv = purple_conversation_get_instance_private(conversation);
needle = purple_conversation_member_new(info);
found = g_list_store_find_with_equal_func(priv->members, needle,
purple_conversation_check_member_equal,
position);
g_clear_object(&needle);
return found;
}
PurpleConversationMember *
purple_conversation_find_member(PurpleConversation *conversation,
PurpleContactInfo *info)
{
PurpleConversationMember *member = NULL;
guint position = 0;
g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), NULL);
g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), NULL);
if(purple_conversation_has_member(conversation, info, &position)) {
PurpleConversationPrivate *priv = NULL;
priv = purple_conversation_get_instance_private(conversation);
member = g_list_model_get_item(G_LIST_MODEL(priv->members), position);
/* We don't return a reference, but get_item does, so we need to get
* rid of that.
*/
g_object_unref(member);
}
return member;
}
PurpleConversationMember *
purple_conversation_add_member(PurpleConversation *conversation,
PurpleContactInfo *info, gboolean announce,
const char *message)
{
PurpleConversationMember *member = NULL;
PurpleConversationPrivate *priv = NULL;
g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), NULL);
g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), NULL);
priv = purple_conversation_get_instance_private(conversation);
member = purple_conversation_find_member(conversation, info);
if(PURPLE_IS_CONVERSATION_MEMBER(member)) {
return member;
}
member = purple_conversation_member_new(info);
g_list_store_append(priv->members, member);
g_signal_emit(conversation, signals[SIG_MEMBER_ADDED], 0, member, announce,
message);
g_object_unref(member);
return member;
}
gboolean
purple_conversation_remove_member(PurpleConversation *conversation,
PurpleConversationMember *member,
gboolean announce, const char *message)
{
PurpleConversationPrivate *priv = NULL;
guint position = 0;
g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), FALSE);
g_return_val_if_fail(PURPLE_IS_CONVERSATION_MEMBER(member), FALSE);
priv = purple_conversation_get_instance_private(conversation);
if(!g_list_store_find(priv->members, member, &position)) {
return FALSE;
}
/* We need to ref member to make sure it stays around long enough for us
* to emit the signal.
*/
g_object_ref(member);
g_list_store_remove(priv->members, position);
g_signal_emit(conversation, signals[SIG_MEMBER_REMOVED], 0, member,
announce, message);
g_object_unref(member);
return TRUE;
}