Migrate certificates, icons, logs to XDG dirs
[pidgin-git.git] / libpurple / buddy.c
blob95153eba023e3d5df5ab3914066b410b5394591b
1 /*
2 * purple
4 * Purple is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
6 * source distribution.
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
23 #include "internal.h"
24 #include "glibcompat.h"
25 #include "dbus-maybe.h"
26 #include "util.h"
28 #define PURPLE_BUDDY_GET_PRIVATE(obj) \
29 (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_BUDDY, PurpleBuddyPrivate))
31 typedef struct _PurpleBuddyPrivate PurpleBuddyPrivate;
33 struct _PurpleBuddyPrivate {
34 char *name; /* The name of the buddy. */
35 char *local_alias; /* The user-set alias of the buddy */
36 char *server_alias; /* The server-specified alias of the buddy.
37 (i.e. MSN "Friendly Names") */
38 void *proto_data; /* This allows the protocol to associate
39 whatever data it wants with a buddy. */
40 PurpleBuddyIcon *icon; /* The buddy icon. */
41 PurpleAccount *account; /* the account this buddy belongs to */
42 PurplePresence *presence; /* Presense information of the buddy */
43 PurpleMediaCaps media_caps; /* The media capabilities of the buddy. */
45 gboolean is_constructed; /* Indicates if the buddy has finished
46 being constructed. */
49 enum {
50 PROP_0,
51 PROP_NAME,
52 PROP_LOCAL_ALIAS,
53 PROP_SERVER_ALIAS,
54 PROP_ICON,
55 PROP_ACCOUNT,
56 PROP_PRESENCE,
57 PROP_MEDIA_CAPS,
58 PROP_LAST
61 /******************************************************************************
62 * Globals
63 *****************************************************************************/
64 static PurpleBlistNode *parent_class;
65 static GParamSpec *properties[PROP_LAST];
67 /******************************************************************************
68 * API
69 *****************************************************************************/
70 void
71 purple_buddy_set_icon(PurpleBuddy *buddy, PurpleBuddyIcon *icon)
73 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
74 PurpleBuddyPrivate *priv = PURPLE_BUDDY_GET_PRIVATE(buddy);
76 g_return_if_fail(priv != NULL);
78 if (priv->icon != icon)
80 purple_buddy_icon_unref(priv->icon);
81 priv->icon = (icon != NULL ? purple_buddy_icon_ref(icon) : NULL);
83 g_object_notify_by_pspec(G_OBJECT(buddy),
84 properties[PROP_ICON]);
87 purple_signal_emit(purple_blist_get_handle(), "buddy-icon-changed", buddy);
89 if (ops && ops->update)
90 ops->update(purple_blist_get_buddy_list(), PURPLE_BLIST_NODE(buddy));
93 PurpleBuddyIcon *
94 purple_buddy_get_icon(const PurpleBuddy *buddy)
96 PurpleBuddyPrivate *priv = PURPLE_BUDDY_GET_PRIVATE(buddy);
98 g_return_val_if_fail(priv != NULL, NULL);
100 return priv->icon;
103 PurpleAccount *
104 purple_buddy_get_account(const PurpleBuddy *buddy)
106 PurpleBuddyPrivate *priv = PURPLE_BUDDY_GET_PRIVATE(buddy);
108 g_return_val_if_fail(priv != NULL, NULL);
110 return priv->account;
113 void
114 purple_buddy_set_name(PurpleBuddy *buddy, const char *name)
116 PurpleBuddyPrivate *priv = PURPLE_BUDDY_GET_PRIVATE(buddy);
117 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
119 g_return_if_fail(priv != NULL);
121 purple_blist_update_buddies_cache(buddy, name);
123 g_free(priv->name);
124 priv->name = purple_utf8_strip_unprintables(name);
126 g_object_notify_by_pspec(G_OBJECT(buddy), properties[PROP_NAME]);
128 if (ops) {
129 if (ops->save_node)
130 ops->save_node(PURPLE_BLIST_NODE(buddy));
131 if (ops->update)
132 ops->update(purple_blist_get_buddy_list(), PURPLE_BLIST_NODE(buddy));
136 const char *
137 purple_buddy_get_name(const PurpleBuddy *buddy)
139 PurpleBuddyPrivate *priv = PURPLE_BUDDY_GET_PRIVATE(buddy);
141 g_return_val_if_fail(priv != NULL, NULL);
143 return priv->name;
146 gpointer
147 purple_buddy_get_protocol_data(const PurpleBuddy *buddy)
149 PurpleBuddyPrivate *priv = PURPLE_BUDDY_GET_PRIVATE(buddy);
151 g_return_val_if_fail(priv != NULL, NULL);
153 return priv->proto_data;
156 void
157 purple_buddy_set_protocol_data(PurpleBuddy *buddy, gpointer data)
159 PurpleBuddyPrivate *priv = PURPLE_BUDDY_GET_PRIVATE(buddy);
161 g_return_if_fail(priv != NULL);
163 priv->proto_data = data;
166 const char *purple_buddy_get_alias_only(PurpleBuddy *buddy)
168 PurpleBuddyPrivate *priv = PURPLE_BUDDY_GET_PRIVATE(buddy);
170 g_return_val_if_fail(priv != NULL, NULL);
172 if ((priv->local_alias != NULL) && (*priv->local_alias != '\0')) {
173 return priv->local_alias;
174 } else if ((priv->server_alias != NULL) &&
175 (*priv->server_alias != '\0')) {
177 return priv->server_alias;
180 return NULL;
183 const char *purple_buddy_get_contact_alias(PurpleBuddy *buddy)
185 PurpleContact *c;
186 PurpleBuddyPrivate *priv = PURPLE_BUDDY_GET_PRIVATE(buddy);
188 g_return_val_if_fail(priv != NULL, NULL);
190 /* Search for an alias for the buddy. In order of precedence: */
191 /* The local buddy alias */
192 if (priv->local_alias != NULL)
193 return priv->local_alias;
195 /* The contact alias */
196 c = purple_buddy_get_contact(buddy);
197 if ((c != NULL) && (purple_contact_get_alias(c) != NULL))
198 return purple_contact_get_alias(c);
200 /* The server alias */
201 if ((priv->server_alias) && (*priv->server_alias))
202 return priv->server_alias;
204 /* The buddy's user name (i.e. no alias) */
205 return priv->name;
208 const char *purple_buddy_get_alias(PurpleBuddy *buddy)
210 PurpleBuddyPrivate *priv = PURPLE_BUDDY_GET_PRIVATE(buddy);
212 g_return_val_if_fail(priv != NULL, NULL);
214 /* Search for an alias for the buddy. In order of precedence: */
215 /* The buddy alias */
216 if (priv->local_alias != NULL)
217 return priv->local_alias;
219 /* The server alias */
220 if ((priv->server_alias) && (*priv->server_alias))
221 return priv->server_alias;
223 /* The buddy's user name (i.e. no alias) */
224 return priv->name;
227 void
228 purple_buddy_set_local_alias(PurpleBuddy *buddy, const char *alias)
230 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
231 PurpleIMConversation *im;
232 char *old_alias;
233 char *new_alias = NULL;
234 PurpleBuddyPrivate *priv = PURPLE_BUDDY_GET_PRIVATE(buddy);
236 g_return_if_fail(priv != NULL);
238 if ((alias != NULL) && (*alias != '\0'))
239 new_alias = purple_utf8_strip_unprintables(alias);
241 if (purple_strequal(priv->local_alias, new_alias)) {
242 g_free(new_alias);
243 return;
246 old_alias = priv->local_alias;
248 if ((new_alias != NULL) && (*new_alias != '\0'))
249 priv->local_alias = new_alias;
250 else {
251 priv->local_alias = NULL;
252 g_free(new_alias); /* could be "\0" */
255 g_object_notify_by_pspec(G_OBJECT(buddy),
256 properties[PROP_LOCAL_ALIAS]);
258 if (ops && ops->save_node)
259 ops->save_node(PURPLE_BLIST_NODE(buddy));
261 if (ops && ops->update)
262 ops->update(purple_blist_get_buddy_list(), PURPLE_BLIST_NODE(buddy));
264 im = purple_conversations_find_im_with_account(priv->name,
265 priv->account);
266 if (im)
267 purple_conversation_autoset_title(PURPLE_CONVERSATION(im));
269 purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased",
270 buddy, old_alias);
271 g_free(old_alias);
274 const char *purple_buddy_get_local_alias(PurpleBuddy *buddy)
276 PurpleBuddyPrivate *priv = PURPLE_BUDDY_GET_PRIVATE(buddy);
278 g_return_val_if_fail(priv != NULL, NULL);
280 return priv->local_alias;
283 void
284 purple_buddy_set_server_alias(PurpleBuddy *buddy, const char *alias)
286 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
287 PurpleIMConversation *im;
288 char *old_alias;
289 char *new_alias = NULL;
290 PurpleBuddyPrivate *priv = PURPLE_BUDDY_GET_PRIVATE(buddy);
292 g_return_if_fail(priv != NULL);
294 if ((alias != NULL) && (*alias != '\0') && g_utf8_validate(alias, -1, NULL))
295 new_alias = purple_utf8_strip_unprintables(alias);
297 if (purple_strequal(priv->server_alias, new_alias)) {
298 g_free(new_alias);
299 return;
302 old_alias = priv->server_alias;
304 if ((new_alias != NULL) && (*new_alias != '\0'))
305 priv->server_alias = new_alias;
306 else {
307 priv->server_alias = NULL;
308 g_free(new_alias); /* could be "\0"; */
311 g_object_notify_by_pspec(G_OBJECT(buddy),
312 properties[PROP_SERVER_ALIAS]);
314 if (ops) {
315 if (ops->save_node)
316 ops->save_node(PURPLE_BLIST_NODE(buddy));
317 if (ops->update)
318 ops->update(purple_blist_get_buddy_list(), PURPLE_BLIST_NODE(buddy));
321 im = purple_conversations_find_im_with_account(priv->name,
322 priv->account);
323 if (im)
324 purple_conversation_autoset_title(PURPLE_CONVERSATION(im));
326 purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased",
327 buddy, old_alias);
328 g_free(old_alias);
331 const char *purple_buddy_get_server_alias(PurpleBuddy *buddy)
333 PurpleBuddyPrivate *priv = PURPLE_BUDDY_GET_PRIVATE(buddy);
335 g_return_val_if_fail(priv != NULL, NULL);
337 if ((priv->server_alias) && (*priv->server_alias))
338 return priv->server_alias;
340 return NULL;
343 PurpleContact *purple_buddy_get_contact(PurpleBuddy *buddy)
345 g_return_val_if_fail(PURPLE_IS_BUDDY(buddy), NULL);
347 return PURPLE_CONTACT(PURPLE_BLIST_NODE(buddy)->parent);
350 PurplePresence *purple_buddy_get_presence(const PurpleBuddy *buddy)
352 PurpleBuddyPrivate *priv = PURPLE_BUDDY_GET_PRIVATE(buddy);
354 g_return_val_if_fail(priv != NULL, NULL);
356 return priv->presence;
359 void
360 purple_buddy_update_status(PurpleBuddy *buddy, PurpleStatus *old_status)
362 PurpleStatus *status;
363 PurpleBlistNode *cnode;
364 PurpleContact *contact;
365 PurpleCountingNode *contact_counter, *group_counter;
366 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
367 PurpleBuddyPrivate *priv = PURPLE_BUDDY_GET_PRIVATE(buddy);
369 g_return_if_fail(priv != NULL);
371 status = purple_presence_get_active_status(priv->presence);
373 purple_debug_info("blistnodetypes", "Updating buddy status for %s (%s)\n",
374 priv->name, purple_account_get_protocol_name(priv->account));
376 if (purple_status_is_online(status) &&
377 !purple_status_is_online(old_status)) {
379 purple_signal_emit(purple_blist_get_handle(), "buddy-signed-on", buddy);
381 cnode = PURPLE_BLIST_NODE(buddy)->parent;
382 contact = PURPLE_CONTACT(cnode);
383 contact_counter = PURPLE_COUNTING_NODE(contact);
384 group_counter = PURPLE_COUNTING_NODE(cnode->parent);
386 purple_counting_node_change_online_count(contact_counter, +1);
387 if (purple_counting_node_get_online_count(contact_counter) == 1)
388 purple_counting_node_change_online_count(group_counter, +1);
389 } else if (!purple_status_is_online(status) &&
390 purple_status_is_online(old_status)) {
392 purple_blist_node_set_int(PURPLE_BLIST_NODE(buddy), "last_seen", time(NULL));
393 purple_signal_emit(purple_blist_get_handle(), "buddy-signed-off", buddy);
395 cnode = PURPLE_BLIST_NODE(buddy)->parent;
396 contact = PURPLE_CONTACT(cnode);
397 contact_counter = PURPLE_COUNTING_NODE(contact);
398 group_counter = PURPLE_COUNTING_NODE(cnode->parent);
400 purple_counting_node_change_online_count(contact_counter, -1);
401 if (purple_counting_node_get_online_count(contact_counter) == 0)
402 purple_counting_node_change_online_count(group_counter, -1);
403 } else {
404 purple_signal_emit(purple_blist_get_handle(),
405 "buddy-status-changed", buddy, old_status,
406 status);
410 * This function used to only call the following two functions if one of
411 * the above signals had been triggered, but that's not good, because
412 * if someone's away message changes and they don't go from away to back
413 * to away then no signal is triggered.
415 * It's a safe assumption that SOMETHING called this function. PROBABLY
416 * because something, somewhere changed. Calling the stuff below
417 * certainly won't hurt anything. Unless you're on a K6-2 300.
419 purple_contact_invalidate_priority_buddy(purple_buddy_get_contact(buddy));
421 if (ops && ops->update)
422 ops->update(purple_blist_get_buddy_list(), PURPLE_BLIST_NODE(buddy));
425 PurpleMediaCaps purple_buddy_get_media_caps(const PurpleBuddy *buddy)
427 PurpleBuddyPrivate *priv = PURPLE_BUDDY_GET_PRIVATE(buddy);
429 g_return_val_if_fail(priv != NULL, 0);
431 return priv->media_caps;
434 void purple_buddy_set_media_caps(PurpleBuddy *buddy, PurpleMediaCaps media_caps)
436 PurpleBuddyPrivate *priv = PURPLE_BUDDY_GET_PRIVATE(buddy);
438 g_return_if_fail(priv != NULL);
440 priv->media_caps = media_caps;
442 g_object_notify_by_pspec(G_OBJECT(buddy),
443 properties[PROP_MEDIA_CAPS]);
446 PurpleGroup *purple_buddy_get_group(PurpleBuddy *buddy)
448 g_return_val_if_fail(PURPLE_IS_BUDDY(buddy), NULL);
450 if (PURPLE_BLIST_NODE(buddy)->parent == NULL)
451 return purple_blist_get_default_group();
453 return PURPLE_GROUP(PURPLE_BLIST_NODE(buddy)->parent->parent);
456 /******************************************************************************
457 * GObject Stuff
458 *****************************************************************************/
459 static void
460 purple_buddy_set_property(GObject *obj, guint param_id, const GValue *value,
461 GParamSpec *pspec)
463 PurpleBuddy *buddy = PURPLE_BUDDY(obj);
464 PurpleBuddyPrivate *priv = PURPLE_BUDDY_GET_PRIVATE(buddy);
466 switch (param_id) {
467 case PROP_NAME:
468 if (priv->is_constructed)
469 purple_buddy_set_name(buddy, g_value_get_string(value));
470 else
471 priv->name =
472 purple_utf8_strip_unprintables(g_value_get_string(value));
473 break;
474 case PROP_LOCAL_ALIAS:
475 if (priv->is_constructed)
476 purple_buddy_set_local_alias(buddy, g_value_get_string(value));
477 else
478 priv->local_alias =
479 purple_utf8_strip_unprintables(g_value_get_string(value));
480 break;
481 case PROP_SERVER_ALIAS:
482 purple_buddy_set_server_alias(buddy, g_value_get_string(value));
483 break;
484 case PROP_ICON:
485 purple_buddy_set_icon(buddy, g_value_get_pointer(value));
486 break;
487 case PROP_ACCOUNT:
488 priv->account = g_value_get_object(value);
489 break;
490 case PROP_MEDIA_CAPS:
491 purple_buddy_set_media_caps(buddy, g_value_get_enum(value));
492 break;
493 default:
494 G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
495 break;
499 static void
500 purple_buddy_get_property(GObject *obj, guint param_id, GValue *value,
501 GParamSpec *pspec)
503 PurpleBuddy *buddy = PURPLE_BUDDY(obj);
505 switch (param_id) {
506 case PROP_NAME:
507 g_value_set_string(value, purple_buddy_get_name(buddy));
508 break;
509 case PROP_LOCAL_ALIAS:
510 g_value_set_string(value, purple_buddy_get_local_alias(buddy));
511 break;
512 case PROP_SERVER_ALIAS:
513 g_value_set_string(value, purple_buddy_get_server_alias(buddy));
514 break;
515 case PROP_ICON:
516 g_value_set_pointer(value, purple_buddy_get_icon(buddy));
517 break;
518 case PROP_ACCOUNT:
519 g_value_set_object(value, purple_buddy_get_account(buddy));
520 break;
521 case PROP_PRESENCE:
522 g_value_set_object(value, purple_buddy_get_presence(buddy));
523 break;
524 case PROP_MEDIA_CAPS:
525 g_value_set_enum(value, purple_buddy_get_media_caps(buddy));
526 break;
527 default:
528 G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
529 break;
533 static void
534 purple_buddy_init(GTypeInstance *instance, gpointer klass) {
535 PURPLE_DBUS_REGISTER_POINTER(PURPLE_BUDDY(instance), PurpleBuddy);
538 static void
539 purple_buddy_constructed(GObject *object) {
540 PurpleBuddy *buddy = PURPLE_BUDDY(object);
541 PurpleBuddyPrivate *priv = PURPLE_BUDDY_GET_PRIVATE(buddy);
542 PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
544 G_OBJECT_CLASS(parent_class)->constructed(object);
546 priv->presence = PURPLE_PRESENCE(purple_buddy_presence_new(buddy));
547 purple_presence_set_status_active(priv->presence, "offline", TRUE);
549 if (ops && ops->new_node)
550 ops->new_node((PurpleBlistNode *)buddy);
552 priv->is_constructed = TRUE;
555 static void
556 purple_buddy_dispose(GObject *object) {
557 PurpleBuddyPrivate *priv = PURPLE_BUDDY_GET_PRIVATE(object);
559 if (priv->icon) {
560 purple_buddy_icon_unref(priv->icon);
561 priv->icon = NULL;
564 if (priv->presence) {
565 g_object_unref(priv->presence);
566 priv->presence = NULL;
569 G_OBJECT_CLASS(parent_class)->dispose(object);
572 static void
573 purple_buddy_finalize(GObject *object) {
574 PurpleBuddy *buddy = PURPLE_BUDDY(object);
575 PurpleBuddyPrivate *priv = PURPLE_BUDDY_GET_PRIVATE(buddy);
576 PurpleProtocol *protocol;
579 * Tell the owner protocol that we're about to free the buddy so it
580 * can free proto_data
582 protocol = purple_protocols_find(purple_account_get_protocol_id(priv->account));
583 if (protocol)
584 purple_protocol_client_iface_buddy_free(protocol, buddy);
586 g_free(priv->name);
587 g_free(priv->local_alias);
588 g_free(priv->server_alias);
590 PURPLE_DBUS_UNREGISTER_POINTER(buddy);
592 G_OBJECT_CLASS(parent_class)->finalize(object);
595 static void purple_buddy_class_init(PurpleBuddyClass *klass) {
596 GObjectClass *obj_class = G_OBJECT_CLASS(klass);
598 parent_class = g_type_class_peek_parent(klass);
600 obj_class->dispose = purple_buddy_dispose;
601 obj_class->finalize = purple_buddy_finalize;
603 /* Setup properties */
604 obj_class->get_property = purple_buddy_get_property;
605 obj_class->set_property = purple_buddy_set_property;
606 obj_class->constructed = purple_buddy_constructed;
608 g_type_class_add_private(klass, sizeof(PurpleBuddyPrivate));
610 properties[PROP_NAME] = g_param_spec_string(
611 "name",
612 "Name",
613 "The name of the buddy.",
614 NULL,
615 G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS
618 properties[PROP_LOCAL_ALIAS] = g_param_spec_string(
619 "local-alias",
620 "Local alias",
621 "Local alias of thee buddy.",
622 NULL,
623 G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS
626 properties[PROP_SERVER_ALIAS] = g_param_spec_string(
627 "server-alias",
628 "Server alias",
629 "Server-side alias of the buddy.",
630 NULL,
631 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
634 properties[PROP_ICON] = g_param_spec_pointer(
635 "icon",
636 "Buddy icon",
637 "The icon for the buddy.",
638 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
641 properties[PROP_ACCOUNT] = g_param_spec_object(
642 "account",
643 "Account",
644 "The account for the buddy.",
645 PURPLE_TYPE_ACCOUNT,
646 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS
649 properties[PROP_PRESENCE] = g_param_spec_object(
650 "presence",
651 "Presence",
652 "The status information for the buddy.",
653 PURPLE_TYPE_PRESENCE,
654 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS
657 properties[PROP_MEDIA_CAPS] = g_param_spec_enum(
658 "media-caps",
659 "Media capabilities",
660 "The media capabilities of the buddy.",
661 PURPLE_MEDIA_TYPE_CAPS, PURPLE_MEDIA_CAPS_NONE,
662 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
665 g_object_class_install_properties(obj_class, PROP_LAST, properties);
668 GType
669 purple_buddy_get_type(void) {
670 static GType type = 0;
672 if(type == 0) {
673 static const GTypeInfo info = {
674 sizeof(PurpleBuddyClass),
675 NULL,
676 NULL,
677 (GClassInitFunc)purple_buddy_class_init,
678 NULL,
679 NULL,
680 sizeof(PurpleBuddy),
682 (GInstanceInitFunc)purple_buddy_init,
683 NULL,
686 type = g_type_register_static(PURPLE_TYPE_BLIST_NODE,
687 "PurpleBuddy",
688 &info, 0);
691 return type;
694 PurpleBuddy *
695 purple_buddy_new(PurpleAccount *account, const char *name, const char *alias)
697 g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL);
698 g_return_val_if_fail(name != NULL, NULL);
700 return g_object_new(PURPLE_TYPE_BUDDY,
701 "account", account,
702 "name", name,
703 "local-alias", alias,
704 NULL);