changeset 1407:776dd8fc2b38

DAAP plugin (work in progress)
author Cristi Magherusan <majeru@atheme-project.org>
date Sun, 05 Aug 2007 00:26:21 +0300
parents fe0a9cf95642
children a7db4a8a7b54 7b3290336f3b
files src/daap/Makefile src/daap/daap.c src/daap/xmms2-daap/Makefile src/daap/xmms2-daap/cc_handlers.c src/daap/xmms2-daap/cc_handlers.h src/daap/xmms2-daap/daap_cmd.c src/daap/xmms2-daap/daap_cmd.h src/daap/xmms2-daap/daap_conn.c src/daap/xmms2-daap/daap_conn.h src/daap/xmms2-daap/daap_md5.c src/daap/xmms2-daap/daap_md5.h src/daap/xmms2-daap/daap_mdns_avahi.c src/daap/xmms2-daap/daap_mdns_browse.h src/daap/xmms2-daap/daap_mdns_dnssd.c src/daap/xmms2-daap/daap_mdns_dummy.c src/daap/xmms2-daap/daap_util.c src/daap/xmms2-daap/daap_util.h src/daap/xmms2-daap/daap_xform.c src/daap/xmms2-daap/wscript
diffstat 19 files changed, 3923 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/daap/Makefile	Sun Aug 05 00:26:21 2007 +0300
@@ -0,0 +1,24 @@
+include ../../mk/rules.mk
+include ../../mk/init.mk
+
+OBJECTIVE_LIBS = libdaap$(SHARED_SUFFIX)
+
+SUBDIRS = xmms2-daap
+
+LIBDIR = $(plugindir)/$(TRANSPORT_PLUGIN_DIR)
+
+LIBADD += ./xmms2-daap/xmms2-daap.a $(GTK_LIBS) $(GLIB_LIBS) $(PANGO_LIBS) 
+
+SOURCES = daap.c
+
+OBJECTS = ${SOURCES:.c=.o}
+
+LIBDEP = ./xmms2-daap/xmms2-daap.a
+
+INC = -I../.. -I./xmms2-daap
+
+WARN = -Wall  -pedantic -std=c99 
+
+CFLAGS += $(PICFLAGS) $(GTK_CFLAGS) $(GLIB_CFLAGS) $(PANGO_CFLAGS) $(INC) $(WARN)  $(GCC42_CFLAGS)
+
+include ../../mk/objective.mk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/daap/daap.c	Sun Aug 05 00:26:21 2007 +0300
@@ -0,0 +1,129 @@
+/*  Audacious DAAP transport plugin
+ *  Copyright (c) 2007 Cristi Magherusan <majeru@gentoo.ro>
+ *      
+ *  With inspiration and code from David Hammerton's tunesbrowser
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Softmcware
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <audacious/vfs.h>
+#include <audacious/plugin.h>
+/*
+#include <audacious/configdb.h>
+#include <libmowgli/mowgli.h>
+#include <curl/curl.h>
+*/
+#include <glib.h>
+#include <daap/client.h>
+
+
+
+
+
+
+DAAP_SClientHost *libopendaap_host;
+
+
+VFSFile * daap_vfs_fopen_impl(const gchar * path, const gchar * mode)
+{
+    VFSFile *file;
+    file = g_new0(VFSFile, 1);
+    /* connectiong daap*/
+
+
+    return file;
+}
+
+gint daap_vfs_fclose_impl(VFSFile * file)
+{
+    return 0;
+}
+size_t daap_vfs_fread_impl(gpointer ptr, size_t size, size_t nmemb, VFSFile * file)
+{
+    return 0;
+}
+
+size_t daap_vfs_fwrite_impl(gconstpointer ptr, size_t size, size_t nmemb, VFSFile * file)
+{
+    return -1;
+}
+
+gint daap_vfs_getc_impl(VFSFile * stream)
+{
+    return 0;
+}
+
+gint daap_vfs_ungetc_impl(gint c, VFSFile * stream)
+{
+    return 0;
+}
+gint daap_vfs_fseek_impl(VFSFile * file, glong offset, gint whence)
+{
+    return -1;
+}
+
+void daap_vfs_rewind_impl(VFSFile * file)
+{
+    return;
+}
+
+glong daap_vfs_ftell_impl(VFSFile * file)
+{
+    return 0;
+}
+
+gboolean daap_vfs_feof_impl(VFSFile * file)
+{
+    return 1;
+}
+
+gint daap_vfs_truncate_impl(VFSFile * file, glong size)
+{
+    return -1;
+}
+off_t daap_vfs_fsize_impl(VFSFile * file)
+{
+    return 0;
+}
+gchar *daap_vfs_metadata_impl(VFSFile * file, const gchar * field)
+{
+    return NULL;
+}
+
+VFSConstructor daap_const = {
+    "daap://",
+    daap_vfs_fopen_impl,
+    daap_vfs_fclose_impl,
+    daap_vfs_fread_impl,
+    daap_vfs_fwrite_impl,
+    daap_vfs_getc_impl,
+    daap_vfs_ungetc_impl,
+    daap_vfs_fseek_impl,
+    daap_vfs_rewind_impl,
+    daap_vfs_ftell_impl,
+    daap_vfs_feof_impl,
+    daap_vfs_truncate_impl,
+    daap_vfs_fsize_impl,
+    daap_vfs_metadata_impl
+};
+
+static void init(void)
+{       
+    vfs_register_transport(&daap_const);
+}
+static void cleanup(void)
+{
+}
+DECLARE_PLUGIN(daap, init, cleanup, NULL, NULL, NULL, NULL, NULL, NULL)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/daap/xmms2-daap/Makefile	Sun Aug 05 00:26:21 2007 +0300
@@ -0,0 +1,24 @@
+include ../../../mk/rules.mk
+include ../../../mk/init.mk
+
+OBJECTIVE_LIBS_NOINST= xmms2-daap.a
+
+SOURCES= cc_handlers.c \
+		 daap_conn.c \
+		 daap_cmd.c \
+		 daap_md5.c \
+		 daap_util.c \
+		 daap_mdns_dnssd.c \
+		 daap_mdns_avahi.c \
+		 daap_mdns_dummy.c 
+
+#		 daap_xform.c 
+LIBADD = $(GLIB_LIBS) 
+ 
+
+CFLAGS = $(GLIB_CFLAGS)  -I/usr/include   -std=c99 -Wall # -H -v
+
+#CFLAGS += $(PICFLAGS) $(ARCH_DEFINES) $(CURL_CFLAGS) -c -I../../../intl -I../../.. -I/usr/include -Wall   -std=c99 -H -v
+OBJECTS=${SOURCES:.c=.o}
+
+include ../../../mk/objective.mk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/daap/xmms2-daap/cc_handlers.c	Sun Aug 05 00:26:21 2007 +0300
@@ -0,0 +1,892 @@
+/** @file cc_handlers.c
+ *  Functions for parsing DAAP content code data.
+ *
+ *  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.
+ */
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <stdlib.h>
+
+#include <glib.h>
+#include <glib/gprintf.h>
+
+#include "cc_handlers.h"
+#include "daap_conn.h"
+
+#define DMAP_BYTES_REMAINING ((gint) (data_end - current_data))
+
+static void
+endian_swap_int16 (gint16 *i)
+{
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+	gint16 tmp;
+	tmp = (gint16) (((*i >>  8) & 0x00FF) |
+	                ((*i <<  8) & 0xFF00));
+	*i = tmp;
+#endif
+}
+
+static void
+endian_swap_int32 (gint32 *i)
+{
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+	gint32 tmp;
+	tmp = (gint32) (((*i >> 24) & 0x000000FF) |
+	                ((*i >>  8) & 0x0000FF00) |
+	                ((*i <<  8) & 0x00FF0000) |
+	                ((*i << 24) & 0xFF000000));
+	*i = tmp;
+#endif
+}
+
+static void
+endian_swap_int64 (gint64 *i)
+{
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+	gint64 tmp;
+	tmp = (gint64) (                 ((*i >> 40) & 0x00000000000000FF) |
+	                                 ((*i >> 32) & 0x000000000000FF00) |
+	                                 ((*i >> 24) & 0x0000000000FF0000) |
+	                                 ((*i >>  8) & 0x00000000FF000000) |
+	               G_GINT64_CONSTANT ((*i <<  8) & 0x000000FF00000000) |
+	               G_GINT64_CONSTANT ((*i << 24) & 0x0000FF0000000000) |
+	               G_GINT64_CONSTANT ((*i << 32) & 0x00FF000000000000) |
+	               G_GINT64_CONSTANT ((*i << 40) & 0xFF00000000000000));
+	*i = tmp;
+#endif
+}
+
+static gint
+grab_data_string (gchar **container, gchar *data, gint str_len)
+{
+	gint offset = 0;
+
+	if (0 != str_len) {
+		*container = (gchar *) malloc (sizeof (gchar) * (str_len+1));
+
+		memcpy (*container, data + DMAP_CC_SZ + DMAP_INT_SZ, str_len);
+		(*container)[str_len] = '\0';
+
+		offset += str_len;
+	}
+
+	return offset;
+}
+
+static gint
+grab_data_version (gint16 *cont_upper, gint16 *cont_lower, gchar *data)
+{
+	gint offset = DMAP_CC_SZ;
+
+	memcpy (cont_lower, data + offset, DMAP_INT_SZ);
+	endian_swap_int16 (cont_lower);
+	offset += DMAP_INT_SZ;
+
+	memcpy (cont_upper, data + offset, DMAP_INT_SZ);
+	endian_swap_int16 (cont_upper);
+	offset += DMAP_INT_SZ;
+
+	return offset;
+}
+
+static gint
+grab_data (void *container, gchar *data, content_type ct)
+{
+	gint offset;
+	gint data_size;
+
+	offset = DMAP_CC_SZ;
+	memcpy (&data_size, data + offset, DMAP_INT_SZ);
+	endian_swap_int32 ((gint32 *)&data_size);
+	offset += DMAP_INT_SZ;
+
+	switch (ct) {
+		case DMAP_CTYPE_BYTE:
+		case DMAP_CTYPE_UNSIGNEDBYTE:
+			memcpy (container, data + offset, DMAP_BYTE_SZ);
+			offset += DMAP_BYTE_SZ;
+			break;
+		case DMAP_CTYPE_SHORT:
+		case DMAP_CTYPE_UNSIGNEDSHORT:
+			memcpy (container, data + offset, DMAP_SHORT_SZ);
+			endian_swap_int16 (container);
+			offset += DMAP_SHORT_SZ;
+			break;
+		case DMAP_CTYPE_INT:
+		case DMAP_CTYPE_UNSIGNEDINT:
+			memcpy (container, data + offset, DMAP_INT_SZ);
+			endian_swap_int32 (container);
+			offset += DMAP_INT_SZ;
+			break;
+		case DMAP_CTYPE_LONG:
+		case DMAP_CTYPE_UNSIGNEDLONG:
+			memcpy (container, data + offset, DMAP_LONG_SZ);
+			endian_swap_int64 (container);
+			offset += DMAP_LONG_SZ;
+			break;
+		case DMAP_CTYPE_STRING:
+			offset += grab_data_string ((gchar **) container, data, data_size);
+			break;
+		case DMAP_CTYPE_DATE:
+			memcpy (container, data + offset, DMAP_INT_SZ);
+			endian_swap_int32 (container);
+			offset += DMAP_INT_SZ;
+			break;
+		default:
+			g_print ("Warning: Unrecognized content type (%d).\n", ct);
+			break;
+	}
+
+	return offset;
+}
+
+static gint
+cc_handler_mtco (cc_data_t *fields, gchar *current_data)
+{
+	gint offset = grab_data (&(fields->n_rec_matches), current_data, DMAP_CTYPE_INT);
+	return offset;
+}
+
+static gint
+cc_handler_mrco (cc_data_t *fields, gchar *current_data)
+{
+	gint offset = grab_data (&(fields->n_ret_items), current_data, DMAP_CTYPE_INT);
+	return offset;
+}
+
+static gint
+cc_handler_muty (cc_data_t *fields, gchar *current_data)
+{
+	gint offset = grab_data (&(fields->updt_type), current_data, DMAP_CTYPE_BYTE);
+	return offset;
+}
+
+static gint
+cc_handler_mstt (cc_data_t *fields, gchar *current_data)
+{
+	gint offset = grab_data (&(fields->status), current_data, DMAP_CTYPE_INT);
+	return offset;
+}
+
+static gint
+cc_handler_mlit (cc_data_t *fields, gchar *data, gint data_len)
+{
+	gint offset = 0;
+	gboolean do_break = FALSE;
+	gchar *current_data, *data_end;
+	cc_item_record_t *item_fields;
+
+	current_data = data + 8;
+	data_end = data + data_len;
+
+	item_fields = g_malloc0 (sizeof (cc_item_record_t));
+
+	while (current_data < data_end && !do_break) {
+		switch (CC_TO_INT (current_data[0], current_data[1],
+		                   current_data[2], current_data[3])) {
+			case CC_TO_INT ('m','i','i','d'):
+				offset += grab_data (&(item_fields->dbid), current_data,
+				                    DMAP_CTYPE_INT);
+				break;
+			case CC_TO_INT ('m','p','e','r'):
+				offset += grab_data (&(item_fields->persistent_id), current_data,
+				                    DMAP_CTYPE_LONG);
+				break;
+			case CC_TO_INT ('m','i','n','m'):
+				offset += grab_data (&(item_fields->iname), current_data,
+				                    DMAP_CTYPE_STRING);
+				break;
+
+			/* song list specific */
+			case CC_TO_INT ('m','i','k','d'):
+				offset += grab_data (&(item_fields->item_kind), current_data,
+				                    DMAP_CTYPE_BYTE);
+				break;
+			case CC_TO_INT ('a','s','d','k'):
+				offset += grab_data (&(item_fields->song_data_kind), current_data,
+				                    DMAP_CTYPE_BYTE);
+				break;
+			case CC_TO_INT ('a','s','u','l'):
+				offset += grab_data (&(item_fields->song_data_url), current_data,
+				                    DMAP_CTYPE_STRING);
+				break;
+			case CC_TO_INT ('a','s','a','l'):
+				offset += grab_data (&(item_fields->song_data_album),
+				                    current_data,
+				                    DMAP_CTYPE_STRING);
+				break;
+			case CC_TO_INT ('a','s','a','r'):
+				offset += grab_data (&(item_fields->song_data_artist),
+				                    current_data,
+				                    DMAP_CTYPE_STRING);
+				break;
+			case CC_TO_INT ('a','s','b','r'):
+				offset += grab_data (&(item_fields->song_bitrate), current_data,
+				                    DMAP_CTYPE_SHORT);
+				break;
+			case CC_TO_INT ('a','s','c','m'):
+				offset += grab_data (&(item_fields->song_comment), current_data,
+				                    DMAP_CTYPE_STRING);
+				break;
+			case CC_TO_INT ('a','s','d','a'):
+				offset += grab_data (&(item_fields->song_date), current_data,
+				                    DMAP_CTYPE_DATE);
+				break;
+			case CC_TO_INT ('a','s','d','m'):
+				offset += grab_data (&(item_fields->song_date_mod), current_data,
+				                    DMAP_CTYPE_DATE);
+				break;
+			case CC_TO_INT ('a','s','g','n'):
+				offset += grab_data (&(item_fields->song_genre), current_data,
+				                    DMAP_CTYPE_STRING);
+				break;
+			case CC_TO_INT ('a','s','f','m'):
+				offset += grab_data (&(item_fields->song_format), current_data,
+				                    DMAP_CTYPE_STRING);
+				break;
+			case CC_TO_INT ('a','s','d','t'):
+				offset += grab_data (&(item_fields->song_description), current_data,
+				                    DMAP_CTYPE_STRING);
+				break;
+			case CC_TO_INT ('a','s','s','r'):
+				offset += grab_data (&(item_fields->sample_rate), current_data,
+				                    DMAP_CTYPE_INT);
+				break;
+			case CC_TO_INT ('a','s','s','z'):
+				offset += grab_data (&(item_fields->song_size), current_data,
+				                    DMAP_CTYPE_INT);
+				break;
+			case CC_TO_INT ('a','s','s','t'):
+				offset += grab_data (&(item_fields->song_start_time), current_data,
+				                    DMAP_CTYPE_INT);
+				break;
+			case CC_TO_INT ('a','s','s','p'):
+				offset += grab_data (&(item_fields->song_stop_time), current_data,
+				                    DMAP_CTYPE_INT);
+				break;
+			case CC_TO_INT ('a','s','t','m'):
+				offset += grab_data (&(item_fields->song_total_time), current_data,
+				                    DMAP_CTYPE_INT);
+				break;
+			case CC_TO_INT ('a','s','y','r'):
+				offset += grab_data (&(item_fields->song_year), current_data,
+				                    DMAP_CTYPE_SHORT);
+				break;
+			case CC_TO_INT ('a','s','t','n'):
+				offset += grab_data (&(item_fields->song_track_no), current_data,
+				                    DMAP_CTYPE_SHORT);
+				break;
+			case CC_TO_INT ('a','s','c','p'):
+				offset += grab_data (&(item_fields->song_composer), current_data,
+				                    DMAP_CTYPE_STRING);
+				break;
+			case CC_TO_INT ('a','s','t','c'):
+				offset += grab_data (&(item_fields->song_track_count), current_data,
+				                    DMAP_CTYPE_SHORT);
+				break;
+			case CC_TO_INT ('a','s','d','c'):
+				offset += grab_data (&(item_fields->song_disc_count), current_data,
+				                    DMAP_CTYPE_SHORT);
+				break;
+			case CC_TO_INT ('a','s','d','n'):
+				offset += grab_data (&(item_fields->song_disc_no), current_data,
+				                    DMAP_CTYPE_SHORT);
+				break;
+			case CC_TO_INT ('a','s','c','o'):
+				offset += grab_data (&(item_fields->song_compilation), current_data,
+				                    DMAP_CTYPE_BYTE);
+				break;
+			case CC_TO_INT ('a','s','b','t'):
+				offset += grab_data (&(item_fields->song_bpm), current_data,
+				                    DMAP_CTYPE_SHORT);
+				break;
+			case CC_TO_INT ('a','g','r','p'):
+				offset += grab_data (&(item_fields->song_grouping), current_data,
+				                    DMAP_CTYPE_STRING);
+				break;
+			case CC_TO_INT ('m','c','t','i'):
+				offset += grab_data (&(item_fields->container_id), current_data,
+				                    DMAP_CTYPE_INT);
+				break;
+			case CC_TO_INT ('m','u','d','l'):
+				offset += grab_data (&(item_fields->deleted_id), current_data,
+				                    DMAP_CTYPE_INT);
+				break;
+
+			/* db list specific */
+			case CC_TO_INT ('m','i','m','c'):
+				offset += grab_data (&(item_fields->db_n_items), current_data,
+				                    DMAP_CTYPE_INT);
+				break;
+			case CC_TO_INT ('m','c','t','c'):
+				offset += grab_data (&(item_fields->db_n_playlist), current_data,
+				                    DMAP_CTYPE_INT);
+				break;
+			case CC_TO_INT ('a','e','S','P'):
+				offset += grab_data (&(item_fields->is_smart_pl), current_data,
+				                    DMAP_CTYPE_BYTE);
+				break;
+			case CC_TO_INT ('a','b','p','l'):
+				offset += grab_data (&(item_fields->is_base_pl), current_data,
+				                    DMAP_CTYPE_BYTE);
+				break;
+
+			/* exit conditions */
+			case CC_TO_INT ('m','l','i','t'):
+				do_break = TRUE;
+				break;
+			default:
+				g_print ("Warning: Unrecognized content code "
+				          "or end of data: %s\n", current_data);
+				do_break = TRUE;
+				break;
+		}
+
+		current_data += offset;
+		offset = 0;
+	}
+
+	fields->record_list = g_slist_prepend (fields->record_list, item_fields);
+
+	return (gint) (current_data - data);
+}
+
+static gint
+cc_handler_mlcl (cc_data_t *fields, gchar *data, gint data_len)
+{
+	gint offset = 0;
+	gboolean do_break = FALSE;
+	gchar *current_data, *data_end;
+	current_data = data + 8;
+	data_end = data + data_len;
+
+	while (current_data < data_end && !do_break) {
+		if (CC_TO_INT (current_data[0], current_data[1],
+		               current_data[2], current_data[3]) ==
+		    CC_TO_INT ('m','l','i','t')) {
+
+			offset += cc_handler_mlit (fields, current_data,
+			                           DMAP_BYTES_REMAINING);
+		} else {
+			break;
+		}
+
+		current_data += offset;
+		offset = 0;
+	}
+
+	return (gint)(current_data - data);
+}
+
+static cc_data_t *
+cc_handler_adbs (gchar *data, gint data_len)
+{
+	gint offset = 0;
+	gboolean do_break = FALSE;
+	gchar *current_data, *data_end;
+	cc_data_t *fields;
+
+	current_data = data + 8;
+	data_end = data + data_len;
+
+	fields = cc_data_new ();
+
+	while ((current_data < data_end) && !do_break) {
+		switch (CC_TO_INT (current_data[0], current_data[1],
+		                   current_data[2], current_data[3])) {
+			case CC_TO_INT ('m','s','t','t'):
+				offset += cc_handler_mstt (fields, current_data);
+				break;
+			case CC_TO_INT ('m','u','t','y'):
+				offset += cc_handler_muty (fields, current_data);
+				break;
+			case CC_TO_INT ('m','t','c','o'):
+				offset += cc_handler_mtco (fields, current_data);
+				break;
+			case CC_TO_INT ('m','r','c','o'):
+				offset += cc_handler_mrco (fields, current_data);
+				break;
+			case CC_TO_INT ('m','l','c','l'):
+				offset += cc_handler_mlcl (fields, current_data,
+				                           DMAP_BYTES_REMAINING);
+				break;
+			default:
+				do_break = TRUE;
+				break;
+		}
+
+		current_data += offset;
+		offset = 0;
+	}
+
+	return fields;
+}
+
+static cc_data_t *
+cc_handler_msrv (gchar *data, gint data_len)
+{
+	gint offset = 0;
+	gboolean do_break = FALSE;
+	gchar *current_data, *data_end;
+	cc_data_t *fields;
+	current_data = data + 8;
+	data_end = data + data_len;
+
+	fields = cc_data_new ();
+
+	while ((current_data < data_end) && !do_break) {
+		switch (CC_TO_INT (current_data[0], current_data[1],
+		                   current_data[2], current_data[3])) {
+			case CC_TO_INT ('m','s','t','t'):
+				offset += cc_handler_mstt (fields, current_data);
+				break;
+			case CC_TO_INT ('a','p','r','o'):
+				offset += grab_data_version (&(fields->daap_proto_major),
+				                            &(fields->daap_proto_minor),
+				                            current_data);
+				break;
+			case CC_TO_INT ('m','p','r','o'):
+				offset += grab_data_version (&(fields->dmap_proto_major),
+				                            &(fields->dmap_proto_minor),
+				                            current_data);
+				break;
+			case CC_TO_INT ('m','s','t','m'):
+				offset += grab_data (&(fields->timeout_interval), current_data,
+				                    DMAP_CTYPE_INT);
+				break;
+			case CC_TO_INT ('m','s','i','x'):
+				offset += grab_data (&(fields->has_indexing), current_data,
+				                    DMAP_CTYPE_BYTE);
+				break;
+			case CC_TO_INT ('m','s','e','x'):
+				offset += grab_data (&(fields->has_extensions), current_data,
+				                    DMAP_CTYPE_BYTE);
+				break;
+			case CC_TO_INT ('m','s','u','p'):
+				offset += grab_data (&(fields->has_update), current_data,
+				                    DMAP_CTYPE_BYTE);
+				break;
+			case CC_TO_INT ('m','s','a','l'):
+				offset += grab_data (&(fields->has_autologout), current_data,
+				                    DMAP_CTYPE_BYTE);
+				break;
+			case CC_TO_INT ('m','s','l','r'):
+				offset += grab_data (&(fields->login_required), current_data,
+				                    DMAP_CTYPE_BYTE);
+				break;
+			case CC_TO_INT ('m','s','q','y'):
+				offset += grab_data (&(fields->has_queries), current_data,
+				                    DMAP_CTYPE_BYTE);
+				break;
+			case CC_TO_INT ('m','s','r','s'):
+				offset += grab_data (&(fields->has_resolve), current_data,
+				                    DMAP_CTYPE_BYTE);
+				break;
+			case CC_TO_INT ('m','s','b','r'):
+				offset += grab_data (&(fields->has_browsing), current_data,
+				                    DMAP_CTYPE_BYTE);
+				break;
+			case CC_TO_INT ('m','s','p','i'):
+				offset += grab_data (&(fields->has_persistent), current_data,
+				                    DMAP_CTYPE_BYTE);
+				break;
+			case CC_TO_INT ('m','s','a','s'):
+				offset += grab_data (&(fields->auth_type), current_data,
+				                    DMAP_CTYPE_BYTE);
+				break;
+			case CC_TO_INT ('m','s','a','u'):
+				offset += grab_data (&(fields->auth_method), current_data,
+				                    DMAP_CTYPE_BYTE);
+				break;
+			case CC_TO_INT ('a','e','S','V'):
+				offset += grab_data (&(fields->sharing_version), current_data,
+				                    DMAP_CTYPE_INT);
+				break;
+			case CC_TO_INT ('m','s','d','c'):
+				offset += grab_data (&(fields->db_count), current_data,
+				                    DMAP_CTYPE_INT);
+				break;
+			case CC_TO_INT ('m','i','n','m'):
+				offset += grab_data (&(fields->server_name), current_data,
+				                    DMAP_CTYPE_STRING);
+				break;
+			default:
+				g_print ("Warning: Unrecognized content code "
+				          "or end of data: %s\n",
+				          current_data);
+				do_break = TRUE;
+				break;
+		}
+
+		current_data += offset;
+		offset = 0;
+	}
+
+	return fields;
+}
+
+static cc_data_t *
+cc_handler_mccr (gchar *data, gint data_len)
+{
+	/* not implemented */
+	return NULL;
+}
+
+static cc_data_t *
+cc_handler_mlog (gchar *data, gint data_len)
+{
+	gint offset = 0;
+	gboolean do_break = FALSE;
+	gchar *current_data, *data_end;
+	cc_data_t *fields;
+
+	current_data = data + 8;
+	data_end = data + data_len;
+
+	fields = cc_data_new ();
+
+	while ((current_data < data_end) && !do_break) {
+		switch (CC_TO_INT (current_data[0], current_data[1],
+		                   current_data[2], current_data[3])) {
+			case CC_TO_INT ('m','s','t','t'):
+				offset += cc_handler_mstt (fields, current_data);
+				break;
+			case CC_TO_INT ('m','l','i','d'):
+				offset += grab_data (&(fields->session_id), current_data, DMAP_CTYPE_INT);
+				break;
+			default:
+				g_print ("Unrecognized content code or end of data: %s\n",
+				          current_data);
+				do_break = TRUE;
+				break;
+		}
+
+		current_data += offset;
+		offset = 0;
+	}
+
+	return fields;
+}
+
+static cc_data_t *
+cc_handler_mupd (gchar *data, gint data_len)
+{
+	gint offset = 0;
+	gboolean do_break = FALSE;
+	gchar *current_data, *data_end;
+	cc_data_t *fields;
+
+	current_data = data + 8;
+	data_end = data + data_len;
+
+	fields = cc_data_new ();
+
+	while ((current_data < data_end) && !do_break) {
+		switch (CC_TO_INT (current_data[0], current_data[1],
+		                   current_data[2], current_data[3])) {
+			case CC_TO_INT ('m','u','s','r'):
+				offset += grab_data (&(fields->revision_id), current_data,
+				                    DMAP_CTYPE_INT);
+				break;
+			case CC_TO_INT ('m','s','t','t'):
+				offset += cc_handler_mstt (fields, current_data);
+				break;
+			default:
+				g_print ("Unrecognized content code or end of data: %s\n",
+				          current_data);
+				do_break = TRUE;
+				break;
+		}
+
+		current_data += offset;
+		offset = 0;
+	}
+
+	return fields;
+}
+
+static cc_data_t *
+cc_handler_avdb (gchar *data, gint data_len)
+{
+	gint offset = 0;
+	gboolean do_break = FALSE;
+	gchar *current_data, *data_end;
+	cc_data_t *fields;
+
+	current_data = data + 8;
+	data_end = data + data_len;
+
+	fields = cc_data_new ();
+
+	while ((current_data < data_end) && !do_break) {
+		switch (CC_TO_INT (current_data[0], current_data[1],
+		                   current_data[2], current_data[3])) {
+			case CC_TO_INT ('m','s','t','t'):
+				offset += cc_handler_mstt (fields, current_data);
+				break;
+			case CC_TO_INT ('m','u','t','y'):
+				offset += cc_handler_muty (fields, current_data);
+				break;
+			case CC_TO_INT ('m','t','c','o'):
+				offset += cc_handler_mtco (fields, current_data);
+				break;
+			case CC_TO_INT ('m','r','c','o'):
+				offset += cc_handler_mrco (fields, current_data);
+				break;
+			case CC_TO_INT ('m','l','c','l'):
+				offset += cc_handler_mlcl (fields, current_data,
+				                           DMAP_BYTES_REMAINING);
+				break;
+			default:
+				do_break = TRUE;
+				break;
+		}
+
+		current_data += offset;
+		offset = 0;
+	}
+
+	return fields;
+}
+
+static cc_data_t *
+cc_handler_apso (gchar *data, gint data_len)
+{
+	gint offset = 0;
+	gboolean do_break = FALSE;
+	gchar *current_data, *data_end;
+	cc_data_t *fields;
+
+	current_data = data + 8;
+	data_end = data + data_len;
+
+	fields = cc_data_new ();
+
+	while ((current_data < data_end) && !do_break) {
+		switch (CC_TO_INT (current_data[0], current_data[1],
+		                   current_data[2], current_data[3])) {
+			case CC_TO_INT ('m','s','t','t'):
+				offset += cc_handler_mstt (fields, current_data);
+				break;
+			case CC_TO_INT ('m','u','t','y'):
+				offset += cc_handler_muty (fields, current_data);
+				break;
+			case CC_TO_INT ('m','t','c','o'):
+				offset += cc_handler_mtco (fields, current_data);
+				break;
+			case CC_TO_INT ('m','r','c','o'):
+				offset += cc_handler_mrco (fields, current_data);
+				break;
+			case CC_TO_INT ('m','l','c','l'):
+				offset += cc_handler_mlcl (fields, current_data,
+				                           DMAP_BYTES_REMAINING);
+				break;
+			default:
+				do_break = TRUE;
+				break;
+		}
+
+		current_data += offset;
+		offset = 0;
+	}
+
+	return fields;
+}
+
+static cc_data_t *
+cc_handler_aply (gchar *data, gint data_len)
+{
+	gint offset = 0;
+	gboolean do_break = FALSE;
+	gchar *current_data, *data_end;
+	cc_data_t *fields;
+
+	current_data = data + 8;
+	data_end = data + data_len;
+
+	fields = cc_data_new ();
+
+	while ((current_data < data_end) && !do_break) {
+		switch (CC_TO_INT (current_data[0], current_data[1],
+		                   current_data[2], current_data[3])) {
+			case CC_TO_INT ('m','s','t','t'):
+				offset += cc_handler_mstt (fields, current_data);
+				break;
+			case CC_TO_INT ('m','u','t','y'):
+				offset += cc_handler_muty (fields, current_data);
+				break;
+			case CC_TO_INT ('m','t','c','o'):
+				offset += cc_handler_mtco (fields, current_data);
+				break;
+			case CC_TO_INT ('m','r','c','o'):
+				offset += cc_handler_mrco (fields, current_data);
+				break;
+			case CC_TO_INT ('m','l','c','l'):
+				offset += cc_handler_mlcl (fields, current_data,
+				                           DMAP_BYTES_REMAINING);
+				break;
+			default:
+				do_break = TRUE;
+				break;
+		}
+
+		current_data += offset;
+		offset = 0;
+	}
+
+	return fields;
+}
+
+cc_data_t *
+cc_data_new ()
+{
+	cc_data_t *retval;
+
+	retval = g_malloc0 (sizeof (cc_data_t));
+	retval->record_list = NULL;
+
+	return retval;
+}
+
+void
+cc_data_free (cc_data_t *fields)
+{
+	if (NULL != fields->server_name) g_free (fields->server_name);
+
+	g_slist_foreach (fields->record_list,
+	                 (GFunc) cc_item_record_free,
+	                 NULL);
+	g_slist_free (fields->record_list);
+
+	g_free (fields);
+}
+
+void
+cc_item_record_free (cc_item_record_t *item)
+{
+	if (NULL != item->iname)            g_free (item->iname);
+	if (NULL != item->song_data_url)    g_free (item->song_data_url);
+	if (NULL != item->song_data_album)  g_free (item->song_data_album);
+	if (NULL != item->song_data_artist) g_free (item->song_data_artist);
+	if (NULL != item->song_comment)     g_free (item->song_comment);
+	if (NULL != item->song_description) g_free (item->song_description);
+	if (NULL != item->song_genre)       g_free (item->song_genre);
+	if (NULL != item->song_format)      g_free (item->song_format);
+	if (NULL != item->song_composer)    g_free (item->song_composer);
+	if (NULL != item->song_grouping)    g_free (item->song_grouping);
+
+	g_free (item);
+}
+
+GSList *
+cc_record_list_deep_copy (GSList *record_list) {
+	GSList *retval = NULL;
+	cc_item_record_t *record, *data;
+
+	for (; record_list; record_list = g_slist_next (record_list)) {
+		data = record_list->data;
+		record = g_malloc0 (sizeof (cc_item_record_t));
+		if (!record) {
+			g_print ("memory allocation failed for cc_record_list_deep_copy\n");
+			return NULL;
+		}
+
+		record->item_kind        = data->item_kind;
+		record->song_data_kind   = data->song_data_kind;
+		record->song_compilation = data->song_compilation;
+		record->is_smart_pl      = data->is_smart_pl;
+		record->is_base_pl       = data->is_base_pl;
+
+		record->song_bitrate     = data->song_bitrate;
+		record->song_year        = data->song_year;
+		record->song_track_no    = data->song_track_no;
+		record->song_track_count = data->song_track_count;
+		record->song_disc_count  = data->song_disc_count;
+		record->song_disc_no     = data->song_disc_no;
+		record->song_bpm         = data->song_bpm;
+
+		record->dbid             = data->dbid;
+		record->sample_rate      = data->sample_rate;
+		record->song_size        = data->song_size;
+		record->song_start_time  = data->song_start_time;
+		record->song_stop_time   = data->song_stop_time;
+		record->song_total_time  = data->song_total_time;
+		record->song_date        = data->song_date;
+		record->song_date_mod    = data->song_date_mod;
+		record->container_id     = data->container_id;
+
+		record->deleted_id       = data->deleted_id;
+
+		record->persistent_id    = data->persistent_id;
+
+		record->iname            = g_strdup (data->iname);
+		record->song_data_url    = g_strdup (data->song_data_url);
+		record->song_data_album  = g_strdup (data->song_data_album);
+		record->song_data_artist = g_strdup (data->song_data_artist);
+		record->song_comment     = g_strdup (data->song_comment);
+		record->song_description = g_strdup (data->song_description);
+		record->song_genre       = g_strdup (data->song_genre);
+		record->song_format      = g_strdup (data->song_format);
+		record->song_composer    = g_strdup (data->song_composer);
+		record->song_grouping    = g_strdup (data->song_grouping);
+
+		/* db list specific */
+		record->db_n_items       = data->db_n_items;
+		record->db_n_playlist    = data->db_n_playlist;
+
+		retval = g_slist_prepend (retval, record);
+	}
+
+	return retval;
+}
+
+cc_data_t *
+cc_handler (gchar *data, gint data_len)
+{
+	cc_data_t *retval;
+
+	switch (CC_TO_INT (data[0],data[1],data[2],data[3])) {
+		case CC_TO_INT ('a','d','b','s'):
+			retval = cc_handler_adbs (data, data_len);
+			break;
+		case CC_TO_INT ('m','s','r','v'):
+			retval = cc_handler_msrv (data, data_len);
+			break;
+		case CC_TO_INT ('m','c','c','r'):
+			retval = cc_handler_mccr (data, data_len);
+			break;
+		case CC_TO_INT ('m','l','o','g'):
+			retval = cc_handler_mlog (data, data_len);
+			break;
+		case CC_TO_INT ('m','u','p','d'):
+			retval = cc_handler_mupd (data, data_len);
+			break;
+		case CC_TO_INT ('a','v','d','b'):
+			retval = cc_handler_avdb (data, data_len);
+			break;
+		case CC_TO_INT ('a','p','s','o'):
+			retval = cc_handler_apso (data, data_len);
+			break;
+		case CC_TO_INT ('a','p','l','y'):
+			retval = cc_handler_aply (data, data_len);
+			break;
+		default:
+			retval = NULL;
+			break;
+	}
+
+	return retval;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/daap/xmms2-daap/cc_handlers.h	Sun Aug 05 00:26:21 2007 +0300
@@ -0,0 +1,174 @@
+/** @file cc_handlers.h
+ *
+ *  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.
+ */
+
+#ifndef CC_HANDLERS_H
+#define CC_HANDLERS_H
+
+#include <glib.h>
+
+#define CC_TO_INT(a,b,c,d) ((gint) ((a << 24) | \
+                                    (b << 16) | \
+                                    (c <<  8) | \
+                                    (d      )    ))
+
+#define DMAP_CC_SZ              (sizeof (gchar) * 4)
+#define DMAP_BYTE_SZ            sizeof (gint8)
+#define DMAP_SHORT_SZ           sizeof (gint16)
+#define DMAP_INT_SZ             sizeof (gint32)
+#define DMAP_LONG_SZ            sizeof (gint64)
+#define DMAP_VERSION_SZ         sizeof (gint16)
+
+#define DMAP_UNKNOWN_CC -1
+
+typedef enum {
+	DMAP_CTYPE_BYTE          = 1,
+	/* unconfirmed */
+	DMAP_CTYPE_UNSIGNEDBYTE  = 2,
+
+	DMAP_CTYPE_SHORT         = 3,
+	/* unconfirmed */
+	DMAP_CTYPE_UNSIGNEDSHORT = 4,
+
+	DMAP_CTYPE_INT           = 5,
+	/* unconfirmed */
+	DMAP_CTYPE_UNSIGNEDINT   = 6,
+
+	DMAP_CTYPE_LONG          = 7,
+	/* unconfirmed */
+	DMAP_CTYPE_UNSIGNEDLONG  = 8,
+	
+	DMAP_CTYPE_STRING        = 9,
+	DMAP_CTYPE_DATE          = 10,
+	DMAP_CTYPE_VERSION       = 11,
+	DMAP_CTYPE_LIST          = 12,
+} content_type;
+
+typedef struct {
+	/* items common to more than one type */
+	gint8 updt_type;
+
+	gint32 n_rec_matches;
+	gint32 n_ret_items;
+	gint32 status;
+
+	GSList *record_list;
+
+	/* msrv - server info */
+	gint8 has_indexing;
+	gint8 has_extensions;
+	gint8 has_update;
+	gint8 has_autologout;
+	gint8 has_queries;
+	gint8 has_resolve;
+	gint8 has_browsing;
+	gint8 has_persistent;
+	gint8 auth_type;
+	gint8 auth_method;
+	gint8 login_required;
+
+	gint16 daap_proto_major;
+	gint16 daap_proto_minor;
+	gint16 dmap_proto_major;
+	gint16 dmap_proto_minor;
+
+	gint32 timeout_interval;
+	gint32 sharing_version;
+	gint32 db_count;
+
+	gchar *server_name;
+
+	/* mccr - content codes */
+	/* none */
+
+	/* mlog - login */
+	guint32 session_id;
+
+	/* mupd - update */
+	guint32 revision_id;
+
+	/* avdb - db list */
+	/* none */
+
+	/* apso - items in playlist */
+	/* none */
+
+	/* aply - playlist list */
+	/* none */
+
+} cc_data_t;
+
+/* mlit -- used in a item listing */
+typedef struct {
+	gint8 item_kind;
+	gint8 song_data_kind;
+	gint8 song_compilation;
+	gint8 is_smart_pl;
+	gint8 is_base_pl;
+	
+	gint16 song_bitrate;
+	gint16 song_year;
+	gint16 song_track_no;
+	gint16 song_track_count;
+	gint16 song_disc_count;
+	gint16 song_disc_no;
+	gint16 song_bpm;
+
+	gint32 dbid;
+	gint32 sample_rate;
+	gint32 song_size;
+	gint32 song_start_time;
+	gint32 song_stop_time;
+	gint32 song_total_time;
+	gint32 song_date;
+	gint32 song_date_mod;
+	gint32 container_id;
+
+	gint32 deleted_id;
+
+	guint64 persistent_id;
+	
+	gchar *iname;
+	gchar *song_data_url;
+	gchar *song_data_album;
+	gchar *song_data_artist;
+	gchar *song_comment;
+	gchar *song_description;
+	gchar *song_genre;
+	gchar *song_format;
+	gchar *song_composer;
+	gchar *song_grouping;
+	
+	/* db list specific */
+	gint32 db_n_items;
+	gint32 db_n_playlist;
+
+} cc_item_record_t;
+
+cc_data_t *
+cc_data_new ();
+
+void
+cc_data_free (cc_data_t *fields);
+
+void
+cc_item_record_free (cc_item_record_t *item);
+
+GSList *
+cc_record_list_deep_copy (GSList *record_list);
+
+cc_data_t *
+cc_handler (gchar *data, gint data_len);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/daap/xmms2-daap/daap_cmd.c	Sun Aug 05 00:26:21 2007 +0300
@@ -0,0 +1,291 @@
+/** @file daap_cmd.c
+ *  Wrapper functions for issuing DAAP commands.
+ *
+ *  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 "daap_cmd.h"
+#include "daap_conn.h"
+
+static cc_data_t *
+daap_request_data (GIOChannel *chan, const gchar *path, gchar *host, guint request_id);
+static gboolean
+daap_request_stream (GIOChannel *chan, gchar *path, gchar *host,
+                     guint request_id, guint *size);
+static gchar *
+daap_url_append_meta (gchar *url, GSList *meta_list);
+
+guint
+daap_command_login (gchar *host, gint port, guint request_id ) {
+	GIOChannel *chan;
+	cc_data_t *cc_data;
+
+	guint session_id = 0;
+
+	chan = daap_open_connection (host, port);
+	if (!chan) {
+        g_print("Connection to server failed! "
+		        "Please make sure the url is of the form:\n"
+		        "daap://ip[:port]/[song]");
+		return 0;
+	}
+
+	cc_data = daap_request_data (chan, "/login", host, request_id);
+	if (cc_data) {
+		session_id = cc_data->session_id;
+		cc_data_free (cc_data);
+	}
+
+	g_io_channel_shutdown (chan, TRUE, NULL);
+	g_io_channel_unref (chan);
+
+	return session_id;
+}
+
+guint
+daap_command_update (gchar *host, gint port, guint session_id, guint request_id)
+{
+	GIOChannel *chan;
+	gchar *request;
+	cc_data_t *cc_data;
+	guint revision_id = 0;
+
+	chan = daap_open_connection (host, port);
+	if (!chan) {
+		return 0;
+	}
+
+	request = g_strdup_printf ("/update?session-id=%d", session_id);
+
+	cc_data = daap_request_data (chan, request, host, request_id);
+	if (cc_data) {
+		revision_id = cc_data->revision_id;
+		cc_data_free (cc_data);
+	}
+
+	g_free (request);
+	g_io_channel_shutdown (chan, TRUE, NULL);
+	g_io_channel_unref (chan);
+
+	return revision_id;
+}
+
+gboolean
+daap_command_logout (gchar *host, gint port, guint session_id, guint request_id)
+{
+	GIOChannel *chan;
+	gchar *request;
+
+	chan = daap_open_connection (host, port);
+	if (!chan) {
+		return FALSE;
+	}
+
+	request = g_strdup_printf ("/logout?session-id=%d", session_id);
+
+	/* there is no cc_data generated, so we don't need to store it anywhere */
+	daap_request_data (chan, request, host, request_id);
+
+	g_free (request);
+	g_io_channel_shutdown (chan, TRUE, NULL);
+	g_io_channel_unref (chan);
+
+	return TRUE;
+}
+
+GSList *
+daap_command_db_list (gchar *host, gint port, guint session_id,
+                      guint revision_id, guint request_id)
+{
+	GIOChannel *chan;
+	gchar *request;
+	cc_data_t *cc_data;
+	GSList *db_id_list = NULL;
+
+	chan = daap_open_connection (host, port);
+	if (!chan) {
+		return NULL;
+	}
+
+	request = g_strdup_printf ("/databases?session-id=%d&revision-id=%d",
+	                           session_id, revision_id);
+
+	cc_data = daap_request_data (chan, request, host, request_id);
+	g_free (request);
+	if (cc_data) {
+		db_id_list = cc_record_list_deep_copy (cc_data->record_list);
+		cc_data_free (cc_data);
+	}
+
+	g_io_channel_shutdown (chan, TRUE, NULL);
+	g_io_channel_unref (chan);
+
+	return db_id_list;
+}
+
+GSList *
+daap_command_song_list (gchar *host, gint port, guint session_id,
+                        guint revision_id, guint request_id, gint db_id)
+{
+	GIOChannel *chan;
+	gchar *request;
+	cc_data_t *cc_data;
+
+	GSList * song_list;
+	GSList * meta_items = NULL;
+
+	chan = daap_open_connection (host, port);
+	if (!chan) {
+		return NULL;
+	}
+
+	meta_items = g_slist_prepend (meta_items, g_strdup ("dmap.itemid"));
+	meta_items = g_slist_prepend (meta_items, g_strdup ("dmap.itemname"));
+	meta_items = g_slist_prepend (meta_items, g_strdup ("daap.songartist"));
+	meta_items = g_slist_prepend (meta_items, g_strdup ("daap.songformat"));
+	meta_items = g_slist_prepend (meta_items, g_strdup ("daap.songtracknumber"));
+	meta_items = g_slist_prepend (meta_items, g_strdup ("daap.songalbum"));
+
+	request = g_strdup_printf ("/databases/%d/items?"
+	                           "session-id=%d&revision-id=%d",
+	                           db_id, session_id, revision_id);
+
+	if (meta_items) {
+		request = daap_url_append_meta (request, meta_items);
+	}
+
+	cc_data = daap_request_data (chan, request, host, request_id);
+	song_list = cc_record_list_deep_copy (cc_data->record_list);
+
+	g_free (request);
+	cc_data_free (cc_data);
+	g_io_channel_shutdown (chan, TRUE, NULL);
+	g_io_channel_unref (chan);
+	g_slist_foreach (meta_items, (GFunc) g_free, NULL);
+	g_slist_free (meta_items);
+
+	return song_list;
+}
+
+GIOChannel *
+daap_command_init_stream (gchar *host, gint port, guint session_id,
+                          guint revision_id, guint request_id,
+                          gint dbid, gchar *song, guint *filesize)
+{
+	GIOChannel *chan;
+	gchar *request;
+	gboolean ok;
+
+	chan = daap_open_connection (host, port);
+	if (!chan) {
+		return NULL;
+	}
+
+	request = g_strdup_printf ("/databases/%d/items%s"
+	                           "?session-id=%d",
+	                           dbid, song, session_id);
+
+	ok = daap_request_stream (chan, request, host, request_id, filesize);
+	g_free (request);
+
+	if (!ok) {
+		return NULL;
+	}
+
+	return chan;
+}
+
+static cc_data_t *
+daap_request_data (GIOChannel *chan, const gchar *path, gchar *host, guint request_id)
+{
+	guint status;
+	gchar *request, *header = NULL;
+	cc_data_t *retval;
+
+	request = daap_generate_request (path, host, request_id);
+	daap_send_request (chan, request);
+	g_free (request);
+
+	daap_receive_header (chan, &header);
+	if (!header) {
+		return NULL;
+	}
+
+	status = get_server_status (header);
+
+	switch (status) {
+		case UNKNOWN_SERVER_STATUS:
+		case HTTP_BAD_REQUEST:
+		case HTTP_FORBIDDEN:
+		case HTTP_NO_CONTENT:
+		case HTTP_NOT_FOUND:
+			retval = NULL;
+			break;
+		case HTTP_OK:
+		default:
+			retval = daap_handle_data (chan, header);
+			break;
+	}
+	g_free (header);
+
+	return retval;
+}
+
+static gboolean
+daap_request_stream (GIOChannel *chan, gchar *path, gchar *host,
+                     guint request_id, guint *size)
+{
+	guint status;
+	gchar *request, *header = NULL;
+
+	request = daap_generate_request (path, host, request_id);
+	daap_send_request (chan, request);
+	g_free (request);
+
+	daap_receive_header (chan, &header);
+	if (!header) {
+		return FALSE;
+	}
+
+	status = get_server_status (header);
+	if (HTTP_OK != status) {
+		g_free (header);
+		return FALSE;
+	}
+
+	*size = get_data_length (header);
+
+	g_free (header);
+
+	return TRUE;
+}
+
+static gchar *
+daap_url_append_meta (gchar *url, GSList *meta_list)
+{
+	gchar * tmpurl;
+
+	tmpurl = url;
+	url = g_strdup_printf ("%s&meta=%s", url, (gchar *) meta_list->data);
+	g_free (tmpurl);
+	meta_list = g_slist_next (meta_list);
+
+	for ( ; meta_list != NULL; meta_list = g_slist_next (meta_list)) {
+		tmpurl = url;
+		url = g_strdup_printf ("%s,%s", url, (gchar *) meta_list->data);
+		g_free (tmpurl);
+	}
+
+	return url;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/daap/xmms2-daap/daap_cmd.h	Sun Aug 05 00:26:21 2007 +0300
@@ -0,0 +1,112 @@
+/** @file daap_cmd.h
+ *
+ *  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.
+ */
+
+#ifndef DAAP_CMD_H
+#define DAAP_CMD_H
+
+#include "cc_handlers.h"
+
+/**
+ * Log into a DAAP server.
+ * Issue the command necessary for logging in.
+ *
+ * @param host host IP of server
+ * @param port port that the server uses on host
+ * @param request_id the request id
+ * @return a session id for use in further commands
+ */
+guint
+daap_command_login (gchar *host, gint port, guint request_id);
+
+/**
+ * Update the DAAP server status.
+ * Issue the command necessary for updating the connection to a DAAP server.
+ *
+ * @param host host IP of server
+ * @param port port that the server uses on host
+ * @param session_id the id of the current session
+ * @param request_id the request id
+ * @return a revision id for use in further commands
+ */
+guint
+daap_command_update (gchar *host, gint port, guint session_id, guint request_id);
+
+/**
+ * Log out of a DAAP server.
+ * Issue the command necessary for logging out.
+ *
+ * @param host host IP of server
+ * @param port port that the server uses on host
+ * @param session_id the id of the current session
+ * @param request_id the request id
+ * @return TRUE on success, FALSE otherwise
+ */
+gboolean
+daap_command_logout (gchar *host, gint port, guint session_id, guint request_id);
+
+/**
+ * Get a list of databases.
+ * Issue the command for fetching a list of song databases on the server.
+ *
+ * @param host host IP of server
+ * @param port port that the server uses on host
+ * @param session_id the id of the current session
+ * @param revision_id the id of the current revision
+ * @param request_id the request id
+ * @return a list of database ids
+ */
+GSList *
+daap_command_db_list (gchar *host, gint port, guint session_id,
+                      guint revision_id, guint request_id);
+
+/**
+ * Get a list of songs in a database.
+ * Issue the command for fetching a list of songs in a database on the server.
+ *
+ * @param host host IP of server
+ * @param port port that the server uses on host
+ * @param session_id the id of the current session
+ * @param revision_id the id of the current revision
+ * @param request_id the request id
+ * @param db_id the database id
+ * @return a list of songs in the database
+ */
+GSList *
+daap_command_song_list (gchar *host, gint port, guint session_id,
+                        guint revision_id, guint request_id, gint db_id);
+
+/**
+ * Begin streaming a song.
+ * Issue the command for streaming a song on the server.
+ * NOTE: This command only _begins_ the stream; unlike the other command
+ * functions, this one does not close the socket/channel, this must be done
+ * manually after reading the data.
+ *
+ * @param host host IP of server
+ * @param port port that the server uses on host
+ * @param session_id the id of the current session
+ * @param revision_id the id of the current revision
+ * @param request_id the request id
+ * @param dbid the database id
+ * @param song a string containing the id and file type of the song to stream
+ * @param filesize a pointer to an integer that stores the content length 
+ * @return: a GIOChannel corresponding to streaming song data
+ */
+GIOChannel *
+daap_command_init_stream (gchar *host, gint port, guint session_id,
+                          guint revision_id, guint request_id,
+                          gint dbid, gchar *song, guint *filesize);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/daap/xmms2-daap/daap_conn.c	Sun Aug 05 00:26:21 2007 +0300
@@ -0,0 +1,331 @@
+/** @file daap_conn.c
+ *  Manages the connection to a DAAP server.
+ *
+ *  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 <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <glib.h>
+#include <glib/gprintf.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+
+
+
+
+
+#include "cc_handlers.h"
+#include "daap_md5.h"
+#include "daap_conn.h"
+#include "daap_util.h"
+
+
+    GIOChannel *
+daap_open_connection (gchar *host, gint port)
+{
+    gint ai_status;
+    gint sockfd;
+    struct sockaddr_in server;
+    struct addrinfo *ai_hint, *ai_result;
+    GIOChannel *sock_chan;
+    GError *err = NULL;
+
+    sockfd = socket (AF_INET, SOCK_STREAM, 0);
+    if (sockfd == -1) {
+        return NULL;
+    }
+
+    sock_chan = g_io_channel_unix_new (sockfd);
+    if (!g_io_channel_get_close_on_unref (sock_chan)) {
+        g_io_channel_set_close_on_unref (sock_chan, TRUE);
+    }
+
+    g_io_channel_set_flags (sock_chan, G_IO_FLAG_NONBLOCK, &err);
+    if (NULL != err) {
+        g_print ("Error setting nonblock flag: %s\n", err->message);
+        g_io_channel_unref (sock_chan);
+        return NULL;
+    }
+
+    /* call getaddrinfo() to convert a hostname to ip */
+
+    ai_hint = g_new0 (struct addrinfo, 1);
+    /* FIXME sometime in the future, we probably want to append
+     *       " | {A,P}F_INET6" for IPv6 support */
+    ai_hint->ai_family = AF_INET;
+    ai_hint->ai_protocol = PF_INET;
+
+    while ((ai_status = getaddrinfo (host, NULL, ai_hint, &ai_result))) {
+        if (ai_status != EAI_AGAIN) {
+            g_print ("Error with getaddrinfo(): %s", gai_strerror (ai_status));
+            g_io_channel_unref (sock_chan);
+            return NULL;
+        }
+    }
+
+    memset (&server, 0, sizeof (struct sockaddr_in));
+
+    server.sin_addr = ((struct sockaddr_in *) ai_result->ai_addr)->sin_addr;
+    server.sin_family = AF_INET;
+    server.sin_port = htons (port);
+
+    g_free (ai_hint);
+    freeaddrinfo (ai_result);
+
+    while (42) {
+        fd_set fds;
+        struct timeval tmout;
+        gint sret;
+        gint err = 0;
+        guint errsize = sizeof (err);
+
+        tmout.tv_sec = 3;
+        tmout.tv_usec = 0;
+
+        sret = connect (sockfd,
+                (struct sockaddr *) &server,
+                sizeof (struct sockaddr_in));
+
+        if (sret == 0) {
+            break;
+        } else if (sret == -1 && errno != EINPROGRESS) {
+            g_print ("connect says: %s", strerror (errno));
+            g_io_channel_unref (sock_chan);
+            return NULL;
+        }
+
+        FD_ZERO (&fds);
+        FD_SET (sockfd, &fds);
+
+		sret = select (sockfd + 1, NULL, &fds, NULL, &tmout);
+		if (sret == 0 || sret == SOCKET_ERROR) {
+			g_io_channel_unref (sock_chan);
+			return NULL;
+		}
+
+		/** Haha, lol lol ololo sockets in POSIX */
+		if (getsockopt (sockfd, SOL_SOCKET, SO_ERROR, &err, &errsize) < 0) {
+			g_io_channel_unref (sock_chan);
+			return NULL;
+		}
+
+		if (err != 0) {
+			g_print ("Connect call failed!");
+			g_io_channel_unref (sock_chan);
+			return NULL;
+		}
+
+		if (FD_ISSET (sockfd, &fds)) {
+			break;
+		}
+	}
+
+	g_io_channel_set_encoding (sock_chan, NULL, &err);
+	if (NULL != err) {
+		g_print ("Error setting encoding: %s\n", err->message);
+		g_io_channel_unref (sock_chan);
+		return NULL;
+	}
+
+	return sock_chan;
+}
+
+gchar *
+daap_generate_request (const gchar *path, gchar *host, gint request_id)
+{
+	gchar *req;
+	gint8 hash[33];
+
+	memset (hash, 0, 33);
+
+	daap_hash_generate (DAAP_VERSION, (guchar *) path, 2, (guchar *) hash,
+	                    request_id);
+
+	req = g_strdup_printf ("GET %s %s\r\n"
+	                       "Host: %s\r\n"
+	                       "Accept: */*\r\n"
+	                       "User-Agent: %s\r\n"
+	                       "Accept-Language: en-us, en;q=5.0\r\n"
+	                       "Client-DAAP-Access-Index: 2\r\n"
+	                       "Client-DAAP-Version: 3.0\r\n"
+	                       "Client-DAAP-Validation: %s\r\n"
+	                       "Client-DAAP-Request-ID: %d\r\n"
+	                       "Connection: close\r\n"
+	                       "\r\n",
+	                       path, HTTP_VER_STRING, host,
+	                       USER_AGENT, hash, request_id);
+	return req;
+}
+
+void
+daap_send_request (GIOChannel *sock_chan, gchar *request)
+{
+	gint n_bytes_to_send;
+
+	n_bytes_to_send = strlen (request);
+
+	write_buffer_to_channel (sock_chan, request, n_bytes_to_send);
+}
+
+void
+daap_receive_header (GIOChannel *sock_chan, gchar **header)
+{
+	guint n_total_bytes_recvd = 0;
+	gsize linelen;
+	gchar *response, *recv_line;
+	GIOStatus io_stat;
+	GError *err = NULL;
+
+	if (NULL != header) {
+		*header = NULL;
+	}
+
+	response = (gchar *) g_malloc0 (sizeof (gchar) * MAX_HEADER_LENGTH);
+	if (NULL == response) {
+		g_print ("Error: couldn't allocate memory for response.\n");
+		return;
+	}
+
+	/* read data from the io channel one line at a time, looking for
+	 * the end of the header */
+	do {
+		io_stat = g_io_channel_read_line (sock_chan, &recv_line, &linelen,
+		                                  NULL, &err);
+		if (io_stat == G_IO_STATUS_ERROR) {
+			g_print ("Error reading from channel: %s\n", err->message);
+			break;
+		}
+
+		if (NULL != recv_line) {
+			memcpy (response+n_total_bytes_recvd, recv_line, linelen);
+			n_total_bytes_recvd += linelen;
+
+			if (strcmp (recv_line, "\r\n") == 0) {
+				g_free (recv_line);
+				if (NULL != header) {
+					*header = (gchar *) g_malloc0 (sizeof (gchar) *
+					                               n_total_bytes_recvd);
+					if (NULL == *header) {
+						g_print ("error: couldn't allocate header\n");
+						break;
+					}
+					memcpy (*header, response, n_total_bytes_recvd);
+				}
+				break;
+			}
+
+			g_free (recv_line);
+		}
+
+		if (io_stat == G_IO_STATUS_EOF) {
+			break;
+		}
+
+		if (n_total_bytes_recvd >= MAX_HEADER_LENGTH) {
+			g_print ("Warning: Maximum header size reached without finding "
+			          "end of header; bailing.\n");
+			break;
+		}
+	} while (TRUE);
+
+	g_free (response);
+
+	if (sock_chan) {
+		g_io_channel_flush (sock_chan, &err);
+		if (NULL != err) {
+			g_print ("Error flushing buffer: %s\n", err->message);
+			return;
+		}
+	}
+}
+
+cc_data_t *
+daap_handle_data (GIOChannel *sock_chan, gchar *header)
+{
+	cc_data_t * retval;
+	gint response_length;
+	gchar *response_data;
+
+	response_length = get_data_length (header);
+
+	if (BAD_CONTENT_LENGTH == response_length) {
+		g_print ("warning: Header does not contain a \""CONTENT_LENGTH
+		          "\" parameter.\n");
+		return NULL;
+	} else if (0 == response_length) {
+		g_print ("warning: "CONTENT_LENGTH" is zero, most likely the result of "
+		          "a bad request.\n");
+		return NULL;
+	}
+
+	response_data = (gchar *) g_malloc0 (sizeof (gchar) * response_length);
+	if (NULL == response_data) {
+		g_print ("error: could not allocate response memory\n");
+		return NULL;
+	}
+
+	read_buffer_from_channel (sock_chan, response_data, response_length);
+
+	retval = cc_handler (response_data, response_length);
+	g_free (response_data);
+
+	return retval;
+}
+
+gint
+get_data_length (gchar *header)
+{
+	gint len;
+	gchar *content_length;
+
+	content_length = strstr (header, CONTENT_LENGTH);
+	if (NULL == content_length) {
+		len = BAD_CONTENT_LENGTH;
+	} else {
+		content_length += strlen (CONTENT_LENGTH);
+		len = atoi (content_length);
+	}
+
+	return len;
+}
+
+gint
+get_server_status (gchar *header)
+{
+	gint status;
+	gchar *server_status;
+
+	server_status = strstr (header, HTTP_VER_STRING);
+	if (NULL == server_status) {
+		status = UNKNOWN_SERVER_STATUS;
+	} else {
+		server_status += strlen (HTTP_VER_STRING" ");
+		status = atoi (server_status);
+	}
+
+	return status;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/daap/xmms2-daap/daap_conn.h	Sun Aug 05 00:26:21 2007 +0300
@@ -0,0 +1,65 @@
+/** @file daap_conn.h
+ *
+ *  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.
+ */
+
+#ifndef DAAP_CONN_H
+#define DAAP_CONN_H
+
+#define MAX_REQUEST_LENGTH 1024
+#define MAX_HEADER_LENGTH (1024 * 16)
+
+#define BAD_CONTENT_LENGTH -1
+
+#define DAAP_VERSION 3
+
+#define HTTP_OK               200
+#define HTTP_NO_CONTENT       204
+#define HTTP_BAD_REQUEST      400
+#define HTTP_FORBIDDEN        403
+#define HTTP_NOT_FOUND        404
+#define UNKNOWN_SERVER_STATUS -1
+
+#define DAAP_URL_PREFIX "daap://"
+#define HTTP_VER_STRING "HTTP/1.1"
+#define CONTENT_LENGTH "Content-Length: "
+#define CONTENT_TYPE "Content-Type: "
+/* TODO does this work ok? */
+#define USER_AGENT VERSION
+/*#define USER_AGENT "iTunes/4.6 (Windows; N)"*/
+
+GIOChannel *
+daap_open_connection (gchar *host, gint port);
+
+gchar *
+daap_generate_request (const gchar *path, gchar *host, gint request_id);
+
+void
+daap_send_request (GIOChannel *sock_chan, gchar *request);
+
+void
+daap_receive_header (GIOChannel *sock_chan, gchar **header);
+
+cc_data_t *
+daap_handle_data (GIOChannel *sock_chan, gchar *header);
+
+void
+daap_stream_data (GIOChannel *input, GIOChannel *output, gchar *header);
+
+gint
+get_data_length (gchar *header);
+
+gint
+get_server_status (gchar *header);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/daap/xmms2-daap/daap_md5.c	Sun Aug 05 00:26:21 2007 +0300
@@ -0,0 +1,497 @@
+/** @file daap_md5.c
+ *
+ *  Implementation of DAAP (iTunes Music Sharing) hashing, parsing, connection
+ *  Slightly modified for use in XMMS2
+ *
+ *  Copyright (C) 2004,2005 Charles Schmidt <cschmidt2@emich.edu>
+ *
+ *  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.
+ *
+ *  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 <stdio.h>
+#include <string.h>
+
+#include "daap_md5.h"
+
+/* hashing - based on/copied from libopendaap
+ * Copyright (c) 2004 David Hammerton
+ */
+
+typedef struct {
+	guint32 buf[4];
+	guint32 bits[2];
+	unsigned char in[64];
+	int version;
+} MD5_CTX;
+
+/*
+* This code implements the MD5 message-digest algorithm.
+* The algorithm is due to Ron Rivest.  This code was
+* written by Colin Plumb in 1993, no copyright is claimed.
+* This code is in the public domain; do with it what you wish.
+*
+* Equivalent code is available from RSA Data Security, Inc.
+* This code has been tested against that, and is equivalent,
+* except that you don't need to include two pages of legalese
+* with every copy.
+*
+* To compute the message digest of a chunk of bytes, declare an MD5Context
+* structure, pass it to OpenDaap_MD5Init, call OpenDaap_MD5Update as needed
+* on buffers full of bytes, and then call OpenDaap_MD5Final, which will fill
+* a supplied 16-byte array with the digest.
+*/
+static void
+MD5Transform (guint32 buf[4],
+              guint32 const in[16],
+              gint version);
+/* for some reason we still have to reverse bytes on bigendian machines
+ * I don't really know why... but otherwise it fails..
+ * Any MD5 gurus out there know why???
+ */
+#if 0 //ndef WORDS_BIGENDIAN /* was: HIGHFIRST */
+#define byteReverse (buf, len)     /* Nothing */
+#else
+static void
+byteReverse (unsigned char *buf,
+             unsigned longs);
+
+#ifndef ASM_MD5
+/*
+* Note: this code is harmless on little-endian machines.
+*/
+static void
+byteReverse (unsigned char *buf,
+             unsigned longs)
+{
+	guint32 t;
+	do {
+		t = (guint32) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
+		              ((unsigned) buf[1] << 8 | buf[0]);
+		*(guint32 *) buf = t;
+		buf += 4;
+	} while (--longs);
+}
+#endif /* ! ASM_MD5 */
+#endif /* #if 0 */
+
+static void
+OpenDaap_MD5Init (MD5_CTX *ctx,
+                  gint version)
+{
+	memset (ctx, 0, sizeof (MD5_CTX));
+	ctx->buf[0] = 0x67452301;
+	ctx->buf[1] = 0xefcdab89;
+	ctx->buf[2] = 0x98badcfe;
+	ctx->buf[3] = 0x10325476;
+
+	ctx->bits[0] = 0;
+	ctx->bits[1] = 0;
+
+	ctx->version = version;
+}
+
+static void
+OpenDaap_MD5Update (MD5_CTX *ctx,
+                    unsigned char const *buf,
+                    unsigned int len)
+{
+	guint32 t;
+
+	/* Update bitcount */
+
+	t = ctx->bits[0];
+	if ((ctx->bits[0] = t + ((guint32) len << 3)) < t)
+		ctx->bits[1]++; /* Carry from low to high */
+	ctx->bits[1] += len >> 29;
+
+	t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */
+
+	/* Handle any leading odd-sized chunks */
+
+	if (t) {
+		unsigned char *p = (unsigned char *) ctx->in + t;
+
+		t = 64 - t;
+		if (len < t) {
+			memcpy (p, buf, len);
+			return;
+		}
+		memcpy (p, buf, t);
+		byteReverse (ctx->in, 16);
+		MD5Transform (ctx->buf, (guint32 *) ctx->in, ctx->version);
+		buf += t;
+		len -= t;
+	}
+	/* Process data in 64-byte chunks */
+
+	while (len >= 64) {
+		memcpy (ctx->in, buf, 64);
+		byteReverse (ctx->in, 16);
+		MD5Transform (ctx->buf, (guint32 *) ctx->in, ctx->version);
+		buf += 64;
+		len -= 64;
+	}
+
+	/* Handle any remaining bytes of data. */
+
+	memcpy (ctx->in, buf, len);
+
+}
+
+static void
+OpenDaap_MD5Final (MD5_CTX *ctx,
+                   unsigned char digest[16])
+{
+	unsigned count;
+	unsigned char *p;
+
+	/* Compute number of bytes mod 64 */
+	count = (ctx->bits[0] >> 3) & 0x3F;
+
+	/* Set the first char of padding to 0x80.  This is safe since there is
+	always at least one byte free */
+	p = ctx->in + count;
+	*p++ = 0x80;
+
+	/* Bytes of padding needed to make 64 bytes */
+	count = 64 - 1 - count;
+
+	/* Pad out to 56 mod 64 */
+	if (count < 8) {
+		/* Two lots of padding:  Pad the first block to 64 bytes */
+		memset (p, 0, count);
+		byteReverse (ctx->in, 16);
+		MD5Transform (ctx->buf, (guint32 *) ctx->in, ctx->version);
+
+		/* Now fill the next block with 56 bytes */
+		memset (ctx->in, 0, 56);
+	} else {
+		/* Pad block to 56 bytes */
+		memset (p, 0, count - 8);
+	}
+	byteReverse (ctx->in, 14);
+
+	/* Append length in bits and transform */
+	((guint32 *) ctx->in)[14] = ctx->bits[0];
+	((guint32 *) ctx->in)[15] = ctx->bits[1];
+
+	MD5Transform (ctx->buf, (guint32 *) ctx->in, ctx->version);
+	byteReverse ((unsigned char *) ctx->buf, 4);
+	memcpy (digest, ctx->buf, 16);
+	memset (ctx, 0, sizeof (ctx));     /* In case it's sensitive */
+
+	return;
+}
+
+#ifndef ASM_MD5
+
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1 (z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) \
+( w += f (x, y, z) + data,  w = w<<s | w>>(32-s),  w += x )
+
+/*
+* The core of the MD5 algorithm, this alters an existing MD5 hash to reflect
+* the addition of 16 longwords of new data.  OpenDaap_MD5Update blocks the
+* data and converts bytes into longwords for this routine.
+*/
+static void
+MD5Transform (guint32 buf[4],
+              guint32 const in[16],
+              gint version)
+{
+	guint32 a, b, c, d;
+
+	a = buf[0];
+	b = buf[1];
+	c = buf[2];
+	d = buf[3];
+
+	MD5STEP (F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+	MD5STEP (F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+	MD5STEP (F1, c, d, a, b, in[2] + 0x242070db, 17);
+	MD5STEP (F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+	MD5STEP (F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+	MD5STEP (F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+	MD5STEP (F1, c, d, a, b, in[6] + 0xa8304613, 17);
+	MD5STEP (F1, b, c, d, a, in[7] + 0xfd469501, 22);
+	MD5STEP (F1, a, b, c, d, in[8] + 0x698098d8, 7);
+	MD5STEP (F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+	MD5STEP (F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+	MD5STEP (F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+	MD5STEP (F1, a, b, c, d, in[12] + 0x6b901122, 7);
+	MD5STEP (F1, d, a, b, c, in[13] + 0xfd987193, 12);
+	MD5STEP (F1, c, d, a, b, in[14] + 0xa679438e, 17);
+	MD5STEP (F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+	MD5STEP (F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+	MD5STEP (F2, d, a, b, c, in[6] + 0xc040b340, 9);
+	MD5STEP (F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+	MD5STEP (F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+	MD5STEP (F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+	MD5STEP (F2, d, a, b, c, in[10] + 0x02441453, 9);
+	MD5STEP (F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+	MD5STEP (F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+	MD5STEP (F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+	MD5STEP (F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+	MD5STEP (F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+
+	if (version == 1) {
+		MD5STEP (F2, b, c, d, a, in[8] + 0x445a14ed, 20);
+	} else {
+		MD5STEP (F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+	}
+	MD5STEP (F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+	MD5STEP (F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+	MD5STEP (F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+	MD5STEP (F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+	MD5STEP (F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+	MD5STEP (F3, d, a, b, c, in[8] + 0x8771f681, 11);
+	MD5STEP (F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+	MD5STEP (F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+	MD5STEP (F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+	MD5STEP (F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+	MD5STEP (F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+	MD5STEP (F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+	MD5STEP (F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+	MD5STEP (F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+	MD5STEP (F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+	MD5STEP (F3, b, c, d, a, in[6] + 0x04881d05, 23);
+	MD5STEP (F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+	MD5STEP (F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+	MD5STEP (F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+	MD5STEP (F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+
+	MD5STEP (F4, a, b, c, d, in[0] + 0xf4292244, 6);
+	MD5STEP (F4, d, a, b, c, in[7] + 0x432aff97, 10);
+	MD5STEP (F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+	MD5STEP (F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+	MD5STEP (F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+	MD5STEP (F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+	MD5STEP (F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+	MD5STEP (F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+	MD5STEP (F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+	MD5STEP (F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+	MD5STEP (F4, c, d, a, b, in[6] + 0xa3014314, 15);
+	MD5STEP (F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+	MD5STEP (F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+	MD5STEP (F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+	MD5STEP (F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+	MD5STEP (F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+
+	buf[0] += a;
+	buf[1] += b;
+	buf[2] += c;
+	buf[3] += d;
+}
+
+#endif
+
+
+
+
+
+static int staticHashDone = 0;
+static unsigned char staticHash_42[256*65] = {0};
+static unsigned char staticHash_45[256*65] = {0};
+
+static const char hexchars[] = "0123456789ABCDEF";
+static char ac[] = "Dpqzsjhiu!3114!Bqqmf!Dpnqvufs-!Jod/"; /* +1 */
+static gboolean ac_unfudged = FALSE;
+
+static void
+DigestToString (const unsigned char *digest,
+                char *string)
+{
+	int i;
+	for (i = 0; i < 16; i++) {
+		unsigned char tmp = digest[i];
+		string[i*2+1] = hexchars[tmp & 0x0f];
+		string[i*2] = hexchars[(tmp >> 4) & 0x0f];
+	}
+}
+
+static void
+GenerateStatic_42 () {
+	MD5_CTX ctx;
+	unsigned char *p = staticHash_42;
+	int i;
+	unsigned char buf[16];
+
+	for (i = 0; i < 256; i++) {
+		OpenDaap_MD5Init (&ctx, 0);
+
+#define MD5_STRUPDATE(str) OpenDaap_MD5Update(&ctx, (unsigned char const *)str, strlen (str))
+
+		if ((i & 0x80) != 0)
+			MD5_STRUPDATE ("Accept-Language");
+		else
+			MD5_STRUPDATE ("user-agent");
+
+		if ((i & 0x40) != 0)
+			MD5_STRUPDATE ("max-age");
+		else
+			MD5_STRUPDATE ("Authorization");
+
+		if ((i & 0x20) != 0)
+			MD5_STRUPDATE ("Client-DAAP-Version");
+		else
+			MD5_STRUPDATE ("Accept-Encoding");
+
+		if ((i & 0x10) != 0)
+			MD5_STRUPDATE ("daap.protocolversion");
+		else
+			MD5_STRUPDATE ("daap.songartist");
+
+		if ((i & 0x08) != 0)
+			MD5_STRUPDATE ("daap.songcomposer");
+		else
+			MD5_STRUPDATE ("daap.songdatemodified");
+
+		if ((i & 0x04) != 0)
+			MD5_STRUPDATE ("daap.songdiscnumber");
+		else
+			MD5_STRUPDATE ("daap.songdisabled");
+
+		if ((i & 0x02) != 0)
+			MD5_STRUPDATE ("playlist-item-spec");
+		else
+			MD5_STRUPDATE ("revision-number");
+
+		if ((i & 0x01) != 0)
+			MD5_STRUPDATE ("session-id");
+		else
+			MD5_STRUPDATE ("content-codes");
+#undef MD5_STRUPDATE
+
+		OpenDaap_MD5Final (&ctx, buf);
+		DigestToString (buf, (char *)p);
+		p += 65;
+	}
+}
+
+static void GenerateStatic_45 ()
+{
+	MD5_CTX ctx;
+	unsigned char *p = staticHash_45;
+	int i;
+	unsigned char buf[16];
+
+	for (i = 0; i < 256; i++) {
+		OpenDaap_MD5Init (&ctx, 1);
+
+#define MD5_STRUPDATE(str) OpenDaap_MD5Update(&ctx, (unsigned char const *)str, strlen (str))
+
+		if ((i & 0x40) != 0)
+			MD5_STRUPDATE ("eqwsdxcqwesdc");
+		else
+			MD5_STRUPDATE ("op[;lm,piojkmn");
+
+		if ((i & 0x20) != 0)
+			MD5_STRUPDATE ("876trfvb 34rtgbvc");
+		else
+			MD5_STRUPDATE ("=-0ol.,m3ewrdfv");
+
+		if ((i & 0x10) != 0)
+			MD5_STRUPDATE ("87654323e4rgbv ");
+		else
+			MD5_STRUPDATE ("1535753690868867974342659792");
+
+		if ((i & 0x08) != 0)
+			MD5_STRUPDATE ("Song Name");
+		else
+			MD5_STRUPDATE ("DAAP-CLIENT-ID:");
+
+		if ((i & 0x04) != 0)
+			MD5_STRUPDATE ("111222333444555");
+		else
+			MD5_STRUPDATE ("4089961010");
+
+		if ((i & 0x02) != 0)
+			MD5_STRUPDATE ("playlist-item-spec");
+		else
+			MD5_STRUPDATE ("revision-number");
+
+		if ((i & 0x01) != 0)
+			MD5_STRUPDATE ("session-id");
+		else
+			MD5_STRUPDATE ("content-codes");
+
+		if ((i & 0x80) != 0)
+			MD5_STRUPDATE ("IUYHGFDCXWEDFGHN");
+		else
+			MD5_STRUPDATE ("iuytgfdxwerfghjm");
+
+#undef MD5_STRUPDATE
+
+		OpenDaap_MD5Final (&ctx, buf);
+		DigestToString (buf, (char *)p);
+		p += 65;
+	}
+}
+
+void
+daap_hash_generate (short version_major,
+                    const guchar *url,
+                    guchar hash_select,
+                    guchar *out,
+                    gint request_id)
+{
+	unsigned char buf[16];
+	MD5_CTX ctx;
+	int i;
+
+	unsigned char *hashTable = (version_major == 3) ?
+	                           staticHash_45 : staticHash_42;
+
+	if (!staticHashDone) {
+		GenerateStatic_42 ();
+		GenerateStatic_45 ();
+		staticHashDone = 1;
+	}
+
+	OpenDaap_MD5Init (&ctx, (version_major == 3) ? 1 : 0);
+
+	OpenDaap_MD5Update (&ctx, url, strlen ((const gchar*)url));
+	if (ac_unfudged == FALSE) {
+		for (i = 0; i < strlen (ac); i++) {
+			ac[i] = ac[i]-1;
+		}
+		ac_unfudged = TRUE;
+	}
+	OpenDaap_MD5Update (&ctx, (const guchar*)ac, strlen (ac));
+
+	OpenDaap_MD5Update (&ctx, &hashTable[hash_select * 65], 32);
+
+	if (request_id && version_major == 3) {
+		char scribble[20];
+		sprintf (scribble, "%u", request_id);
+		OpenDaap_MD5Update (&ctx, (const guchar*)scribble, strlen (scribble));
+	}
+
+	OpenDaap_MD5Final (&ctx, buf);
+	DigestToString (buf, (char *)out);
+
+	return;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/daap/xmms2-daap/daap_md5.h	Sun Aug 05 00:26:21 2007 +0300
@@ -0,0 +1,37 @@
+/** @file daap_md5.h
+ *
+ *  Header for DAAP (iTunes Music Sharing) hashing, connection
+ *  Slightly modified for use in XMMS2
+ *
+ *  Copyright (C) 2004,2005 Charles Schmidt <cschmidt2@emich.edu>
+ *
+ *  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.
+ *
+ *  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.
+ *
+ */
+
+#ifndef DAAP_MD5_H
+#define DAAP_MD5_H
+
+#include <glib.h>
+
+void
+daap_hash_generate (short version_major,
+                    const guchar *url,
+                    guchar hash_select,
+                    guchar *out,
+                    gint request_id);
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/daap/xmms2-daap/daap_mdns_avahi.c	Sun Aug 05 00:26:21 2007 +0300
@@ -0,0 +1,267 @@
+/** @file daap_mdns_browse.c
+ *  Browser for DAAP servers shared via mDNS.
+ *
+ *  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 "daap_mdns_browse.h"
+
+#include <string.h>
+#include <glib.h>
+
+#include <avahi-client/client.h>
+#include <avahi-client/lookup.h>
+
+#include <avahi-common/malloc.h>
+#include <avahi-common/error.h>
+#include <avahi-common/timeval.h>
+
+#include <avahi-glib/glib-watch.h>
+#include <avahi-glib/glib-malloc.h>
+
+#define ADDR_LEN (3 * 4 + 3 + 1) /* standard dotted-quad fmt */
+
+typedef struct {
+	AvahiClient *client;
+	GMainLoop *mainloop;
+} browse_callback_userdata_t;
+
+static GSList *g_server_list = NULL;
+static GStaticMutex serv_list_mut = G_STATIC_MUTEX_INIT;
+static AvahiGLibPoll *gl_poll = NULL;
+static AvahiClient *client = NULL;
+static AvahiServiceBrowser *browser = NULL;
+
+static GSList *
+daap_mdns_serv_remove (GSList *serv_list, gchar *addr, guint port)
+{
+	GSList *first = serv_list;
+	daap_mdns_server_t *serv;
+
+	for ( ; serv_list != NULL; serv_list = g_slist_next (serv_list)) {
+		serv = (daap_mdns_server_t *) serv_list->data;
+		if ( (port == serv->port) && (!strcmp (addr, serv->address)) ) {
+			serv_list = g_slist_remove (first, serv);
+
+			g_free (serv->server_name);
+			g_free (serv->mdns_hostname);
+			g_free (serv->address);
+			g_free (serv);
+
+			return serv_list;
+		}
+	}
+	return NULL;
+}
+
+static void
+daap_mdns_resolve_cb (AvahiServiceResolver *resolv,
+                      AvahiIfIndex iface,
+                      AvahiProtocol proto,
+                      AvahiResolverEvent event,
+                      const gchar *name,
+                      const gchar *type,
+                      const gchar *domain,
+                      const gchar *hostname,
+                      const AvahiAddress *addr,
+                      guint16 port,
+                      AvahiStringList *text,
+                      AvahiLookupResultFlags flags,
+                      void *userdata)
+{
+	gboolean *remove = userdata;
+	gchar ad[ADDR_LEN];
+	daap_mdns_server_t *server;
+
+	if (!resolv) {
+		return;
+	}
+
+	switch (event) {
+		case AVAHI_RESOLVER_FOUND:
+			server = (daap_mdns_server_t *)
+			         g_malloc0 (sizeof (daap_mdns_server_t));
+			avahi_address_snprint (ad, sizeof (ad), addr);
+
+			server->server_name = g_strdup (name);
+			server->address = g_strdup (ad);
+			server->mdns_hostname = g_strdup (hostname);
+			server->port = port;
+
+			if (*remove) {
+				g_static_mutex_lock (&serv_list_mut);
+				g_server_list = daap_mdns_serv_remove (g_server_list, ad, port);
+				g_static_mutex_unlock (&serv_list_mut);
+			} else {
+				g_static_mutex_lock (&serv_list_mut);
+				g_server_list = g_slist_prepend (g_server_list, server);
+				g_static_mutex_unlock (&serv_list_mut);
+			}
+			g_free (remove);
+
+			break;
+
+		case AVAHI_RESOLVER_FAILURE:
+			break;
+
+		default:
+			break;
+	}
+
+	avahi_service_resolver_free (resolv);
+}
+
+static void
+daap_mdns_browse_cb (AvahiServiceBrowser *browser,
+                     AvahiIfIndex iface,
+                     AvahiProtocol proto,
+                     AvahiBrowserEvent event,
+                     const gchar *name,
+                     const gchar *type,
+                     const gchar *domain,
+                     AvahiLookupResultFlags flags,
+                     void *userdata)
+{
+	gboolean ok = FALSE;
+	gboolean *b = g_malloc (sizeof (gboolean));
+
+	AvahiClient *client = ((browse_callback_userdata_t *) userdata)->client;
+
+	if (!browser) {
+		return;
+	}
+
+	switch (event) {
+		case AVAHI_BROWSER_NEW:
+			*b = FALSE;
+			ok = (gboolean)
+			     avahi_service_resolver_new (client, iface, proto, name, type,
+			                                 domain, AVAHI_PROTO_UNSPEC, 0,
+			                                 daap_mdns_resolve_cb, b);
+			break;
+
+		case AVAHI_BROWSER_REMOVE:
+			*b = TRUE;
+			ok = (gboolean)
+			     avahi_service_resolver_new (client, iface, proto, name, type,
+			                                 domain, AVAHI_PROTO_UNSPEC, 0,
+			                                 daap_mdns_resolve_cb, b);
+			break;
+
+		case AVAHI_BROWSER_CACHE_EXHAUSTED:
+			break;
+
+		case AVAHI_BROWSER_ALL_FOR_NOW:
+			break;
+
+		default:
+			break;
+	}
+}
+
+static void
+daap_mdns_client_cb (AvahiClient *client,
+                     AvahiClientState state,
+                     void * userdata)
+{
+	if (!client) {
+		return;
+	}
+
+	switch (state) {
+		case AVAHI_CLIENT_FAILURE:
+			break;
+		default:
+			break;
+	}
+}
+
+static void
+daap_mdns_timeout (AvahiTimeout *to, void *userdata)
+{
+}
+
+static gboolean
+daap_mdns_timeout_glib (void *userdata)
+{
+	return FALSE;
+}
+
+gboolean
+daap_mdns_initialize ()
+{
+	const AvahiPoll *av_poll;
+
+	GMainLoop *ml = NULL;
+
+	gboolean ok = TRUE;
+	gint errval;
+	struct timeval tv;
+	browse_callback_userdata_t *browse_userdata;
+
+	if (gl_poll) {
+		ok = FALSE;
+		goto fail;
+	}
+
+	browse_userdata = g_malloc0 (sizeof (browse_callback_userdata_t));
+
+	avahi_set_allocator (avahi_glib_allocator ());
+
+	ml = g_main_loop_new (NULL, FALSE);
+
+	gl_poll = avahi_glib_poll_new (NULL, G_PRIORITY_DEFAULT);
+	av_poll = avahi_glib_poll_get (gl_poll);
+
+	avahi_elapse_time (&tv, 2000, 0);
+	av_poll->timeout_new (av_poll, &tv, daap_mdns_timeout, NULL);
+	g_timeout_add (5000, daap_mdns_timeout_glib, ml);
+
+	client = avahi_client_new (av_poll, 0, daap_mdns_client_cb, ml, &errval);
+	if (!client) {
+		ok = FALSE;
+		goto fail;
+	}
+
+	browse_userdata->client = client;
+	browse_userdata->mainloop = ml;
+
+	browser = avahi_service_browser_new (client, AVAHI_IF_UNSPEC,
+	                                     AVAHI_PROTO_UNSPEC, "_daap._tcp", NULL,
+	                                     0, daap_mdns_browse_cb,
+	                                     browse_userdata);
+	if (!browser) {
+		ok = FALSE;
+		goto fail;
+	}
+
+fail:
+	return ok;
+}
+
+GSList *
+daap_mdns_get_server_list ()
+{
+	GSList * l;
+	g_static_mutex_lock (&serv_list_mut);
+	l = g_slist_copy (g_server_list);
+	g_static_mutex_unlock (&serv_list_mut);
+	return l;
+}
+
+void
+daap_mdns_destroy ()
+{
+	/* FIXME: deinit avahi */
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/daap/xmms2-daap/daap_mdns_browse.h	Sun Aug 05 00:26:21 2007 +0300
@@ -0,0 +1,37 @@
+/** @file daap_mdns_browse.h
+ *
+ *  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.
+ */
+
+#ifndef DAAP_MDNS_BROWSE_H
+#define DAAP_MDNS_BROWSE_H
+
+#include <glib.h>
+
+typedef struct {
+	gchar *server_name;
+	gchar *address;
+	gchar *mdns_hostname;
+	guint16 port;
+} daap_mdns_server_t;
+
+gboolean
+daap_mdns_initialize ();
+
+GSList *
+daap_mdns_get_server_list ();
+
+void
+daap_mdns_destroy ();
+
+#endif
--- /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);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/daap/xmms2-daap/daap_mdns_dummy.c	Sun Aug 05 00:26:21 2007 +0300
@@ -0,0 +1,36 @@
+/** 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 "daap_mdns_browse.h"
+
+gboolean
+daap_mdns_initialize ()
+{
+	return FALSE;
+}
+
+GSList *
+daap_mdns_get_server_list ()
+{
+	return NULL;
+}
+
+void
+daap_mdns_destroy ()
+{
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/daap/xmms2-daap/daap_util.c	Sun Aug 05 00:26:21 2007 +0300
@@ -0,0 +1,79 @@
+/** @file daap_util.c
+ *  Miscellaneous utility functions.
+ *
+ *  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 <glib/gprintf.h>
+
+#include "daap_util.h"
+
+void
+write_buffer_to_channel (GIOChannel *chan, gchar *buf, gint bufsize)
+{
+	guint total_sent_bytes = 0;
+	gsize sent_bytes;
+	GIOStatus io_stat;
+	GError *err = NULL;
+
+	do {
+		io_stat = g_io_channel_write_chars (chan,
+		                                    buf + total_sent_bytes,
+		                                    bufsize - total_sent_bytes,
+		                                    &sent_bytes,
+		                                    &err);
+		if (io_stat == G_IO_STATUS_ERROR) {
+			if (NULL != err) {
+				g_print ("Error writing to channel: %s\n", err->message);
+			}
+			break;
+		}
+
+		bufsize -= sent_bytes;
+		total_sent_bytes += sent_bytes;
+	} while (bufsize > 0);
+
+	g_io_channel_flush (chan, &err);
+	if (NULL != err) {
+		g_print ("warning: error flushing channel: %s\n", err->message);
+	}
+}
+
+gint
+read_buffer_from_channel (GIOChannel *chan, gchar *buf, gint bufsize)
+{
+	guint n_total_bytes_read = 0;
+	gsize read_bytes;
+	GIOStatus io_stat;
+	GError *err = NULL;
+
+	do {
+		io_stat = g_io_channel_read_chars (chan,
+		                                   buf + n_total_bytes_read,
+		                                   bufsize - n_total_bytes_read,
+		                                   &read_bytes,
+		                                   &err);
+		if (io_stat == G_IO_STATUS_ERROR) {
+			g_print ("warning: error reading from channel: %s\n", err->message);
+		}
+		n_total_bytes_read += read_bytes;
+
+		if (io_stat == G_IO_STATUS_EOF) {
+			break;
+		}
+	} while (bufsize > n_total_bytes_read);
+
+	return n_total_bytes_read;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/daap/xmms2-daap/daap_util.h	Sun Aug 05 00:26:21 2007 +0300
@@ -0,0 +1,25 @@
+/** @file daap_util.h
+ *
+ *  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.
+ */
+
+#ifndef DAAP_UTIL_H
+#define DAAP_UTIL_H
+
+gint
+read_buffer_from_channel (GIOChannel *chan, gchar *buf, gint bufsize);
+
+void
+write_buffer_to_channel (GIOChannel *chan, gchar *buf, gint bufsize);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/daap/xmms2-daap/daap_xform.c	Sun Aug 05 00:26:21 2007 +0300
@@ -0,0 +1,449 @@
+/** @file daap_xform.c
+ *  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.
+ */
+
+/* XXX as of the current implementation, there is no method of logging out
+ * of the servers.
+ */
+
+#include "xmms/xmms_xformplugin.h"
+#include "xmms/xmms_log.h"
+
+#include "daap_cmd.h"
+#include "daap_util.h"
+#include "daap_mdns_browse.h"
+
+#include <stdlib.h>
+#include <glib.h>
+#include <glib/gprintf.h>
+
+#define DEFAULT_DAAP_PORT 3689
+
+/*
+ * Type definitions
+ */
+
+typedef struct {
+	gchar *host;
+	guint port;
+
+	GIOChannel *channel;
+
+	xmms_error_t status;
+} xmms_daap_data_t;
+
+typedef struct {
+	gboolean logged_in;
+
+	guint session_id;
+	guint revision_id;
+	guint request_id;
+} xmms_daap_login_data_t;
+
+static GHashTable *login_sessions = NULL;
+
+/*
+ * Function prototypes
+ */
+
+static gboolean
+xmms_daap_plugin_setup (xmms_xform_plugin_t *xform_plugin);
+static gboolean
+xmms_daap_init (xmms_xform_t *xform);
+static void
+xmms_daap_destroy (xmms_xform_t *xform);
+static gint
+xmms_daap_read (xmms_xform_t *xform, void *buffer,
+                gint len, xmms_error_t *error);
+static gboolean
+xmms_daap_browse (xmms_xform_t *xform, const gchar *url, xmms_error_t *error);
+
+/*
+ * Plugin header
+ */
+XMMS_XFORM_PLUGIN ("daap",
+                   "DAAP access plugin",
+                   "SoC",
+                   "Accesses iTunes (DAAP) music shares",
+                   xmms_daap_plugin_setup);
+
+
+/**
+ * Extract hostname, port and command from an url.
+ * daap://hostname:port/command
+ */
+static gboolean
+get_data_from_url (const gchar *url, gchar **host, guint *port, gchar **cmd, xmms_error_t *err)
+{
+	const gchar *port_ptr, *cmd_ptr, *end_ptr, *stripped;
+
+	stripped = url + sizeof (gchar) * strlen ("daap://");
+
+	end_ptr = stripped + sizeof (gchar) * strlen (stripped);
+
+	if (stripped == end_ptr) {
+		xmms_error_set (err, XMMS_ERROR_INVAL, "Empty URL");
+		return FALSE;
+	}
+
+	port_ptr = strstr (stripped, ":");
+	if (port && port_ptr && (port_ptr + 1) != end_ptr) {
+		*port = strtol (port_ptr + 1, (gchar **) NULL, 10);
+		if (*port == 0) {
+			*port = DEFAULT_DAAP_PORT;
+		}
+	} else if (port) {
+		*port = DEFAULT_DAAP_PORT;
+	}
+
+	cmd_ptr = strstr (stripped, "/");
+	if (cmd && cmd_ptr && (cmd_ptr + 1) != end_ptr) {
+		*cmd = g_strdup (cmd_ptr);
+	} else if (cmd) {
+		/* cmd wanted but not found */
+		xmms_error_set (err, XMMS_ERROR_INVAL, "No file requested");
+	} else if (!cmd && cmd_ptr && (cmd_ptr + 1) != end_ptr) {
+		/* cmd not wanted but found */
+		xmms_error_set (err, XMMS_ERROR_NOENT, "No such directory");
+		return FALSE;
+	}
+
+	if (port_ptr) {
+		*host = g_strndup (stripped, port_ptr - stripped);
+	} else if (cmd_ptr) {
+		*host = g_strndup (stripped, cmd_ptr - stripped);
+	} else {
+		*host = g_strdup (stripped);
+	}
+
+	return TRUE;
+}
+
+
+static gboolean
+xmms_daap_plugin_setup (xmms_xform_plugin_t *xform_plugin)
+{
+	xmms_xform_methods_t methods;
+
+	XMMS_XFORM_METHODS_INIT (methods);
+	methods.init = xmms_daap_init;
+	methods.destroy = xmms_daap_destroy;
+	methods.read = xmms_daap_read;
+	methods.browse = xmms_daap_browse;
+
+	xmms_xform_plugin_methods_set (xform_plugin, &methods);
+
+	xmms_xform_plugin_indata_add (xform_plugin,
+	                              XMMS_STREAM_TYPE_MIMETYPE,
+	                              "application/x-url",
+	                              XMMS_STREAM_TYPE_URL,
+	                              "daap://*",
+	                              XMMS_STREAM_TYPE_END);
+
+	daap_mdns_initialize ();
+
+	if (!login_sessions) {
+		login_sessions = g_hash_table_new (g_str_hash, g_str_equal);
+	}
+
+	return TRUE;
+}
+
+
+/**
+ * Add a song to the browsing list.
+ */
+static void
+daap_add_song_to_list (xmms_xform_t *xform, cc_item_record_t *song)
+{
+	gchar *songurl;
+
+	songurl = g_strdup_printf ("%u.%s", song->dbid, song->song_format);
+	xmms_xform_browse_add_entry (xform, songurl, 0);
+	g_free (songurl);
+
+	if (song->iname) {
+		xmms_xform_browse_add_entry_property_str (xform, "title",
+		                                          song->iname);
+	}
+
+	if (song->song_data_artist) {
+		xmms_xform_browse_add_entry_property_str (xform, "artist",
+		                                          song->song_data_artist);
+	}
+
+	if (song->song_data_album) {
+		xmms_xform_browse_add_entry_property_str (xform, "album",
+		                                          song->song_data_album);
+	}
+
+	xmms_xform_browse_add_entry_property_int (xform, "tracknr",
+	                                          song->song_track_no);
+}
+
+
+/**
+ * Scan a daap server for songs.
+ */
+static gboolean
+daap_get_urls_from_server (xmms_xform_t *xform, gchar *host, guint port,
+                           xmms_error_t *err)
+{
+	GSList *dbid_list = NULL;
+	GSList *song_list = NULL, *song_el;
+	cc_item_record_t *db_data;
+	xmms_daap_login_data_t *login_data;
+	gchar *hash;
+
+	hash = g_strdup_printf ("%s:%u", host, port);
+
+	login_data = g_hash_table_lookup (login_sessions, hash);
+
+	if (!login_data) {
+		login_data = g_new0 (xmms_daap_login_data_t, 1);
+
+		login_data->session_id = daap_command_login (host, port, 0, err);
+		if (xmms_error_iserror (err)) {
+			return FALSE;
+		}
+
+		login_data->revision_id = daap_command_update (host, port,
+		                                               login_data->session_id,
+		                                               0);
+
+		login_data->request_id = 1;
+		login_data->logged_in = TRUE;
+
+		g_hash_table_insert (login_sessions, hash, login_data);
+	} else {
+		login_data->revision_id = daap_command_update (host, port,
+		                                               login_data->session_id,
+		                                               0);
+	}
+
+	dbid_list = daap_command_db_list (host, port, login_data->session_id,
+	                                  login_data->revision_id, 0);
+	if (!dbid_list) {
+		return FALSE;
+	}
+
+	/* XXX i've never seen more than one db per server out in the wild,
+	 *     let's hope that never changes *wink*
+	 *     just use the first db in the list */
+	db_data = (cc_item_record_t *) dbid_list->data;
+	song_list = daap_command_song_list (host, port, login_data->session_id,
+	                                    login_data->revision_id,
+	                                    0, db_data->dbid);
+
+	g_slist_foreach (dbid_list, (GFunc) cc_item_record_free, NULL);
+	g_slist_free (dbid_list);
+
+	if (!song_list) {
+		return FALSE;
+	}
+
+	for (song_el = song_list; song_el; song_el = g_slist_next (song_el)) {
+		daap_add_song_to_list (xform, song_el->data);
+	}
+
+	g_slist_foreach (song_list, (GFunc) cc_item_record_free, NULL);
+	g_slist_free (song_list);
+
+	return TRUE;
+}
+
+
+/*
+ * Member functions
+ */
+
+static gboolean
+xmms_daap_init (xmms_xform_t *xform)
+{
+	gint dbid;
+	GSList *dbid_list = NULL;
+	xmms_daap_data_t *data;
+	xmms_daap_login_data_t *login_data;
+	xmms_error_t err;
+	const gchar *url;
+	const gchar *metakey;
+	gchar *command, *hash;
+	guint filesize;
+
+	g_return_val_if_fail (xform, FALSE);
+
+	url = xmms_xform_indata_get_str (xform, XMMS_STREAM_TYPE_URL);
+
+	g_return_val_if_fail (url, FALSE);
+
+	data = g_new0 (xmms_daap_data_t, 1);
+
+	xmms_error_reset (&err);
+
+	if (!get_data_from_url (url, &(data->host), &(data->port), &command, &err)) {
+		return FALSE;
+	}
+
+	hash = g_strdup_printf ("%s:%u", data->host, data->port);
+
+	login_data = g_hash_table_lookup (login_sessions, hash);
+	if (!login_data) {
+		XMMS_DBG ("creating login data for %s", hash);
+		login_data = g_new0 (xmms_daap_login_data_t, 1);
+
+		login_data->request_id = 1;
+		login_data->logged_in = TRUE;
+
+		login_data->session_id = daap_command_login (data->host, data->port,
+		                                             login_data->request_id,
+		                                             &err);
+		if (xmms_error_iserror (&err)) {
+			return FALSE;
+		}
+
+		g_hash_table_insert (login_sessions, hash, login_data);
+	}
+
+	login_data->revision_id = daap_command_update (data->host, data->port,
+	                                               login_data->session_id,
+	                                               login_data->request_id);
+	dbid_list = daap_command_db_list (data->host, data->port,
+	                                  login_data->session_id,
+	                                  login_data->revision_id,
+	                                  login_data->request_id);
+	if (!dbid_list) {
+		return FALSE;
+	}
+
+	/* XXX: see XXX in the browse function above */
+	dbid = ((cc_item_record_t *) dbid_list->data)->dbid;
+	/* want to request a stream, but don't read the data yet */
+	data->channel = daap_command_init_stream (data->host, data->port,
+	                                          login_data->session_id,
+	                                          login_data->revision_id,
+	                                          login_data->request_id, dbid,
+	                                          command, &filesize);
+	if (! data->channel) {
+		return FALSE;
+	}
+	login_data->request_id++;
+
+	metakey = XMMS_MEDIALIB_ENTRY_PROPERTY_SIZE;
+	xmms_xform_metadata_set_int (xform, metakey, filesize);
+
+	xmms_xform_private_data_set (xform, data);
+
+	xmms_xform_outdata_type_add (xform,
+	                             XMMS_STREAM_TYPE_MIMETYPE,
+	                             "application/octet-stream",
+	                             XMMS_STREAM_TYPE_END);
+
+	g_slist_foreach (dbid_list, (GFunc) cc_item_record_free, NULL);
+	g_slist_free (dbid_list);
+	g_free (command);
+
+	return TRUE;
+}
+
+static void
+xmms_daap_destroy (xmms_xform_t *xform)
+{
+	xmms_daap_data_t *data;
+
+	data = xmms_xform_private_data_get (xform);
+
+	g_io_channel_shutdown (data->channel, TRUE, NULL);
+	g_io_channel_unref (data->channel);
+
+	g_free (data->host);
+	g_free (data);
+}
+
+static gint
+xmms_daap_read (xmms_xform_t *xform, void *buffer, gint len, xmms_error_t *error)
+{
+	xmms_daap_data_t *data;
+	gsize read_bytes = 0;
+	GIOStatus status;
+
+	data = xmms_xform_private_data_get (xform);
+
+	/* request is performed, header is stripped. now read the data. */
+	while (read_bytes == 0) {
+		status = g_io_channel_read_chars (data->channel, buffer, len,
+		                                  &read_bytes, NULL);
+		if (status == G_IO_STATUS_EOF || status == G_IO_STATUS_ERROR) {
+			break;
+		}
+	}
+
+	return read_bytes;
+}
+
+
+static gboolean
+xmms_daap_browse (xmms_xform_t *xform, const gchar *url, xmms_error_t *error)
+{
+	gboolean ret = FALSE;
+
+	if (g_strcasecmp (url, "daap://") == 0) {
+
+		GSList *sl = daap_mdns_get_server_list ();
+
+		for (; sl; sl = g_slist_next (sl)) {
+			daap_mdns_server_t *mdns_serv;
+			gchar *str;
+
+			mdns_serv = sl->data;
+
+			str = g_strdup_printf ("%s:%d", mdns_serv->address,
+			                       mdns_serv->port);
+			xmms_xform_browse_add_entry (xform, str, XMMS_XFORM_BROWSE_FLAG_DIR);
+			g_free (str);
+
+			xmms_xform_browse_add_entry_property_str (xform, "servername",
+			                                          mdns_serv->server_name);
+
+			xmms_xform_browse_add_entry_property_str (xform, "ip",
+			                                          mdns_serv->address);
+
+			xmms_xform_browse_add_entry_property_str (xform, "name",
+			                                          mdns_serv->mdns_hostname);
+
+			xmms_xform_browse_add_entry_property_int (xform, "port",
+			                                          mdns_serv->port);
+
+			/* TODO implement the machinery to allow for this */
+			// val = xmms_object_cmd_value_int_new (mdns_serv->need_auth);
+			// xmms_xform_browse_add_entry_property (xform, "passworded", val);
+			// val = xmms_object_cmd_value_int_new (mdns_serv->version);
+			// xmms_xform_browse_add_entry_property (xform, "version", val);
+		}
+
+		ret = TRUE;
+
+		g_slist_free (sl);
+	} else {
+		gchar *host;
+		guint port;
+
+		if (get_data_from_url (url, &host, &port, NULL, error)) {
+			ret = daap_get_urls_from_server (xform, host, port, error);
+			g_free (host);
+		}
+	}
+
+	return ret;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/daap/xmms2-daap/wscript	Sun Aug 05 00:26:21 2007 +0300
@@ -0,0 +1,36 @@
+from waftools.plugin import plugin
+from Params import g_platform
+
+def plugin_configure(conf):
+    # Set the default fallthrough, if no "intelligent" backend is found.
+    conf.env['XMMS_DAAP_BACKEND'] = 'dummy'
+
+    # First look for Avahi mdns support
+    if (conf.check_pkg("avahi-glib", destvar='avahiglib') and
+        conf.check_pkg("avahi-client", destvar='avahiclient')):
+        # Avahi found
+        conf.env['XMMS_DAAP_BACKEND'] = 'avahi'
+    elif conf.check_header('dns_sd.h'):
+        # We might have dnssd support. If we're not on OSX, check for the
+        # presence of the lib.
+        if g_platform == 'darwin':
+            conf.env['XMMS_DAAP_BACKEND'] = 'dnssd'
+        elif conf.check_library2('dns_sd', uselib='dnssd', mandatory=0):
+            conf.env['XMMS_DAAP_BACKEND'] = 'dnssd'
+    return True
+
+def plugin_build(bld, obj):
+    daap_backend = bld.env_of_name('default')['XMMS_DAAP_BACKEND']
+    obj.source.append("daap_mdns_%s.c" % daap_backend)
+    if daap_backend == 'avahi':
+        obj.uselib += ' avahiglib avahiclient'
+    elif daap_backend == 'dnssd':
+        obj.uselib += ' dnssd'
+    if g_platform == 'win32':
+        obj.uselib += ' socket'
+
+configure, build = plugin('daap', configure=plugin_configure,
+                          extra_libs=['curl'], build=plugin_build,
+                          source="""daap_xform.c daap_cmd.c daap_conn.c
+                                    daap_util.c daap_md5.c cc_handlers.c
+                                 """.split())