changeset 18854:2516cf81b763

merge of '1aba81cae8b963708281123ed6c438ba5861113d' and 'a44fe9cfe3fd6d287a438835c65977f4570da41e'
author Daniel Atallah <daniel.atallah@gmail.com>
date Thu, 09 Aug 2007 18:34:01 +0000
parents fab096e7b804 (current diff) 2283d5bfc24b (diff)
children 28a7c9dbbc2b
files
diffstat 10 files changed, 411 insertions(+), 98 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/protocols/bonjour/bonjour.c	Thu Aug 09 02:42:49 2007 +0000
+++ b/libpurple/protocols/bonjour/bonjour.c	Thu Aug 09 18:34:01 2007 +0000
@@ -138,6 +138,8 @@
 		return;
 	}
 
+	bonjour_dns_sd_update_buddy_icon(bd->dns_sd_data);
+
 	/* Create a group for bonjour buddies */
 	bonjour_group = purple_group_new(BONJOUR_GROUP_NAME);
 	purple_blist_add_group(bonjour_group, NULL);
@@ -283,6 +285,14 @@
 	bb->conversation = NULL;
 }
 
+static
+void bonjour_set_buddy_icon(PurpleConnection *conn, PurpleStoredImage *img)
+{
+	BonjourData *bd = conn->proto_data;
+	bonjour_dns_sd_update_buddy_icon(bd->dns_sd_data);
+}
+
+
 static char *
 bonjour_status_text(PurpleBuddy *buddy)
 {
@@ -339,8 +349,7 @@
 	OPT_PROTO_NO_PASSWORD,
 	NULL,                                                    /* user_splits */
 	NULL,                                                    /* protocol_options */
-	/* {"png", 0, 0, 96, 96, 0, PURPLE_ICON_SCALE_DISPLAY}, */ /* icon_spec */
-	NO_BUDDY_ICONS, /* not yet */                            /* icon_spec */
+	{"png,gif,jpeg", 0, 0, 96, 96, 65535, PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */
 	bonjour_list_icon,                                       /* list_icon */
 	NULL,													 /* list_emblem */
 	bonjour_status_text,                                     /* status_text */
@@ -384,7 +393,7 @@
 	NULL,                                                    /* buddy_free */
 	bonjour_convo_closed,                                    /* convo_closed */
 	NULL,                                                    /* normalize */
-	NULL,                                                    /* set_buddy_icon */
+	bonjour_set_buddy_icon,                                  /* set_buddy_icon */
 	NULL,                                                    /* remove_group */
 	NULL,                                                    /* get_cb_real_name */
 	NULL,                                                    /* set_chat_topic */
@@ -533,7 +542,7 @@
 	{
 		default_firstname = g_strndup(fullname, splitpoint - fullname);
 		tmp = &splitpoint[1];
-		
+
 		/* The last name may be followed by a comma and additional data.
 		 * Only use the last name itself.
 		 */
--- a/libpurple/protocols/bonjour/buddy.c	Thu Aug 09 02:42:49 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.c	Thu Aug 09 18:34:01 2007 +0000
@@ -18,6 +18,7 @@
 #include <stdlib.h>
 
 #include "internal.h"
+#include "cipher.h"
 #include "buddy.h"
 #include "account.h"
 #include "blist.h"
@@ -106,11 +107,10 @@
 	PurpleBuddy *buddy;
 	PurpleGroup *group;
 	PurpleAccount *account = bonjour_buddy->account;
-	const char *status_id, *first, *last, *old_hash, *new_hash;
-	gchar *alias = NULL;
+	const char *status_id, *old_hash, *new_hash;
 
 	/* Translate between the Bonjour status and the Purple status */
-	if (g_ascii_strcasecmp("dnd", bonjour_buddy->status) == 0)
+	if (bonjour_buddy->status != NULL && g_ascii_strcasecmp("dnd", bonjour_buddy->status) == 0)
 		status_id = BONJOUR_STATUS_ID_AWAY;
 	else
 		status_id = BONJOUR_STATUS_ID_AVAILABLE;
@@ -138,15 +138,21 @@
 	}
 
 	/* Create the alias for the buddy using the first and the last name */
-	first = bonjour_buddy->first;
-	last = bonjour_buddy->last;
-	if ((first && *first) || (last && *last))
-		alias = g_strdup_printf("%s%s%s",
-					(first && *first ? first : ""),
-					(first && *first && last && *last ? " " : ""),
-					(last && *last ? last : ""));
-	serv_got_alias(purple_account_get_connection(account), buddy->name, alias);
-	g_free(alias);
+	if (bonjour_buddy->nick)
+		serv_got_alias(purple_account_get_connection(account), buddy->name, bonjour_buddy->nick);
+	else {
+		gchar *alias = NULL;
+		const char *first, *last;
+		first = bonjour_buddy->first;
+		last = bonjour_buddy->last;
+		if ((first && *first) || (last && *last))
+			alias = g_strdup_printf("%s%s%s",
+						(first && *first ? first : ""),
+						(first && *first && last && *last ? " " : ""),
+						(last && *last ? last : ""));
+		serv_got_alias(purple_account_get_connection(account), buddy->name, alias);
+		g_free(alias);
+	}
 
 	/* Set the user's status */
 	if (bonjour_buddy->msg != NULL)
@@ -166,12 +172,46 @@
 	new_hash = (bonjour_buddy->phsh && *(bonjour_buddy->phsh)) ? bonjour_buddy->phsh : NULL;
 	if (new_hash && (!old_hash || strcmp(old_hash, new_hash) != 0)) {
 		/* Look up the new icon data */
+		/* TODO: Make sure the hash assigned to the retrieved buddy icon is the same
+		 * as what we looked up. */
 		bonjour_dns_sd_retrieve_buddy_icon(bonjour_buddy);
-	} else
+	} else if (!new_hash)
 		purple_buddy_icons_set_for_user(account, buddy->name, NULL, 0, NULL);
 }
 
 /**
+ * We got the buddy icon data; deal with it
+ */
+void bonjour_buddy_got_buddy_icon(BonjourBuddy *buddy, gconstpointer data, gsize len) {
+	/* Recalculate the hash instead of using the current phsh to make sure it is accurate for the icon. */
+	int i;
+	gchar *enc;
+	char *p, hash[41];
+	unsigned char hashval[20];
+
+	if (data == NULL || len == 0)
+		return;
+
+	enc = purple_base64_encode(data, len);
+
+	purple_cipher_digest_region("sha1", data,
+				    len, sizeof(hashval),
+				    hashval, NULL);
+
+	p = hash;
+	for(i=0; i<20; i++, p+=2)
+		snprintf(p, 3, "%02x", hashval[i]);
+
+	purple_debug_info("bonjour", "Got buddy icon for %s icon hash='%s' phsh='%s'.\n", buddy->name,
+			  hash, buddy->phsh ? buddy->phsh : "(null)");
+
+	purple_buddy_icons_set_for_user(buddy->account, buddy->name,
+		g_memdup(data, len), len, hash);
+
+	g_free(enc);
+}
+
+/**
  * Deletes a buddy from memory.
  */
 void
@@ -179,8 +219,6 @@
 {
 	g_free(buddy->name);
 	g_free(buddy->ip);
-	g_free(buddy->full_service_name);
-
 	g_free(buddy->first);
 	g_free(buddy->phsh);
 	g_free(buddy->status);
--- a/libpurple/protocols/bonjour/buddy.h	Thu Aug 09 02:42:49 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.h	Thu Aug 09 18:34:01 2007 +0000
@@ -29,7 +29,6 @@
 	gchar *name;
 	/* TODO: Remove and just use the hostname */
 	gchar *ip;
-	gchar *full_service_name;
 	gint port_p2pj;
 
 	gchar *first;
@@ -91,6 +90,11 @@
 void bonjour_buddy_add_to_purple(BonjourBuddy *buddy);
 
 /**
+ * We got the buddy icon data; deal with it
+ */
+void bonjour_buddy_got_buddy_icon(BonjourBuddy *buddy, gconstpointer data, gsize len);
+
+/**
  * Deletes a buddy from memory.
  */
 void bonjour_buddy_delete(BonjourBuddy *buddy);
--- a/libpurple/protocols/bonjour/issues.txt	Thu Aug 09 02:42:49 2007 +0000
+++ b/libpurple/protocols/bonjour/issues.txt	Thu Aug 09 18:34:01 2007 +0000
@@ -3,6 +3,5 @@
 ==========================================
 
 * Status changes don't work
-* Avatars
 * File transfers
 * Typing notifications
--- a/libpurple/protocols/bonjour/mdns_avahi.c	Thu Aug 09 02:42:49 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_avahi.c	Thu Aug 09 18:34:01 2007 +0000
@@ -19,6 +19,7 @@
 #include "mdns_interface.h"
 #include "debug.h"
 #include "buddy.h"
+#include "bonjour.h"
 
 #include <avahi-client/client.h>
 #include <avahi-client/lookup.h>
@@ -32,14 +33,25 @@
 #include <avahi-glib/glib-malloc.h>
 #include <avahi-glib/glib-watch.h>
 
+/* For some reason, this is missing from the Avahi type defines */
+#ifndef AVAHI_DNS_TYPE_NULL
+#define AVAHI_DNS_TYPE_NULL 0x0A
+#endif
+
 /* data used by avahi bonjour implementation */
 typedef struct _avahi_session_impl_data {
 	AvahiClient *client;
 	AvahiGLibPoll *glib_poll;
 	AvahiServiceBrowser *sb;
 	AvahiEntryGroup *group;
+	AvahiEntryGroup *buddy_icon_group;
 } AvahiSessionImplData;
 
+typedef struct _avahi_buddy_impl_data {
+	AvahiServiceResolver *resolver;
+	AvahiRecordBrowser *buddy_icon_rec_browser;
+} AvahiBuddyImplData;
+
 static void
 _resolver_callback(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol,
 		  AvahiResolverEvent event, const char *name, const char *type, const char *domain,
@@ -59,11 +71,14 @@
 		case AVAHI_RESOLVER_FAILURE:
 			purple_debug_error("bonjour", "_resolve_callback - Failure: %s\n",
 				avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))));
+			avahi_service_resolver_free(r);
 			break;
 		case AVAHI_RESOLVER_FOUND:
 			/* create a buddy record */
 			buddy = bonjour_buddy_new(name, account);
 
+			((AvahiBuddyImplData *)buddy->mdns_impl_data)->resolver = r;
+
 			/* Get the ip as a string */
 			buddy->ip = g_malloc(AVAHI_ADDRESS_STR_MAX);
 			avahi_address_snprint(buddy->ip, AVAHI_ADDRESS_STR_MAX, a);
@@ -95,7 +110,6 @@
 			purple_debug_info("bonjour", "Unrecognized Service Resolver event: %d.\n", event);
 	}
 
-	avahi_service_resolver_free(r);
 }
 
 static void
@@ -145,6 +159,30 @@
 }
 
 static void
+_buddy_icon_group_cb(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
+	BonjourDnsSd *data = userdata;
+	AvahiSessionImplData *idata = data->mdns_impl_data;
+
+	g_return_if_fail(g == idata->buddy_icon_group || idata->buddy_icon_group == NULL);
+
+	switch(state) {
+		case AVAHI_ENTRY_GROUP_ESTABLISHED:
+			purple_debug_info("bonjour", "Successfully registered buddy icon data.\n");
+		case AVAHI_ENTRY_GROUP_COLLISION:
+			purple_debug_error("bonjour", "Collision registering buddy icon data.\n");
+			break;
+		case AVAHI_ENTRY_GROUP_FAILURE:
+			purple_debug_error("bonjour", "Error registering buddy icon data: %s\n.",
+				avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
+			break;
+		case AVAHI_ENTRY_GROUP_UNCOMMITED:
+		case AVAHI_ENTRY_GROUP_REGISTERING:
+			break;
+	}
+
+}
+
+static void
 _entry_group_cb(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
 	AvahiSessionImplData *idata = userdata;
 
@@ -170,6 +208,31 @@
 
 }
 
+static void
+_buddy_icon_record_cb(AvahiRecordBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol,
+		      AvahiBrowserEvent event, const char *name, uint16_t clazz, uint16_t type,
+		      const void *rdata, size_t size, AvahiLookupResultFlags flags, void *userdata) {
+	BonjourBuddy *buddy = userdata;
+	AvahiBuddyImplData *idata = buddy->mdns_impl_data;
+
+	switch (event) {
+		case AVAHI_BROWSER_NEW:
+			bonjour_buddy_got_buddy_icon(buddy, rdata, size);
+			break;
+		case AVAHI_BROWSER_REMOVE:
+		case AVAHI_BROWSER_CACHE_EXHAUSTED:
+		case AVAHI_BROWSER_ALL_FOR_NOW:
+		case AVAHI_BROWSER_FAILURE:
+			purple_debug_error("bonjour", "Error rerieving buddy icon record: %s\n",
+				avahi_strerror(avahi_client_errno(avahi_record_browser_get_client(b))));
+			break;
+	}
+
+	/* Stop listening */
+	avahi_record_browser_free(idata->buddy_icon_rec_browser);
+	idata->buddy_icon_rec_browser = NULL;
+}
+
 /****************************
  * mdns_interface functions *
  ****************************/
@@ -290,7 +353,8 @@
 		return FALSE;
 	}
 
-	if ((publish_result = avahi_entry_group_commit(idata->group)) < 0) {
+	if (type == PUBLISH_START
+			&& (publish_result = avahi_entry_group_commit(idata->group)) < 0) {
 		purple_debug_error("bonjour",
 			"Failed to commit " ICHAT_SERVICE " service. Error: %s\n",
 			avahi_strerror(publish_result));
@@ -317,9 +381,71 @@
 	return TRUE;
 }
 
-/* This is done differently than with Howl/Apple Bonjour */
-guint _mdns_register_to_mainloop(BonjourDnsSd *data) {
-	return 0;
+gboolean _mdns_set_buddy_icon_data(BonjourDnsSd *data, gconstpointer avatar_data, gsize avatar_len) {
+	AvahiSessionImplData *idata = data->mdns_impl_data;
+
+	if (idata == NULL || idata->client == NULL)
+		return FALSE;
+
+	if (avatar_data != NULL) {
+		gboolean new_group = FALSE;
+		gchar *svc_name;
+		int ret;
+		AvahiPublishFlags flags = 0;
+
+		if (idata->buddy_icon_group == NULL) {
+			purple_debug_info("bonjour", "Setting new buddy icon.\n");
+			new_group = TRUE;
+
+			idata->buddy_icon_group = avahi_entry_group_new(idata->client,
+				_buddy_icon_group_cb, data);
+		} else {
+			purple_debug_info("bonjour", "Updating existing buddy icon.\n");
+			flags |= AVAHI_PUBLISH_UPDATE;
+		}
+
+		if (idata->buddy_icon_group == NULL) {
+			purple_debug_error("bonjour",
+				"Unable to initialize the buddy icon group (%s).\n",
+				avahi_strerror(avahi_client_errno(idata->client)));
+			return FALSE;
+		}
+
+		svc_name = g_strdup_printf("%s." ICHAT_SERVICE "local",
+				purple_account_get_username(data->account));
+
+		ret = avahi_entry_group_add_record(idata->buddy_icon_group, AVAHI_IF_UNSPEC,
+			AVAHI_PROTO_UNSPEC, flags, svc_name,
+			AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_NULL, 120, avatar_data, avatar_len);
+
+		g_free(svc_name);
+
+		if (ret < 0) {
+			purple_debug_error("bonjour",
+				"Failed to register buddy icon. Error: %s\n", avahi_strerror(ret));
+			if (new_group) {
+				avahi_entry_group_free(idata->buddy_icon_group);
+				idata->buddy_icon_group = NULL;
+			}
+			return FALSE;
+		}
+
+		if (new_group && (ret = avahi_entry_group_commit(idata->buddy_icon_group)) < 0) {
+			purple_debug_error("bonjour",
+				"Failed to commit buddy icon group. Error: %s\n", avahi_strerror(ret));
+			if (new_group) {
+				avahi_entry_group_free(idata->buddy_icon_group);
+				idata->buddy_icon_group = NULL;
+			}
+			return FALSE;
+		}
+	} else if (idata->buddy_icon_group != NULL) {
+		purple_debug_info("bonjour", "Removing existing buddy icon.\n");
+		avahi_entry_group_free(idata->buddy_icon_group);
+		idata->buddy_icon_group = NULL;
+	}
+
+	return TRUE;
 }
 
 void _mdns_stop(BonjourDnsSd *data) {
@@ -340,10 +466,50 @@
 }
 
 void _mdns_init_buddy(BonjourBuddy *buddy) {
+	buddy->mdns_impl_data = g_new0(AvahiBuddyImplData, 1);
 }
 
 void _mdns_delete_buddy(BonjourBuddy *buddy) {
+	AvahiBuddyImplData *idata = buddy->mdns_impl_data;
+
+	g_return_if_fail(idata != NULL);
+
+	if (idata->buddy_icon_rec_browser != NULL)
+		avahi_record_browser_free(idata->buddy_icon_rec_browser);
+
+	if (idata->resolver != NULL)
+		avahi_service_resolver_free(idata->resolver);
+
+	g_free(idata);
+
+	buddy->mdns_impl_data = NULL;
 }
 
-void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy) {
+void _mdns_retrieve_buddy_icon(BonjourBuddy* buddy) {
+	PurpleConnection *conn = purple_account_get_connection(buddy->account);
+	BonjourData *bd = conn->proto_data;
+	AvahiSessionImplData *session_idata = bd->dns_sd_data->mdns_impl_data;
+	AvahiBuddyImplData *idata = buddy->mdns_impl_data;
+	gchar *name;
+
+	g_return_if_fail(idata != NULL);
+
+	if (idata->buddy_icon_rec_browser != NULL)
+		avahi_record_browser_free(idata->buddy_icon_rec_browser);
+
+	purple_debug_info("bonjour", "Retrieving buddy icon for '%s'.\n", buddy->name);
+
+	name = g_strdup_printf("%s." ICHAT_SERVICE "local", buddy->name);
+	idata->buddy_icon_rec_browser = avahi_record_browser_new(session_idata->client, AVAHI_IF_UNSPEC,
+		AVAHI_PROTO_UNSPEC, name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_NULL, 0,
+		_buddy_icon_record_cb, buddy);
+	g_free(name);
+
+	if (!idata->buddy_icon_rec_browser) {
+		purple_debug_error("bonjour",
+			"Unable to initialize buddy icon record browser.  Error: %s\n.",
+			avahi_strerror(avahi_client_errno(session_idata->client)));
+	}
+
 }
+
--- a/libpurple/protocols/bonjour/mdns_common.c	Thu Aug 09 02:42:49 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_common.c	Thu Aug 09 18:34:01 2007 +0000
@@ -17,11 +17,13 @@
 #include <string.h>
 
 #include "internal.h"
+#include "cipher.h"
+#include "debug.h"
+
 #include "mdns_common.h"
 #include "mdns_interface.h"
 #include "bonjour.h"
 #include "buddy.h"
-#include "debug.h"
 
 
 /**
@@ -54,8 +56,7 @@
  * Send a new dns-sd packet updating our status.
  */
 void
-bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message)
-{
+bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message) {
 	g_free(data->status);
 	g_free(data->msg);
 
@@ -67,6 +68,64 @@
 }
 
 /**
+ * Retrieve the buddy icon blob
+ */
+void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy) {
+	_mdns_retrieve_buddy_icon(buddy);
+}
+
+void
+bonjour_dns_sd_update_buddy_icon(BonjourDnsSd *data) {
+	PurpleStoredImage *img;
+
+	if ((img = purple_buddy_icons_find_account_icon(data->account))) {
+		gconstpointer avatar_data;
+		gsize avatar_len;
+
+		avatar_data = purple_imgstore_get_data(img);
+		avatar_len = purple_imgstore_get_size(img);
+
+		if (_mdns_set_buddy_icon_data(data, avatar_data, avatar_len)) {
+			int i;
+			gchar *enc;
+			char *p, hash[41];
+			unsigned char hashval[20];
+
+			enc = purple_base64_encode(avatar_data, avatar_len);
+
+			purple_cipher_digest_region("sha1", avatar_data,
+						    avatar_len, sizeof(hashval),
+						    hashval, NULL);
+
+			p = hash;
+			for(i=0; i<20; i++, p+=2)
+				snprintf(p, 3, "%02x", hashval[i]);
+
+			g_free(data->phsh);
+			data->phsh = g_strdup(hash);
+
+			g_free(enc);
+
+			/* Update our TXT record */
+			_mdns_publish(data, PUBLISH_UPDATE);
+		}
+
+		purple_imgstore_unref(img);
+	} else {
+		/* We need to do this regardless of whether data->phsh is set so that we
+		 * cancel any icons that are currently in the process of being set */
+		_mdns_set_buddy_icon_data(data, NULL, 0);
+		if (data->phsh != NULL) {
+			/* Clear the buddy icon */
+			g_free(data->phsh);
+			data->phsh = NULL;
+			/* Update our TXT record */
+			_mdns_publish(data, PUBLISH_UPDATE);
+		}
+	}
+}
+
+/**
  * Advertise our presence within the dns-sd daemon and start browsing
  * for other bonjour peers.
  */
@@ -91,11 +150,6 @@
 		return FALSE;
 	}
 
-
-	/* Get the socket that communicates with the mDNS daemon and bind it to a */
-	/* callback that will handle the dns_sd packets */
-	gc->inpa = _mdns_register_to_mainloop(data);
-
 	return TRUE;
 }
 
@@ -104,13 +158,6 @@
  */
 
 void
-bonjour_dns_sd_stop(BonjourDnsSd *data)
-{
-	PurpleConnection *gc;
-
+bonjour_dns_sd_stop(BonjourDnsSd *data) {
 	_mdns_stop(data);
-
-	gc = purple_account_get_connection(data->account);
-	if (gc->inpa > 0)
-		purple_input_remove(gc->inpa);
 }
--- a/libpurple/protocols/bonjour/mdns_common.h	Thu Aug 09 02:42:49 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_common.h	Thu Aug 09 18:34:01 2007 +0000
@@ -42,6 +42,11 @@
 void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy);
 
 /**
+ * Deal with a buddy icon update
+ */
+void bonjour_dns_sd_update_buddy_icon(BonjourDnsSd *data);
+
+/**
  * Advertise our presence within the dns-sd daemon and start
  * browsing for other bonjour peers.
  */
--- a/libpurple/protocols/bonjour/mdns_howl.c	Thu Aug 09 02:42:49 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_howl.c	Thu Aug 09 18:34:01 2007 +0000
@@ -26,6 +26,7 @@
 typedef struct _howl_impl_data {
 	sw_discovery session;
 	sw_discovery_oid session_id;
+	guint session_handler;
 } HowlSessionImplData;
 
 static sw_result HOWL_API
@@ -276,17 +277,18 @@
 
 	g_return_val_if_fail(idata != NULL, FALSE);
 
-	return (sw_discovery_browse(idata->session, 0, ICHAT_SERVICE, NULL, _browser_reply,
-				    data->account, &session_id) == SW_OKAY);
+	if (sw_discovery_browse(idata->session, 0, ICHAT_SERVICE, NULL, _browser_reply,
+				    data->account, &session_id) == SW_OKAY) {
+		idata->session_handler = purple_input_add(sw_discovery_socket(idata->session),
+				PURPLE_INPUT_READ, _mdns_handle_event, idata->session);
+		return TRUE;
+	}
+
+	return FALSE;
 }
 
-guint _mdns_register_to_mainloop(BonjourDnsSd *data) {
-	HowlSessionImplData *idata = data->mdns_impl_data;
-
-	g_return_val_if_fail(idata != NULL, 0);
-
-	return purple_input_add(sw_discovery_socket(idata->session),
-				PURPLE_INPUT_READ, _mdns_handle_event, idata->session);
+gboolean _mdns_set_buddy_icon_data(BonjourDnsSd *data, gconstpointer avatar_data, gsize avatar_len) {
+	return FALSE;
 }
 
 void _mdns_stop(BonjourDnsSd *data) {
@@ -297,6 +299,8 @@
 
 	sw_discovery_cancel(idata->session, idata->session_id);
 
+	purple_input_remove(idata->session_handler);
+
 	/* TODO: should this really be g_free()'d ??? */
 	g_free(idata->session);
 
@@ -311,5 +315,7 @@
 void _mdns_delete_buddy(BonjourBuddy *buddy) {
 }
 
-void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy) {
+void _mdns_retrieve_buddy_icon(BonjourBuddy* buddy) {
 }
+
+
--- a/libpurple/protocols/bonjour/mdns_interface.h	Thu Aug 09 02:42:49 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_interface.h	Thu Aug 09 18:34:01 2007 +0000
@@ -26,15 +26,14 @@
 
 gboolean _mdns_browse(BonjourDnsSd *data);
 
-guint _mdns_register_to_mainloop(BonjourDnsSd *data);
+void _mdns_stop(BonjourDnsSd *data);
 
-void _mdns_stop(BonjourDnsSd *data);
+gboolean _mdns_set_buddy_icon_data(BonjourDnsSd *data, gconstpointer avatar_data, gsize avatar_len);
 
 void _mdns_init_buddy(BonjourBuddy *buddy);
 
 void _mdns_delete_buddy(BonjourBuddy *buddy);
 
-/* This doesn't quite belong here, but there really isn't any shared functionality */
-void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy);
+void _mdns_retrieve_buddy_icon(BonjourBuddy* buddy);
 
 #endif
--- a/libpurple/protocols/bonjour/mdns_win32.c	Thu Aug 09 02:42:49 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_win32.c	Thu Aug 09 18:34:01 2007 +0000
@@ -21,12 +21,14 @@
 #include "mdns_interface.h"
 #include "dns_sd_proxy.h"
 #include "dnsquery.h"
+#include "mdns_common.h"
 
 
 /* data structure for the resolve callback */
 typedef struct _ResolveCallbackArgs {
 	DNSServiceRef resolver;
 	guint resolver_handler;
+	gchar *full_service_name;
 
 	PurpleDnsQueryData *query;
 
@@ -35,10 +37,12 @@
 
 /* data used by win32 bonjour implementation */
 typedef struct _win32_session_impl_data {
-	DNSServiceRef advertisement;
-	DNSServiceRef browser;
+	DNSServiceRef presence_svc;
+	DNSServiceRef browser_svc;
+	DNSRecordRef buddy_icon_rec;
 
-	guint advertisement_handler; /* hack... windows bonjour is broken, so we have to have this */
+	guint presence_handler;
+	guint browser_handler;
 } Win32SessionImplData;
 
 typedef struct _win32_buddy_impl_data {
@@ -90,8 +94,7 @@
 
 			g_return_if_fail(idata != NULL);
 
-			purple_buddy_icons_set_for_user(buddy->account, buddy->name,
-							g_memdup(rdata, rdlen), rdlen, buddy->phsh);
+			bonjour_buddy_got_buddy_icon(buddy, rdata, rdlen);
 
 			/* We've got what we need; stop listening */
 			purple_input_remove(idata->null_query_handler);
@@ -120,14 +123,16 @@
 
 		/* finally, set up the continuous txt record watcher, and add the buddy to purple */
 
-		if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->txt_query, 0, 0, buddy->full_service_name,
-				kDNSServiceType_TXT, kDNSServiceClass_IN, _mdns_text_record_query_callback, buddy)) {
-			int fd = DNSServiceRefSockFD(idata->txt_query);
-			idata->txt_query_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, idata->txt_query);
+		if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->txt_query, kDNSServiceFlagsLongLivedQuery,
+				kDNSServiceInterfaceIndexAny, args->full_service_name, kDNSServiceType_TXT,
+				kDNSServiceClass_IN, _mdns_text_record_query_callback, buddy)) {
+
+			purple_debug_info("bonjour", "Found buddy %s at %s:%d\n", buddy->name, buddy->ip, buddy->port_p2pj);
+
+			idata->txt_query_handler = purple_input_add(DNSServiceRefSockFD(idata->txt_query),
+				PURPLE_INPUT_READ, _mdns_handle_event, idata->txt_query);
 
 			bonjour_buddy_add_to_purple(buddy);
-
-			purple_debug_info("bonjour", "Found buddy %s at %s:%d\n", buddy->name, buddy->ip, buddy->port_p2pj);
 		} else
 			bonjour_buddy_delete(buddy);
 
@@ -138,6 +143,7 @@
 
 	/* free the remaining args memory */
 	purple_dnsquery_destroy(args->query);
+	g_free(args->full_service_name);
 	g_free(args);
 }
 
@@ -165,13 +171,14 @@
 		_mdns_parse_text_record(args->buddy, txtRecord, txtLen);
 
 		/* set more arguments, and start the host resolver */
-		args->buddy->full_service_name = g_strdup(fullname);
+		args->full_service_name = g_strdup(fullname);
 
 		if (!(args->query =
 			purple_dnsquery_a(hosttarget, port, _mdns_resolve_host_callback, args)))
 		{
 			purple_debug_error("bonjour", "service resolver - host resolution failed.\n");
 			bonjour_buddy_delete(args->buddy);
+			g_free(args->full_service_name);
 			g_free(args);
 		}
 	}
@@ -180,11 +187,11 @@
 
 static void DNSSD_API
 _mdns_service_register_callback(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode,
-    const char *name, const char *regtype, const char *domain, void *context)
-{
-	/* we don't actually care about anything said in this callback - this is only here because Bonjour for windows is broken */
+				const char *name, const char *regtype, const char *domain, void *context) {
+
+	/* TODO: deal with collision */
 	if (kDNSServiceErr_NoError != errorCode)
-		purple_debug_error("bonjour", "service advertisement - callback error.\n");
+		purple_debug_error("bonjour", "service advertisement - callback error (%d).\n", errorCode);
 	else
 		purple_debug_info("bonjour", "service advertisement - callback.\n");
 }
@@ -301,14 +308,15 @@
 
 		switch (type) {
 			case PUBLISH_START:
-				purple_debug_info("bonjour", "Registering service on port %d\n", data->port_p2pj);
-				err = DNSServiceRegister(&idata->advertisement, 0, 0, purple_account_get_username(data->account), ICHAT_SERVICE,
+				purple_debug_info("bonjour", "Registering presence on port %d\n", data->port_p2pj);
+				err = DNSServiceRegister(&idata->presence_svc, 0, 0, purple_account_get_username(data->account), ICHAT_SERVICE,
 					NULL, NULL, htons(data->port_p2pj), TXTRecordGetLength(&dns_data), TXTRecordGetBytesPtr(&dns_data),
 					_mdns_service_register_callback, NULL);
 				break;
 
 			case PUBLISH_UPDATE:
-				err = DNSServiceUpdateRecord(idata->advertisement, NULL, 0, TXTRecordGetLength(&dns_data), TXTRecordGetBytesPtr(&dns_data), 0);
+				purple_debug_info("bonjour", "Updating presence.\n");
+				err = DNSServiceUpdateRecord(idata->presence_svc, NULL, 0, TXTRecordGetLength(&dns_data), TXTRecordGetBytesPtr(&dns_data), 0);
 				break;
 		}
 
@@ -316,9 +324,12 @@
 			purple_debug_error("bonjour", "Failed to publish presence service.\n");
 			ret = FALSE;
 		} else if (type == PUBLISH_START) {
-			/* hack: Bonjour on windows is broken. We don't care about the callback but we have to listen anyway */
-			gint fd = DNSServiceRefSockFD(idata->advertisement);
-			idata->advertisement_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, idata->advertisement);
+			/* We need to do this because according to the Apple docs:
+			 * "the client is responsible for ensuring that DNSServiceProcessResult() is called
+			 * whenever there is a reply from the daemon - the daemon may terminate its connection
+			 * with a client that does not process the daemon's responses */
+			idata->presence_handler = purple_input_add(DNSServiceRefSockFD(idata->presence_svc),
+				PURPLE_INPUT_READ, _mdns_handle_event, idata->presence_svc);
 		}
 	}
 
@@ -332,37 +343,64 @@
 
 	g_return_val_if_fail(idata != NULL, FALSE);
 
-	return (DNSServiceBrowse(&idata->browser, 0, 0, ICHAT_SERVICE, NULL,
+	if (DNSServiceBrowse(&idata->browser_svc, 0, 0, ICHAT_SERVICE, NULL,
 				 _mdns_service_browse_callback, data->account)
-			== kDNSServiceErr_NoError);
-}
+			== kDNSServiceErr_NoError) {
+		idata->browser_handler = purple_input_add(DNSServiceRefSockFD(idata->browser_svc),
+			PURPLE_INPUT_READ, _mdns_handle_event, idata->browser_svc);
+		return TRUE;
+	}
 
-guint _mdns_register_to_mainloop(BonjourDnsSd *data) {
-	Win32SessionImplData *idata = data->mdns_impl_data;
-
-	g_return_val_if_fail(idata != NULL, 0);
-
-	return purple_input_add(DNSServiceRefSockFD(idata->browser),
-				PURPLE_INPUT_READ, _mdns_handle_event, idata->browser);
+	return FALSE;
 }
 
 void _mdns_stop(BonjourDnsSd *data) {
 	Win32SessionImplData *idata = data->mdns_impl_data;
 
-	if (idata == NULL || idata->advertisement == NULL || idata->browser == NULL)
+	if (idata == NULL)
 		return;
 
-	/* hack: for win32, we need to stop listening to the advertisement pipe too */
-	purple_input_remove(idata->advertisement_handler);
+	if (idata->presence_svc != NULL) {
+		purple_input_remove(idata->presence_handler);
+		DNSServiceRefDeallocate(idata->presence_svc);
+	}
 
-	DNSServiceRefDeallocate(idata->advertisement);
-	DNSServiceRefDeallocate(idata->browser);
+	if (idata->browser_svc != NULL) {
+		purple_input_remove(idata->browser_handler);
+		DNSServiceRefDeallocate(idata->browser_svc);
+	}
 
 	g_free(idata);
 
 	data->mdns_impl_data = NULL;
 }
 
+gboolean _mdns_set_buddy_icon_data(BonjourDnsSd *data, gconstpointer avatar_data, gsize avatar_len) {
+	Win32SessionImplData *idata = data->mdns_impl_data;
+	DNSServiceErrorType err = kDNSServiceErr_NoError;
+
+	g_return_val_if_fail(idata != NULL, FALSE);
+
+	if (avatar_data != NULL && idata->buddy_icon_rec == NULL) {
+		purple_debug_info("bonjour", "Setting new buddy icon.\n");
+		err = DNSServiceAddRecord(idata->presence_svc, &idata->buddy_icon_rec,
+			0, kDNSServiceType_NULL, avatar_len, avatar_data, 0);
+	} else if (avatar_data != NULL) {
+		purple_debug_info("bonjour", "Updating existing buddy icon.\n");
+		err = DNSServiceUpdateRecord(idata->presence_svc, idata->buddy_icon_rec,
+			0, avatar_len, avatar_data, 0);
+	} else if (idata->buddy_icon_rec != NULL) {
+		purple_debug_info("bonjour", "Removing existing buddy icon.\n");
+		DNSServiceRemoveRecord(idata->presence_svc, idata->buddy_icon_rec, 0);
+		idata->buddy_icon_rec = NULL;
+	}
+
+	if (err != kDNSServiceErr_NoError)
+		purple_debug_error("bonjour", "Error (%d) setting buddy icon record.\n", err);
+
+	return (err == kDNSServiceErr_NoError);
+}
+
 void _mdns_init_buddy(BonjourBuddy *buddy) {
 	buddy->mdns_impl_data = g_new0(Win32BuddyImplData, 1);
 }
@@ -387,8 +425,9 @@
 	buddy->mdns_impl_data = NULL;
 }
 
-void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy) {
+void _mdns_retrieve_buddy_icon(BonjourBuddy* buddy) {
 	Win32BuddyImplData *idata = buddy->mdns_impl_data;
+	char svc_name[kDNSServiceMaxDomainName];
 
 	g_return_if_fail(idata != NULL);
 
@@ -400,10 +439,11 @@
 		idata->null_query = NULL;
 	}
 
-	if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->null_query, 0, 0, buddy->full_service_name,
+	DNSServiceConstructFullName(svc_name, buddy->name, ICHAT_SERVICE, "local");
+	if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->null_query, 0, kDNSServiceInterfaceIndexAny, svc_name,
 			kDNSServiceType_NULL, kDNSServiceClass_IN, _mdns_text_record_query_callback, buddy)) {
-		int fd = DNSServiceRefSockFD(idata->null_query);
-		idata->null_query_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, idata->null_query);
+		idata->null_query_handler = purple_input_add(DNSServiceRefSockFD(idata->null_query),
+			PURPLE_INPUT_READ, _mdns_handle_event, idata->null_query);
 	}
 
 }