diff src/daap/xmms2-daap/daap_mdns_dnssd.c @ 1407:776dd8fc2b38

DAAP plugin (work in progress)
author Cristi Magherusan <majeru@atheme-project.org>
date Sun, 05 Aug 2007 00:26:21 +0300
parents
children 7b3290336f3b
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/daap/xmms2-daap/daap_mdns_dnssd.c	Sun Aug 05 00:26:21 2007 +0300
@@ -0,0 +1,418 @@
+/** XMMS2 transform for accessing DAAP music shares.
+ *
+ *  Copyright (C) 2006-2007 XMMS2 Team
+ *
+ *  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 General Public License for more details.
+ */
+
+#include <glib.h>
+#include <dns_sd.h>
+#include <string.h>
+
+#include "daap_mdns_browse.h"
+
+typedef struct GMDNS_t GMDNS;
+typedef struct GMDNSServer_t GMDNSServer;
+typedef struct GMDNSUserData_t GMDNSUserData;
+
+typedef void (*GMDNSFunc)(GMDNS *, gint, GMDNSServer *, gpointer);
+
+struct GMDNS_t {
+	GMutex *mutex;
+	GSList *service_list;
+
+	GMDNSFunc callback;
+	gpointer user_data;
+	GMDNSUserData *browse_ud;
+};
+
+struct GMDNSUserData_t {
+	GMDNS *mdns;
+	GMDNSServer *server;
+	GPollFD *fd;
+	GSource *source;
+
+	DNSServiceRef client;
+};
+
+struct GMDNSServer_t {
+	gchar *mdnsname;
+	gchar *hostname;
+	gchar *address;
+	GHashTable *txtvalues;
+	guint16 port;
+};
+
+enum {
+	G_MDNS_SERVER_ADD,
+	G_MDNS_SERVER_REMOVE
+};
+
+static void g_mdns_user_data_destroy (GMDNSUserData *ud);
+static void g_mdns_server_destroy (GMDNSServer *server);
+static gboolean g_mdns_poll_add (GMDNS *mdns, GMDNSUserData *ud, DNSServiceRef client);
+
+static GMDNS *g_mdns;
+
+static void
+qr_reply (DNSServiceRef sdRef,
+          DNSServiceFlags flags,
+          uint32_t ifIndex,
+          DNSServiceErrorType errorCode,
+          const char *fullname,
+          uint16_t rrtype,
+          uint16_t rrclass,
+          uint16_t rdlen,
+          const void *rdata,
+          uint32_t ttl,
+          void *context)
+{
+	GMDNSUserData *ud = context;
+	gchar addr[1000];
+	const guchar *rd = (guchar *)rdata;
+
+	g_return_if_fail (ud);
+	g_return_if_fail (rrtype == kDNSServiceType_A);
+
+	g_snprintf (addr, 1000, "%d.%d.%d.%d", rd[0], rd[1], rd[2], rd[3]);
+
+	ud->server->address = g_strdup (addr);
+
+	g_print ("adding server %s %s", ud->server->mdnsname, ud->server->address);
+	g_mutex_lock (ud->mdns->mutex);
+	ud->mdns->service_list = g_slist_prepend (ud->mdns->service_list, ud->server);
+	g_mutex_unlock (ud->mdns->mutex);
+
+	if (ud->mdns->callback) {
+		ud->mdns->callback (ud->mdns, G_MDNS_SERVER_ADD, ud->server, ud->mdns->user_data);
+	}
+	g_mdns_user_data_destroy (ud);
+}
+
+
+static void
+resolve_reply (DNSServiceRef client,
+               DNSServiceFlags flags,
+               uint32_t ifIndex,
+               DNSServiceErrorType errorCode,
+               const char *fullname,
+               const char *hosttarget,
+               uint16_t opaqueport,
+               uint16_t txtLen,
+               const char *txtRecord,
+               void *context)
+{
+	GMDNSUserData *ud = context;
+	GMDNSUserData *ud2;
+	DNSServiceErrorType err;
+	gint i;
+	union { guint16 s; guchar b[2]; } portu = { opaqueport };
+
+	g_return_if_fail (ud);
+
+	ud->server->port = ((guint16) portu.b[0]) << 8 | portu.b[1];
+	ud->server->hostname = g_strdup (hosttarget);
+	ud->server->txtvalues = g_hash_table_new_full (g_str_hash, g_str_equal,
+	                                               g_free, g_free);
+
+	for (i = 0; i < TXTRecordGetCount (txtLen, txtRecord); i++) {
+		gchar key[256];
+		const void *txt_value;
+		gchar *value;
+		guint8 vallen;
+
+		err = TXTRecordGetItemAtIndex (txtLen, txtRecord, i, 256, key, &vallen, &txt_value);
+		if (err != kDNSServiceErr_NoError) {
+			g_warning ("error parsing TXT records!");
+		}
+
+		value = g_malloc (vallen + 1);
+		g_strlcpy (value, txt_value, vallen + 1);
+		g_hash_table_insert (ud->server->txtvalues, g_strdup (key), value);
+	}
+
+	ud2 = g_new0 (GMDNSUserData, 1);
+
+	err = DNSServiceQueryRecord (&ud2->client, 0,
+	                             kDNSServiceInterfaceIndexAny,
+	                             ud->server->hostname,
+	                             kDNSServiceType_A,
+	                             kDNSServiceClass_IN,
+	                             qr_reply, ud2);
+
+	if (err != kDNSServiceErr_NoError) {
+		g_warning ("Error from QueryRecord!");
+	}
+
+	g_mdns_poll_add (ud->mdns, ud2, ud2->client);
+	ud2->server = ud->server;
+
+	g_mdns_user_data_destroy (ud);
+}
+
+
+static void
+browse_reply (DNSServiceRef client,
+              DNSServiceFlags flags,
+              uint32_t ifIndex,
+              DNSServiceErrorType errorCode,
+              const char *replyName,
+              const char *replyType,
+              const char *replyDomain,
+              void *context)
+{
+	DNSServiceErrorType err;
+	GMDNSServer *server;
+	GMDNSUserData *ud = context;
+	GMDNSUserData *ud2;
+	gboolean remove = (flags & kDNSServiceFlagsAdd) ? FALSE : TRUE;
+
+	if (!remove) {
+		server = g_new0 (GMDNSServer, 1);
+		server->mdnsname = g_strdup (replyName);
+		ud2 = g_new0 (GMDNSUserData, 1);
+		err = DNSServiceResolve (&ud2->client, 0, kDNSServiceInterfaceIndexAny,
+		                         server->mdnsname,
+		                         "_daap._tcp", "local",
+		                         resolve_reply, ud2);
+
+		if (err != kDNSServiceErr_NoError) {
+			g_warning ("Couldn't do ServiceResolv");
+			g_free (server->mdnsname);
+			g_free (server);
+			return;
+		}
+
+		ud2->server = server;
+
+		g_mdns_poll_add (ud->mdns, ud2, ud2->client);
+	} else {
+		GSList *n, *nxt;
+		g_mutex_lock (ud->mdns->mutex);
+		for (n = ud->mdns->service_list; n; n = nxt) {
+			nxt = g_slist_next (n);
+			GMDNSServer *server = n->data;
+			if (strcmp (server->mdnsname, replyName) == 0) {
+				n = ud->mdns->service_list = g_slist_remove (ud->mdns->service_list, server);
+				g_mutex_unlock (ud->mdns->mutex);
+				if (ud->mdns->callback)
+					ud->mdns->callback (ud->mdns, G_MDNS_SERVER_REMOVE, server, ud->mdns->user_data);
+				g_mdns_server_destroy (server);
+				g_mutex_lock (ud->mdns->mutex);
+			}
+		}
+		g_mutex_unlock (ud->mdns->mutex);
+	}
+
+}
+
+static void
+g_mdns_server_destroy (GMDNSServer *server)
+{
+	g_return_if_fail (server);
+	if (server->hostname)
+		g_free (server->hostname);
+	if (server->mdnsname)
+		g_free (server->mdnsname);
+	if (server->address)
+		g_free (server->address);
+	if (server->txtvalues)
+		g_hash_table_destroy (server->txtvalues);
+
+	g_free (server);
+}
+
+static gboolean
+g_mdns_source_prepare (GSource *source, gint *timeout_)
+{
+	/* No timeout here */
+	return FALSE;
+}
+
+static gboolean
+g_mdns_source_check (GSource *source)
+{
+	/* Maybe check for errors here? */
+	return TRUE;
+}
+
+static gboolean
+g_mdns_source_dispatch (GSource *source,
+                        GSourceFunc callback,
+                        gpointer user_data)
+{
+	GMDNSUserData *ud = user_data;
+	DNSServiceErrorType err;
+
+	if ((ud->fd->revents & G_IO_ERR) || (ud->fd->revents & G_IO_HUP)) {
+		return FALSE;
+	} else if (ud->fd->revents & G_IO_IN) {
+		err = DNSServiceProcessResult (ud->client);
+		if (err != kDNSServiceErr_NoError) {
+			g_warning ("DNSServiceProcessResult returned error");
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+static GSourceFuncs g_mdns_poll_funcs = {
+	g_mdns_source_prepare,
+	g_mdns_source_check,
+	g_mdns_source_dispatch,
+	NULL
+};
+
+static void
+g_mdns_user_data_destroy (GMDNSUserData *ud)
+{
+	g_return_if_fail (ud);
+
+	g_source_remove_poll (ud->source, ud->fd);
+	g_free (ud->fd);
+	g_source_destroy (ud->source);
+	DNSServiceRefDeallocate (ud->client);
+	g_free (ud);
+}
+
+static gboolean
+g_mdns_poll_add (GMDNS *mdns, GMDNSUserData *ud, DNSServiceRef client)
+{
+	ud->fd = g_new0 (GPollFD, 1);
+	ud->fd->fd = DNSServiceRefSockFD (client);
+	ud->client = client;
+	ud->mdns = mdns;
+
+	if (ud->fd->fd == -1) {
+		g_free (ud->fd);
+		g_free (ud);
+		return FALSE;
+	}
+
+	ud->fd->events = G_IO_IN | G_IO_HUP | G_IO_ERR;
+
+	ud->source = g_source_new (&g_mdns_poll_funcs, sizeof (GSource));
+	g_source_set_callback (ud->source,
+	                       (GSourceFunc) g_mdns_source_dispatch,
+	                       ud, NULL);
+	g_source_add_poll (ud->source, ud->fd);
+	g_source_attach (ud->source, NULL);
+
+	return TRUE;
+}
+
+/**
+ * Browse for a service. The callback will be called
+ * when it's fully resloved and queried
+ */
+static gboolean
+g_mdns_browse (GMDNS *mdns,
+               gchar *service,
+               GMDNSFunc callback,
+               gpointer user_data)
+{
+	DNSServiceErrorType err;
+	DNSServiceRef client;
+	GMDNSUserData *ud;
+
+	g_return_val_if_fail (!mdns->browse_ud, FALSE);
+
+	ud = g_new0 (GMDNSUserData, 1);
+
+	err = DNSServiceBrowse (&client, 0, kDNSServiceInterfaceIndexAny,
+	                        service, 0, browse_reply, ud);
+
+	if (err != kDNSServiceErr_NoError) {
+		g_warning ("Couldn't setup mDNS poller");
+		return FALSE;
+	}
+
+	g_mdns_poll_add (mdns, ud, client);
+
+	mdns->callback = callback;
+	mdns->user_data = user_data;
+	mdns->browse_ud = ud;
+
+	return TRUE;
+}
+
+/**
+ * Remove updates for browsing. Make sure to
+ * call this before you initialize a new browsing
+ */
+static gboolean
+g_mdns_stop_browsing (GMDNS *mdns)
+{
+	g_return_val_if_fail (mdns, FALSE);
+
+	g_mdns_user_data_destroy (mdns->browse_ud);
+	mdns->callback = NULL;
+	mdns->user_data = NULL;
+
+	return TRUE;
+}
+
+/**
+ * Return the full list of services
+ * the list is threadsafe but not the entries
+ * so it might be removed while you using it.
+ */
+GSList *
+daap_mdns_get_server_list ()
+{
+	GSList *ret=NULL, *n;
+	daap_mdns_server_t *server;
+
+	g_mutex_lock (g_mdns->mutex);
+	for (n = g_mdns->service_list; n; n = g_slist_next (n)) {
+		GMDNSServer *s = n->data;
+		server = g_new0 (daap_mdns_server_t, 1);
+		server->mdns_hostname = s->mdnsname;
+		server->server_name = s->hostname;
+		server->port = s->port;
+		server->address = s->address;
+		ret = g_slist_append (ret, server);
+	}
+	g_mutex_unlock (g_mdns->mutex);
+
+	return ret;
+}
+
+/**
+ * Free resources held by GMDNS
+ */
+void
+daap_mdns_destroy ()
+{
+	GSList *n;
+	g_return_if_fail (g_mdns);
+
+	g_mdns_stop_browsing (g_mdns);
+
+	g_mutex_lock (g_mdns->mutex);
+	for (n = g_mdns->service_list; n; n = g_slist_next (n)) {
+		g_mdns_server_destroy (n->data);
+	}
+	g_mutex_unlock (g_mdns->mutex);
+	g_mutex_free (g_mdns->mutex);
+
+	g_free (g_mdns);
+}
+
+gboolean
+daap_mdns_initialize ()
+{
+	g_mdns = g_new0 (GMDNS, 1);
+	g_mdns->mutex = g_mutex_new ();
+	return g_mdns_browse (g_mdns, "_daap._tcp", NULL, NULL);
+}
+