Mercurial > pidgin.yaz
diff libpurple/protocols/bonjour/mdns_avahi.c @ 18805:1244b5f43661
Here is a native avahi implementation of the bonjour prpl that I whipped up. Someone more autofoo savvy will probably want to review what I've done to that stuff. Fixes #326.
author | Daniel Atallah <daniel.atallah@gmail.com> |
---|---|
date | Mon, 06 Aug 2007 00:00:45 +0000 |
parents | |
children | 2ae9b483c4db 7bf6b9a70b41 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/bonjour/mdns_avahi.c Mon Aug 06 00:00:45 2007 +0000 @@ -0,0 +1,349 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "internal.h" + +#include "mdns_interface.h" +#include "debug.h" +#include "buddy.h" + +#include <avahi-client/client.h> +#include <avahi-client/lookup.h> +#include <avahi-client/publish.h> + +#include <avahi-common/address.h> +#include <avahi-common/malloc.h> +#include <avahi-common/error.h> +#include <avahi-common/strlst.h> + +#include <avahi-glib/glib-malloc.h> +#include <avahi-glib/glib-watch.h> + +/* data used by avahi bonjour implementation */ +typedef struct _avahi_session_impl_data { + AvahiClient *client; + AvahiGLibPoll *glib_poll; + AvahiServiceBrowser *sb; + AvahiEntryGroup *group; +} AvahiSessionImplData; + +static void +_resolver_callback(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, + AvahiResolverEvent event, const char *name, const char *type, const char *domain, + const char *host_name, const AvahiAddress *a, uint16_t port, AvahiStringList *txt, + AvahiLookupResultFlags flags, void *userdata) { + + BonjourBuddy *buddy; + PurpleAccount *account = userdata; + AvahiStringList *l; + size_t size; + char *key, *value; + int ret; + + g_return_if_fail(r != NULL); + + switch (event) { + case AVAHI_RESOLVER_FAILURE: + purple_debug_error("bonjour", "_resolve_callback - Failure: %s\n", + avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r)))); + break; + case AVAHI_RESOLVER_FOUND: + /* create a buddy record */ + buddy = bonjour_buddy_new(name, account); + + /* Get the ip as a string */ + buddy->ip = g_malloc(AVAHI_ADDRESS_STR_MAX); + avahi_address_snprint(buddy->ip, AVAHI_ADDRESS_STR_MAX, a); + + buddy->port_p2pj = port; + + /* Obtain the parameters from the text_record */ + l = txt; + while (l != NULL) { + ret = avahi_string_list_get_pair(l, &key, &value, &size); + l = l->next; + if (ret < 0) + continue; + set_bonjour_buddy_value(buddy, key, value, size); + /* TODO: Since we're using the glib allocator, I think we + * can use the values instead of re-copying them */ + avahi_free(key); + avahi_free(value); + } + + if (!bonjour_buddy_check(buddy)) + bonjour_buddy_delete(buddy); + else + /* Add or update the buddy in our buddy list */ + bonjour_buddy_add_to_purple(buddy); + + break; + default: + purple_debug_info("bonjour", "Unrecognized Service Resolver event: %d.\n", event); + } + + avahi_service_resolver_free(r); +} + +static void +_browser_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, + AvahiProtocol protocol, AvahiBrowserEvent event, + const char *name, const char *type, const char *domain, + AvahiLookupResultFlags flags, void *userdata) { + + PurpleAccount *account = userdata; + PurpleBuddy *gb = NULL; + + switch (event) { + case AVAHI_BROWSER_FAILURE: + purple_debug_error("bonjour", "_browser_callback - Failure: %s\n", + avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b)))); + /* TODO: This is an error that should be handled. */ + break; + case AVAHI_BROWSER_NEW: + /* A new peer has joined the network and uses iChat bonjour */ + purple_debug_info("bonjour", "_browser_callback - new service\n"); + /* Make sure it isn't us */ + if (g_ascii_strcasecmp(name, account->username) != 0) { + if (!avahi_service_resolver_new(avahi_service_browser_get_client(b), + interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, + 0, _resolver_callback, account)) { + purple_debug_warning("bonjour", "_browser_callback -- Error initiating resolver: %s\n", + avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b)))); + } + } + break; + case AVAHI_BROWSER_REMOVE: + purple_debug_info("bonjour", "_browser_callback - Remove service\n"); + gb = purple_find_buddy(account, name); + if (gb != NULL) { + bonjour_buddy_delete(gb->proto_data); + purple_blist_remove_buddy(gb); + } + break; + case AVAHI_BROWSER_ALL_FOR_NOW: + case AVAHI_BROWSER_CACHE_EXHAUSTED: + purple_debug_warning("bonjour", "(Browser) %s\n", + event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW"); + break; + default: + purple_debug_info("bonjour", "Unrecognized Service browser event: %d.\n", event); + } +} + +static void +_entry_group_cb(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) { + AvahiSessionImplData *idata = userdata; + + g_return_if_fail(g == idata->group || idata->group == NULL); + + switch(state) { + case AVAHI_ENTRY_GROUP_ESTABLISHED: + purple_debug_info("bonjour", "Successfully registered service.\n"); + break; + case AVAHI_ENTRY_GROUP_COLLISION: + purple_debug_error("bonjour", "Collision registering entry group.\n"); + /* TODO: Handle error - this should log out the account. (Possibly with "wants to die")*/ + break; + case AVAHI_ENTRY_GROUP_FAILURE: + purple_debug_error("bonjour", "Error registering entry group: %s\n.", + avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g)))); + /* TODO: Handle error - this should log out the account.*/ + break; + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING: + break; + } + +} + +/**************************** + * mdns_interface functions * + ****************************/ + +gboolean _mdns_init_session(BonjourDnsSd *data) { + AvahiSessionImplData *idata = g_new0(AvahiSessionImplData, 1); + const AvahiPoll *poll_api; + int error; + + /* Tell avahi to use g_malloc and g_free */ + avahi_set_allocator (avahi_glib_allocator ()); + + /* This currently depends on the glib mainloop, + * we should make it use the libpurple abstraction */ + + idata->glib_poll = avahi_glib_poll_new(NULL, G_PRIORITY_DEFAULT); + + poll_api = avahi_glib_poll_get(idata->glib_poll); + + idata->client = avahi_client_new(poll_api, 0, NULL, data, &error); + + if (idata->client == NULL) { + purple_debug_error("bonjour", "Error initializing Avahi: %s", avahi_strerror(error)); + avahi_glib_poll_free(idata->glib_poll); + g_free(idata); + return FALSE; + } + + data->mdns_impl_data = idata; + + return TRUE; +} + +gboolean _mdns_publish(BonjourDnsSd *data, PublishType type) { + int publish_result = 0; + char portstring[6]; + const char *jid, *aim, *email; + AvahiSessionImplData *idata = data->mdns_impl_data; + AvahiStringList *lst = NULL; + + g_return_val_if_fail(idata != NULL, FALSE); + + if (!idata->group) { + idata->group = avahi_entry_group_new(idata->client, + _entry_group_cb, idata); + if (!idata->group) { + purple_debug_error("bonjour", + "Unable to initialize the data for the mDNS (%s).\n", + avahi_strerror(avahi_client_errno(idata->client))); + return FALSE; + } + } + + /* Convert the port to a string */ + snprintf(portstring, sizeof(portstring), "%d", data->port_p2pj); + + jid = purple_account_get_string(data->account, "jid", NULL); + aim = purple_account_get_string(data->account, "AIM", NULL); + email = purple_account_get_string(data->account, "email", NULL); + + /* We should try to follow XEP-0174, but some clients have "issues", so we humor them. + * See http://telepathy.freedesktop.org/wiki/SalutInteroperability + */ + + /* Needed by iChat */ + lst = avahi_string_list_add_pair(lst,"txtvers", "1"); + /* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */ + lst = avahi_string_list_add_pair(lst, "1st", data->first); + /* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */ + lst = avahi_string_list_add_pair(lst, "last", data->last); + /* Needed by Adium */ + lst = avahi_string_list_add_pair(lst, "port.p2pj", portstring); + /* Needed by iChat, Gaim/Pidgin <= 2.0.1 */ + lst = avahi_string_list_add_pair(lst, "status", data->status); + /* Currently always set to "!" since we don't support AV and wont ever be in a conference */ + lst = avahi_string_list_add_pair(lst, "vc", data->vc); + lst = avahi_string_list_add_pair(lst, "ver", VERSION); + if (email != NULL && *email != '\0') + lst = avahi_string_list_add_pair(lst, "email", email); + if (jid != NULL && *jid != '\0') + lst = avahi_string_list_add_pair(lst, "jid", jid); + /* Nonstandard, but used by iChat */ + if (aim != NULL && *aim != '\0') + lst = avahi_string_list_add_pair(lst, "AIM", aim); + if (data->msg != NULL && *data->msg != '\0') + lst = avahi_string_list_add_pair(lst, "msg", data->msg); + if (data->phsh != NULL && *data->phsh != '\0') + lst = avahi_string_list_add_pair(lst, "phsh", data->phsh); + + /* TODO: ext, nick, node */ + + + /* Publish the service */ + switch (type) { + case PUBLISH_START: + publish_result = avahi_entry_group_add_service_strlst( + idata->group, AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, 0, + purple_account_get_username(data->account), + ICHAT_SERVICE, NULL, NULL, data->port_p2pj, lst); + break; + case PUBLISH_UPDATE: + publish_result = avahi_entry_group_update_service_txt_strlst( + idata->group, AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, 0, + purple_account_get_username(data->account), + ICHAT_SERVICE, NULL, lst); + break; + } + + /* Free the memory used by temp data */ + avahi_string_list_free(lst); + + if (publish_result < 0) { + purple_debug_error("bonjour", + "Failed to add the " ICHAT_SERVICE " service. Error: %s\n", + avahi_strerror(publish_result)); + return FALSE; + } + + if ((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)); + return FALSE; + } + + return TRUE; +} + +gboolean _mdns_browse(BonjourDnsSd *data) { + AvahiSessionImplData *idata = data->mdns_impl_data; + + g_return_val_if_fail(idata != NULL, FALSE); + + idata->sb = avahi_service_browser_new(idata->client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, ICHAT_SERVICE, NULL, 0, _browser_callback, data->account); + if (!idata->sb) { + + purple_debug_error("bonjour", + "Unable to initialize service browser. Error: %s\n.", + avahi_strerror(avahi_client_errno(idata->client))); + return FALSE; + } + + return TRUE; +} + +/* This is done differently than with Howl/Apple Bonjour */ +guint _mdns_register_to_mainloop(BonjourDnsSd *data) { + return 0; +} + +void _mdns_stop(BonjourDnsSd *data) { + AvahiSessionImplData *idata = data->mdns_impl_data; + + if (idata == NULL || idata->client == NULL) + return; + + if (idata->sb != NULL) + avahi_service_browser_free(idata->sb); + + avahi_client_free(idata->client); + avahi_glib_poll_free(idata->glib_poll); + + g_free(idata); + + data->mdns_impl_data = NULL; +} + +void _mdns_init_buddy(BonjourBuddy *buddy) { +} + +void _mdns_delete_buddy(BonjourBuddy *buddy) { +} + +void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy) { +}