[libdmapsharing] Apply Alexandre Rosenfeld's DACP patch from Google Summer of Code 2010



commit d2ef353f013449a680b7c385a3913131706b43bb
Author: W. Michael Petullo <mike flyn org>
Date:   Fri Aug 20 10:12:15 2010 -0500

    Apply Alexandre Rosenfeld's DACP patch from Google Summer of Code 2010
    
    Add DACP support. This allows an application such as Rhythmbox to be
    controlled using a remote control application on an iPhone, iPod Touch,
    iPad or other similar device. The DACP code is not yet complete, but
    progress warrants commiting it to the development tree and focusing
    future work on specific patches.
    Signed-off-by: W. Michael Petullo <mike flyn org>

 ChangeLog                                  |    5 +
 Makefile.am                                |    6 +-
 config.h.in                                |    6 +
 configure.ac                               |   27 +-
 docs/libdmapsharing-docs.sgml              |    1 +
 libdmapsharing.pc.in                       |    2 +-
 libdmapsharing/Makefile.am                 |   31 +-
 libdmapsharing/daap-record.c               |   66 ++-
 libdmapsharing/daap-record.h               |    9 +
 libdmapsharing/daap-share.c                |   54 +-
 libdmapsharing/dacp-player.c               |  150 ++++
 libdmapsharing/dacp-player.h               |   85 +++
 libdmapsharing/dacp-share.c                | 1040 ++++++++++++++++++++++++++++
 libdmapsharing/dacp-share.h                |  169 +++++
 libdmapsharing/dmap-connection.c           |  107 ++--
 libdmapsharing/dmap-connection.h           |    4 +-
 libdmapsharing/dmap-db.c                   |  176 ++++--
 libdmapsharing/dmap-db.h                   |    4 +-
 libdmapsharing/dmap-marshal.list           |    5 +
 libdmapsharing/dmap-mdns-browser-avahi.c   |   25 +-
 libdmapsharing/dmap-mdns-browser-howl.c    |   12 +
 libdmapsharing/dmap-mdns-browser.h         |    4 +-
 libdmapsharing/dmap-mdns-publisher-avahi.c |   57 ++-
 libdmapsharing/dmap-mdns-publisher.h       |    4 +
 libdmapsharing/dmap-share.c                |  464 +++++++++++--
 libdmapsharing/dmap-share.h                |   25 +-
 libdmapsharing/dmap-structure.c            |   50 ++-
 libdmapsharing/dmap-structure.h            |   43 ++-
 libdmapsharing/dmap-utils.h                |   10 +
 libdmapsharing/dmap.h                      |    2 +
 tests/Makefile.am                          |    3 +
 31 files changed, 2443 insertions(+), 203 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index 3d65083..8d8ecd2 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+20 August 2010 Alexandre Rosenfeld <alexandre rosenfeld gmail com>
+
+	* Apply Alexandre Rosenfeld's DACP patch from Google Summer of
+	Code 2010.
+
 22 July 2010 W. Michael Petullo <mike flyn org>
 
 	* Remove redundancy between dmap_connection_build_message()
diff --git a/Makefile.am b/Makefile.am
index 569303f..3a1498e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,12 +2,14 @@ SUBDIRS = docs libdmapsharing tests
 DIST_SUBDIRS = docs libdmapsharing m4 media tests
 
 ### all of the standard pc files we need to generate
-pcfiles = libdmapsharing- LIBDMAPSHARING_MAJORMINOR@.pc
+# pcfiles = libdmapsharing- LIBDMAPSHARING_MAJORMINOR@.pc
+pcfiles = libdmapsharing- API_VERSION@.pc
 
 all-local: $(pcfiles)
 
 ### how to generate pc files
-%- LIBDMAPSHARING_MAJORMINOR@.pc: %.pc
+# %- LIBDMAPSHARING_MAJORMINOR@.pc: %.pc
+%- API_VERSION@.pc: %.pc
 	cp $< $@
 
 pkgconfigdir = $(libdir)/pkgconfig
diff --git a/config.h.in b/config.h.in
index 4e5b5f2..3e1f55f 100644
--- a/config.h.in
+++ b/config.h.in
@@ -15,6 +15,9 @@
 /* Define if libsoup supports SOUP_ENCODING_EOF */
 #undef HAVE_ENCODING_EOF
 
+/* Define if gdk-pixbuf support is enabled */
+#undef HAVE_GDKPIXBUF
+
 /* Defined when glib-2.0 was found */
 #undef HAVE_GLIB
 
@@ -82,6 +85,9 @@
 /* Define to the one symbol short name of this package. */
 #undef PACKAGE_TARNAME
 
+/* Define to the home page for this package. */
+#undef PACKAGE_URL
+
 /* Define to the version of this package. */
 #undef PACKAGE_VERSION
 
diff --git a/configure.ac b/configure.ac
index 141c479..cec7bec 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,8 +1,15 @@
-AC_INIT(libdmapsharing, 2.0.0)
+AC_INIT(libdmapsharing, 2.1.0)
 
 dnl when going to/from release please set the nano (fourth number) right !
 dnl releases only do Wall, cvs and prerelease does Werror too
-AS_VERSION(libdmapsharing, LIBDMAPSHARING, 2, 0, 0, LIBDMAPSHARING_CVS="no", LIBDMAPSHARING_CVS="yes")
+AS_VERSION(libdmapsharing, LIBDMAPSHARING, 2, 1, 0, LIBDMAPSHARING_CVS="no", LIBDMAPSHARING_CVS="yes")
+
+dnl FIXME:
+dnl would like to automate this off the above definitions, but API might be 2.2 with version 2.1.
+dnl see also Makefile.am, libdmapsharing/Makefile.am and libdmapsharing.pc.in
+API_VERSION=2.2
+#AC_DEFINE(API_VERSION, $API_VERSION, [library API version])
+AC_SUBST(API_VERSION)
 
 AM_INIT_AUTOMAKE
 
@@ -105,6 +112,22 @@ dnl make GOBJECT_CFLAGS and GOBJECT_LIBS available
 AC_SUBST(GOBJECT_CFLAGS)
 AC_SUBST(GOBJECT_LIBS)
 
+dnl Check for gdk-pixbuf, needed for DACP Now Playing Artwork
+PKG_CHECK_MODULES(GDKPIXBUF, gdk-pixbuf-2.0,
+  HAVE_GDKPIXBUF=yes,
+  HAVE_GDKPIXBUF=no)
+
+if test x"$HAVE_GDKPIXBUF" = "xyes"; then
+  AC_DEFINE(HAVE_GDKPIXBUF, 1, [Define if gdk-pixbuf support is enabled])
+else
+  AC_WARN(Gdk-pixbuf library not present, Now Playing artwork might be affected.)
+fi
+
+AM_CONDITIONAL(USE_GDKPIXBUF, test x"$HAVE_GDKPIXBUF" = "xyes")
+
+AC_SUBST(GDKPIXBUF_CFLAGS)
+AC_SUBST(GDKPIXBUF_LIBS)
+
 AC_ARG_WITH(mdns,
    AC_HELP_STRING([--with-mdns=auto|howl|avahi|dns_sd],
    [Select the mDNS/DNS-SD implementation to use (default auto)]),
diff --git a/docs/libdmapsharing-docs.sgml b/docs/libdmapsharing-docs.sgml
index 9fd9c39..1cd2ee4 100644
--- a/docs/libdmapsharing-docs.sgml
+++ b/docs/libdmapsharing-docs.sgml
@@ -51,6 +51,7 @@
     <xi:include href="xml/dpap-connection.xml"/>
     <xi:include href="xml/dpap-record.xml"/>
     <xi:include href="xml/dpap-share.xml"/>
+    <xi:include href="xml/dacp-share.xml"/>
   </chapter>
 
   <chapter id="object-tree">
diff --git a/libdmapsharing.pc.in b/libdmapsharing.pc.in
index 47412fd..c95a5cf 100644
--- a/libdmapsharing.pc.in
+++ b/libdmapsharing.pc.in
@@ -8,4 +8,4 @@ Description: libdmapsharing
 Version: @VERSION@
 Requires: glib-2.0 libsoup-2.4
 Libs: -L${libdir} -ldmapsharing
-Cflags: -I${includedir}/libdmapsharing- LIBDMAPSHARING_MAJORMINOR@
+Cflags: -I${includedir}/libdmapsharing- API_VERSION@
diff --git a/libdmapsharing/Makefile.am b/libdmapsharing/Makefile.am
index 920648e..6a589d4 100644
--- a/libdmapsharing/Makefile.am
+++ b/libdmapsharing/Makefile.am
@@ -1,11 +1,13 @@
 lib_LTLIBRARIES = libdmapsharing.la
 
-BUILT_SOURCES = dmap-marshal.c dmap-marshal.h
+BUILT_SOURCES = dmap-marshal.c dmap-marshal.h dmap-enums.c dmap-enums.h
 
 libdmapsharing_la_SOURCES = \
 	$(BUILT_SOURCES) \
 	daap-record.c \
 	daap-share.c \
+	dacp-share.c \
+	dacp-player.c \
 	dmap-connection.c \
 	dmap-container-db.c \
 	dmap-container-record.c \
@@ -45,6 +47,7 @@ endif
 libdmapsharing_la_CFLAGS = \
 	-DG_LOG_DOMAIN=\"libdmapsharing\" \
 	-I$(top_srcdir) \
+	$(GDKPIXBUF_CFLAGS) \
 	$(SOUP_CFLAGS) \
 	$(GSTREAMERAPP_CFLAGS) \
 	$(MDNS_CFLAGS)
@@ -54,16 +57,19 @@ libdmapsharing_la_LDFLAGS = \
 	$(GLIB_LIBS) \
 	$(GTHREAD_LIBS) \
 	$(GOBJECT_LIBS) \
+	$(GDKPIXBUF_LIBS) \
 	$(MDNS_LIBS) \
 	$(GSTREAMERAPP_LIBS) \
 	$(SOUP_LIBS)
 
 libdmapsharingincludedir = \
-	$(includedir)/libdmapsharing- LIBDMAPSHARING_MAJORMINOR@/libdmapsharing
+	$(includedir)/libdmapsharing- API_VERSION@/libdmapsharing
 
 libdmapsharinginclude_HEADERS = \
 	daap-record.h \
 	daap-share.h \
+	dacp-share.h \
+	dacp-player.h \
 	dmap.h \
 	dmap-connection.h \
 	dmap-container-db.h \
@@ -79,6 +85,7 @@ libdmapsharinginclude_HEADERS = \
 	dpap-share.h
 
 noinst_HEADERS = \
+	dmap-enums.h \
 	dmap-marshal.h \
 	dmap-config.h \
 	dmap-mdns-avahi.h \
@@ -94,6 +101,26 @@ dmap-marshal.c: dmap-marshal.list
 
 dmap-marshal.h: dmap-marshal.list
 	@glib-genmarshal --prefix=dmap_marshal $(srcdir)/dmap-marshal.list --header > dmap-marshal.h
+	
+dmap-enums.h: $(libdmapsharinginclude_HEADERS) Makefile
+	@glib-mkenums \
+			--fhead "/* Automatically generated by glib-mkenums */\n\n#ifndef DMAP_ENUMS_H\n#define DMAP_ENUMS_H\n\n#include <glib-object.h>\n\nG_BEGIN_DECLS\n\n" \
+			--fprod "\n/* enumerations from \"@filename \" */\n" \
+			--vhead "GType @enum_name _get_type (void) G_GNUC_CONST;\n#define TYPE_ ENUMNAME@ (@enum_name _get_type())\n" \
+			--ftail "G_END_DECLS\n\n#endif /* !DMAP_ENUMS_H */" $(addprefix $(srcdir)/,$(libdmapsharinginclude_HEADERS)) > $  tmp \
+	&& sed -e "s/d_map/dmap/" -e "s/D_MAP/DMAP/" -e "s/d_acp/dacp/" -e "s/D_ACP/DACP/" $  tmp > $@ \
+	&& rm -f $  tmp
+
+dmap-enums.c: $(libdmapsharinginclude_headers) Makefile dmap-enums.h
+	@glib-mkenums \
+			--fhead "/* Automatically generated by glib-mkenums */\n\n#include \"dacp-share.h\"\n#include \"dacp-player.h\"\n#include \"dmap-mdns-publisher.h\"\n#include \"dmap-mdns-browser.h\"\n#include \"dmap-connection.h\"\n#include \"dmap-enums.h\"" \
+			--fprod "\n/* enumerations from \"@filename \" */" \
+			--vhead "GType\n enum_name@_get_type (void)\n{\n  static GType etype = 0;\n  if (etype == 0) {\n    static const G Type@Value values[] = {" \
+			--vprod "      { @VALUENAME@, \"@VALUENAME \", \"@valuenick \" }," \
+			--vtail "      { 0, NULL, NULL }\n    };\n    etype = g_ type@_register_static (\"@EnumName \", values);\n  }\n  return etype;\n}\n" \
+		$(addprefix $(srcdir)/,$(libdmapsharinginclude_HEADERS)) > $  tmp \
+	&& sed -e "s/d_map/dmap/" -e "s/D_MAP/DMAP/" -e "s/d_acp/dacp/" -e "s/D_ACP/DACP/" $  tmp > $@ \
+	&& rm -f $  tmp
 
 CLEANFILES = $(BUILT_SOURCES)
 
diff --git a/libdmapsharing/daap-record.c b/libdmapsharing/daap-record.c
index 54e764b..bfc5f06 100644
--- a/libdmapsharing/daap-record.c
+++ b/libdmapsharing/daap-record.c
@@ -30,6 +30,17 @@ daap_record_init (DAAPRecordInterface *iface)
         daap_record_init_count++;
 
 	if (! is_initialized) {
+
+		g_object_interface_install_property (iface,
+			g_param_spec_int ("itemid",
+			                  "Item ID",
+			                  "Item ID",
+			                  0,
+			                  G_MAXINT,
+			                  0,
+			                  G_PARAM_READWRITE));
+
+		
 		g_object_interface_install_property (iface,
 			g_param_spec_string ("location",
 					     "URI pointing to song data",
@@ -44,16 +55,35 @@ daap_record_init (DAAPRecordInterface *iface)
 					     "Unknown",
 					     G_PARAM_READWRITE));
 
-		/* NOTE: experimenting with matching property name with
-		 * DAAP protocol keyword to make some code simpler. */
+		//FIXME: This is actually an enum
+		g_object_interface_install_property (iface,
+			g_param_spec_int ("mediakind",
+			                  "Media kind",
+			                  "Media kind",
+			                  0,
+			                  G_MAXINT,
+			                  1,
+			                  G_PARAM_READWRITE));
+
+		/* NOTE: the name must match the part after the last dot of the
+		   DAAP name, so daap.songalbum becomes songalbum and so on.*/
 		g_object_interface_install_property (iface,
-			g_param_spec_string ("daap.songalbum",
+			g_param_spec_string ("songalbum",
 					     "Album name",
 					     "Album name",
 					     "Unknown",
 					     G_PARAM_READWRITE));
 
 		g_object_interface_install_property (iface,
+			g_param_spec_int64 ("songalbumid",
+			                    "Album id",
+			                    "Album id",
+			                    G_MININT64,
+			                    G_MAXINT64,
+			                    0,
+			                    G_PARAM_READWRITE));
+
+		g_object_interface_install_property (iface,
 			g_param_spec_string ("sort-album",
 					     "Album sort name",
 					     "Album sort name",
@@ -61,7 +91,7 @@ daap_record_init (DAAPRecordInterface *iface)
 					     G_PARAM_READWRITE));
 
 		g_object_interface_install_property (iface,
-			g_param_spec_string ("daap.songartist",
+			g_param_spec_string ("songartist",
 					     "Song artist",
 					     "Song artist",
 					     "Unknown",
@@ -75,7 +105,7 @@ daap_record_init (DAAPRecordInterface *iface)
 					     G_PARAM_READWRITE));
 
 		g_object_interface_install_property (iface,
-			g_param_spec_string ("daap.songgenre",
+			g_param_spec_string ("songgenre",
 					     "Song genre",
 					     "Song genre",
 					     "Unknown",
@@ -216,3 +246,29 @@ daap_record_read (DAAPRecord *record, GError **err)
 {
 	return DAAP_RECORD_GET_INTERFACE (record)->read (record, err);
 }
+
+gint 
+daap_record_cmp_by_album (DAAPRecord *a, DAAPRecord *b) 
+{
+	gchar *album_a, *album_b;
+	gchar *sort_album_a, *sort_album_b;
+	gint track_a, track_b;
+	gint ret;
+	g_object_get (a, "songalbum", &album_a, "sort-album", &sort_album_a, "track", &track_a, NULL);
+	g_object_get (b, "songalbum", &album_b, "sort-album", &sort_album_b, "track", &track_b, NULL);
+	if (sort_album_a && sort_album_b)
+		ret = g_strcmp0 (sort_album_a, sort_album_b);
+	else
+		ret = g_strcmp0 (album_a, album_b);
+	if (ret == 0) {
+		if (track_a < track_b) 
+			ret = -1;
+		else 
+			ret = (track_a == track_b) ? 0 : 1;
+	}
+	g_free (album_a);
+	g_free (album_b);
+	g_free (sort_album_a);
+	g_free (sort_album_b);
+	return ret;
+}
\ No newline at end of file
diff --git a/libdmapsharing/daap-record.h b/libdmapsharing/daap-record.h
index e1b8a57..7c8696e 100644
--- a/libdmapsharing/daap-record.h
+++ b/libdmapsharing/daap-record.h
@@ -93,6 +93,15 @@ gboolean      daap_record_itunes_compat (DAAPRecord *record);
  */
 GInputStream *daap_record_read          (DAAPRecord *record, GError **err);
 
+/**
+ * daap_record_cmp_by_album:
+ * @a: first DAAPRecord.
+ * @b: second DAAPRecord.
+ *
+ * Compares two records according to album. Suitable to sort lists of albums.
+ */ 
+gint daap_record_cmp_by_album (DAAPRecord *a, DAAPRecord *b);
+
 #endif /* __DAAP_RECORD_H */
 
 G_END_DECLS
diff --git a/libdmapsharing/daap-share.c b/libdmapsharing/daap-share.c
index cd423b9..3d5cccd 100644
--- a/libdmapsharing/daap-share.c
+++ b/libdmapsharing/daap-share.c
@@ -540,18 +540,27 @@ add_entry_to_mlcl (gpointer id,
 
 	if (_dmap_share_client_requested (mb->bits, ITEM_KIND))
 		dmap_structure_add (mlit, DMAP_CC_MIKD, (gchar) DAAP_ITEM_KIND_AUDIO);
-	if (_dmap_share_client_requested (mb->bits, ITEM_ID))
-		dmap_structure_add (mlit, DMAP_CC_MIID, GPOINTER_TO_UINT (id));
+	if (_dmap_share_client_requested (mb->bits, ITEM_ID)) {
+		gint itemid;
+		g_object_get (record, "itemid", &itemid, NULL);
+		dmap_structure_add (mlit, DMAP_CC_MIID, (guint) itemid);
+	}
 	if (_dmap_share_client_requested (mb->bits, ITEM_NAME)) {
 		gchar *title;
 		g_object_get (record, "title", &title, NULL);
 		dmap_structure_add (mlit, DMAP_CC_MINM, title);
 		g_free (title);
 	}
-	if (_dmap_share_client_requested (mb->bits, PERSISTENT_ID))
-		dmap_structure_add (mlit, DMAP_CC_MPER, GPOINTER_TO_UINT (id));
-	if (_dmap_share_client_requested (mb->bits, CONTAINER_ITEM_ID))
-		dmap_structure_add (mlit, DMAP_CC_MCTI, GPOINTER_TO_UINT (id));
+	if (_dmap_share_client_requested (mb->bits, PERSISTENT_ID)) {
+		gint itemid;
+		g_object_get (record, "itemid", &itemid, NULL);
+		dmap_structure_add (mlit, DMAP_CC_MPER, (guint) itemid);
+	}
+	if (_dmap_share_client_requested (mb->bits, CONTAINER_ITEM_ID)) {
+		gint itemid;
+		g_object_get (record, "itemid", &itemid, NULL);
+		dmap_structure_add (mlit, DMAP_CC_MCTI, (guint) itemid);
+	}
 	if (_dmap_share_client_requested (mb->bits, SONG_DATA_KIND))
 		dmap_structure_add (mlit, DMAP_CC_ASDK, (gchar) DAAP_SONG_DATA_KIND_NONE);
 	/* FIXME: Any use for this?
@@ -560,7 +569,7 @@ add_entry_to_mlcl (gpointer id,
 	*/
 	if (_dmap_share_client_requested (mb->bits, SONG_ALBUM)) {
 		gchar *album;
-		g_object_get (record, "daap.songalbum", &album, NULL);
+		g_object_get (record, "songalbum", &album, NULL);
 		dmap_structure_add (mlit, DMAP_CC_ASAL, album);
 		g_free (album);
 	}
@@ -568,7 +577,7 @@ add_entry_to_mlcl (gpointer id,
 		dmap_structure_add (mlit, DMAP_CC_AGRP, "");
 	if (_dmap_share_client_requested (mb->bits, SONG_ARTIST)) {
 		gchar *artist;
-		g_object_get (record, "daap.songartist", &artist, NULL);
+		g_object_get (record, "songartist", &artist, NULL);
 		dmap_structure_add (mlit, DMAP_CC_ASAR, artist);
 		g_free (artist);
 	}
@@ -618,7 +627,7 @@ add_entry_to_mlcl (gpointer id,
 	}
 	if (_dmap_share_client_requested (mb->bits, SONG_GENRE)) {
 		gchar *genre;
-		g_object_get (record, "daap.songgenre", &genre, NULL);
+		g_object_get (record, "songgenre", &genre, NULL);
 		dmap_structure_add (mlit, DMAP_CC_ASGN, genre);
 		g_free (genre);
 	}
@@ -688,7 +697,9 @@ static void
 genre_tabulator (gpointer id, DMAPRecord *record, GHashTable *ht)
 {
 	const gchar *genre;
-	g_object_get (record, "daap.songgenre", &genre, NULL);
+	g_object_get (record, "songgenre", &genre, NULL);
+	if (!genre)
+		return;
 	if (! g_hash_table_lookup (ht, genre))
 		g_hash_table_insert (ht, (gchar *) genre, NULL);
 }
@@ -697,7 +708,9 @@ static void
 artist_tabulator (gpointer id, DMAPRecord *record, GHashTable *ht)
 {
 	const gchar *artist;
-	g_object_get (record, "daap.songartist", &artist, NULL);
+	g_object_get (record, "songartist", &artist, NULL);
+	if (!artist)
+		return;
 	if (! g_hash_table_lookup (ht, artist))
 		g_hash_table_insert (ht, (gchar *) artist, NULL);
 }
@@ -706,13 +719,15 @@ static void
 album_tabulator (gpointer id, DMAPRecord *record, GHashTable *ht)
 {
 	const gchar *album;
-	g_object_get (record, "daap.songalbum", &album, NULL);
+	g_object_get (record, "songalbum", &album, NULL);
+	if (!album)
+		return;
 	if (! g_hash_table_lookup (ht, album))
 		g_hash_table_insert (ht, (gchar *) album, NULL);
 }
 
 static void
-add_to_category_listing (gpointer key, gpointer value, gpointer user_data)
+add_to_category_listing (gpointer key, gpointer user_data)
 {
 	GNode *mlit;
 	GNode *node = (GNode *) user_data;
@@ -748,6 +763,7 @@ databases_browse_xxx (DMAPShare *share,
 	const gchar *browse_category;
 	GHashTable *category_items;
 	DMAPContentCode category_cc;
+	GList *values;
 
 	rest_of_path = strchr (path + 1, '/');
 	browse_category = rest_of_path + 10;
@@ -783,9 +799,15 @@ databases_browse_xxx (DMAPShare *share,
 
 	node = dmap_structure_add (abro, category_cc);
 
-	g_hash_table_foreach (category_items,
-			      add_to_category_listing,
-			      node);
+	values = g_hash_table_get_keys (category_items);
+	if (g_hash_table_lookup (query, "include-sort-headers")) {
+		g_debug ("Sorting...");
+		values = g_list_sort (values, (GCompareFunc) g_ascii_strcasecmp);
+	}
+
+	g_list_foreach (values, add_to_category_listing, node);
+	
+	g_list_free (values);
 
 	_dmap_share_message_set_from_dmap_structure (share, msg, abro);
 	dmap_structure_destroy (abro);
diff --git a/libdmapsharing/dacp-player.c b/libdmapsharing/dacp-player.c
new file mode 100644
index 0000000..67213a6
--- /dev/null
+++ b/libdmapsharing/dacp-player.c
@@ -0,0 +1,150 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+ * libdmapsharing
+ * Copyright (C) Alexandre Rosenfeld 2010 <alexandre rosenfeld gmail com>
+ * 
+ * libdmapsharing 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 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * libdmapsharing 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <libdmapsharing/dacp-player.h>
+#include <libdmapsharing/dmap-enums.h>
+#include <libdmapsharing/daap-record.h>
+
+static void
+dacp_player_init (DACPPlayerInterface *iface)
+{
+	static gboolean initialized = FALSE;
+
+	
+	//FIXME Remove debug message
+	g_debug ("DACPPlayer initializing: %d", initialized);
+
+	if (!initialized) {
+		initialized = TRUE;
+
+		g_object_interface_install_property (iface,
+			g_param_spec_ulong ("playing-time",
+			                    "Playing time",
+			                    "Playing time (ms)",
+			                    0,
+			                    G_MAXULONG,
+			                    0,
+			                    G_PARAM_READWRITE));
+
+		g_object_interface_install_property (iface,
+			g_param_spec_boolean ("shuffle-state",
+			                      "Shuffle state",
+			                      "Shufle state",
+			                      FALSE,
+			                      G_PARAM_READWRITE));
+
+		g_object_interface_install_property (iface,
+			g_param_spec_enum ("repeat-state",
+			                   "Repeat state",
+			                   "Repeat state",
+			                   TYPE_DACP_REPEAT_STATE,
+			                   REPEAT_NONE,
+			                   G_PARAM_READWRITE));
+
+		g_object_interface_install_property (iface,
+			g_param_spec_enum ("play-state",
+			                   "Play state",
+			                   "Play state",
+			                   TYPE_DACP_PLAY_STATE,
+			                   PLAY_STOPPED,
+			                   G_PARAM_READWRITE));
+		
+		g_object_interface_install_property (iface,
+			g_param_spec_ulong ("volume",
+			                    "Volume",
+			                    "Volume",
+			                    0,
+			                    100,
+			                    0,
+			                    G_PARAM_READWRITE));
+	}
+}
+
+static void
+dacp_player_finalize (DACPPlayerInterface *iface)
+{
+	//FIXME Remove debug message
+	g_debug ("DACPPlayer finalizing");
+}
+
+GType
+dacp_player_get_type (void)
+{
+	static GType object_type = 0;
+	if (!object_type) {
+		static const GTypeInfo object_info = {
+			sizeof(DACPPlayerInterface),
+			(GBaseInitFunc) dacp_player_init,
+			(GBaseFinalizeFunc) dacp_player_finalize
+		};
+		object_type = g_type_register_static(G_TYPE_INTERFACE,
+		                                     "DACPPlayer",
+		                                     &object_info, 0);
+	}
+	return object_type;
+}
+
+DAAPRecord *
+dacp_player_now_playing_record (DACPPlayer *player)
+{
+	return DACP_PLAYER_GET_INTERFACE (player)->now_playing_record (player);
+}
+
+gchar *
+dacp_player_now_playing_artwork (DACPPlayer *player, guint width, guint height)
+{
+	return DACP_PLAYER_GET_INTERFACE (player)->now_playing_artwork (player, width, height);
+}
+
+void 
+dacp_player_play_pause (DACPPlayer *player)
+{
+	DACP_PLAYER_GET_INTERFACE (player)->play_pause (player);
+}
+
+
+void 
+dacp_player_pause (DACPPlayer *player)
+{
+	DACP_PLAYER_GET_INTERFACE (player)->pause (player);
+}
+
+void 
+dacp_player_next_item (DACPPlayer *player)
+{
+	DACP_PLAYER_GET_INTERFACE (player)->next_item (player);
+}
+
+void 
+dacp_player_prev_item (DACPPlayer *player)
+{
+	DACP_PLAYER_GET_INTERFACE (player)->prev_item (player);
+}
+
+void 
+dacp_player_cue_clear (DACPPlayer *player)
+{
+	DACP_PLAYER_GET_INTERFACE (player)->cue_clear (player);
+}
+
+void 
+dacp_player_cue_play (DACPPlayer *player, GList *records, guint index)
+{
+	DACP_PLAYER_GET_INTERFACE (player)->cue_play (player, records, index);
+}
diff --git a/libdmapsharing/dacp-player.h b/libdmapsharing/dacp-player.h
new file mode 100644
index 0000000..2486a33
--- /dev/null
+++ b/libdmapsharing/dacp-player.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+ * libdmapsharing
+ * Copyright (C) Alexandre Rosenfeld 2010 <alexandre rosenfeld gmail com>
+ * 
+ * libdmapsharing 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 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * libdmapsharing 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _DACP_PLAYER_H_
+#define _DACP_PLAYER_H_
+
+#include <glib-object.h>
+
+#include "daap-record.h"
+
+G_BEGIN_DECLS
+
+#define TYPE_DACP_PLAYER               (dacp_player_get_type ())
+#define DACP_PLAYER(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_DACP_PLAYER, DACPPlayer))
+#define IS_DACP_PLAYER(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_DACP_PLAYER))
+#define DACP_PLAYER_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), \
+                                        TYPE_DACP_PLAYER, DACPPlayerInterface))
+
+typedef struct _DACPPlayerInterface DACPPlayerInterface;
+typedef struct _DACPPlayer DACPPlayer;
+
+/**
+ * DACPRepeatState:
+ */
+typedef enum {
+	REPEAT_NONE = 0,
+	REPEAT_SINGLE = 1,
+	REPEAT_ALL = 2
+} DACPRepeatState;
+
+/**
+ * DACPPlayState:
+ */
+typedef enum {
+	PLAY_STOPPED = 2,
+	PLAY_PAUSED = 3,
+	PLAY_PLAYING = 4
+} DACPPlayState;
+
+struct _DACPPlayerInterface
+{
+	GTypeInterface parent_class;
+
+	DAAPRecord *(*now_playing_record)  (DACPPlayer *player);
+	gchar *(*now_playing_artwork)      (DACPPlayer *player, guint width, guint height);
+	void (*play_pause)                 (DACPPlayer *player);
+	void (*pause)                      (DACPPlayer *player);
+	void (*next_item)                  (DACPPlayer *player);
+	void (*prev_item)                  (DACPPlayer *player);
+
+	void (*cue_clear)                  (DACPPlayer *player);
+	void (*cue_play)                   (DACPPlayer *player, GList *records, guint index);
+};
+
+GType dacp_player_get_type (void);
+
+DAAPRecord *dacp_player_now_playing_record  (DACPPlayer *player);
+gchar      *dacp_player_now_playing_artwork (DACPPlayer *player, guint width, guint height);
+void        dacp_player_play_pause          (DACPPlayer *player);
+void        dacp_player_pause               (DACPPlayer *player);
+void        dacp_player_next_item           (DACPPlayer *player);
+void        dacp_player_prev_item           (DACPPlayer *player);
+
+void        dacp_player_cue_clear           (DACPPlayer *player);
+void        dacp_player_cue_play            (DACPPlayer *player, GList *records, guint index);
+
+G_END_DECLS
+
+#endif /* _DACP_PLAYER_H_ */
diff --git a/libdmapsharing/dacp-share.c b/libdmapsharing/dacp-share.c
new file mode 100644
index 0000000..337a2ff
--- /dev/null
+++ b/libdmapsharing/dacp-share.c
@@ -0,0 +1,1040 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Implmentation of DACP (e.g., iTunes Remote) sharing
+ *
+ * Copyright (C) 2010 Alexandre Rosenfeld <airmind gmail com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include "config.h"
+
+#include <time.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <glib.h>
+#include <glib-object.h>
+
+#ifdef HAVE_GDKPIXBUF
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#endif /* HAVE_GDKPIXBUF */
+
+#include <libsoup/soup.h>
+#include <libsoup/soup-address.h>
+#include <libsoup/soup-message.h>
+#include <libsoup/soup-uri.h>
+#include <libsoup/soup-server.h>
+
+#include <libdmapsharing/dmap.h>
+#include <libdmapsharing/dmap-marshal.h>
+#include <libdmapsharing/dmap-structure.h>
+
+#include <libdmapsharing/dmap-share.h>
+#include <libdmapsharing/dacp-share.h>
+#include <libdmapsharing/dacp-player.h>
+
+static void dacp_share_set_property  (GObject *object,
+					 guint prop_id,
+					 const GValue *value,
+					 GParamSpec *pspec);
+static void dacp_share_get_property  (GObject *object,
+					 guint prop_id,
+					 GValue *value,
+				 	 GParamSpec *pspec);
+static void dacp_share_dispose	(GObject *object);
+const char *dacp_share_get_type_of_service (DMAPShare *share);
+void dacp_share_ctrl_int (DMAPShare *share,
+		      SoupServer        *server,
+		      SoupMessage       *message,
+		      const char        *path,
+		      GHashTable        *query,
+		      SoupClientContext *context);
+void dacp_share_login (DMAPShare *share,
+		  SoupServer        *server,
+		  SoupMessage       *message,
+		  const char        *path,
+		  GHashTable        *query,
+		  SoupClientContext *context);
+
+static gchar *dacp_share_pairing_code(DACPShare *share, gchar* pair_txt, gchar passcode[4]);
+static void dacp_share_send_playstatusupdate (DACPShare *share);
+static void dacp_share_fill_playstatusupdate (DACPShare *share, SoupMessage *message);
+
+#define DACP_TYPE_OF_SERVICE "_touch-able._tcp"
+#define DACP_PORT 3689
+
+struct DACPSharePrivate {
+	DMAPMdnsBrowser *mdns_browser;
+
+	gchar *library_name;
+	GHashTable *remotes;
+
+	gint current_revision;
+
+	GSList *update_queue;
+
+	DACPPlayer *player;
+};
+
+/*
+ * Internal representation of a DACP remote.
+ */
+typedef struct {
+	gchar *host;
+	guint port;
+	gchar *pair_txt;
+	DMAPConnection *connection;
+} DACPRemoteInfo;
+
+#define DACP_SHARE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TYPE_DACP_SHARE, DACPSharePrivate))
+
+enum {
+	PROP_0,
+	PROP_LIBRARY_NAME,
+	PROP_PLAYER
+};
+
+enum {
+	REMOTE_FOUND,
+	REMOTE_LOST,
+	REMOTE_PAIRED,
+
+	LOOKUP_GUID,
+	ADD_GUID,
+
+	LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE (DACPShare, dacp_share, TYPE_DAAP_SHARE)
+
+static void
+dacp_share_class_init (DACPShareClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	DMAPShareClass *dmap_class = DMAP_SHARE_CLASS (object_class);
+
+	object_class->get_property = dacp_share_get_property;
+	object_class->set_property = dacp_share_set_property;
+	object_class->dispose = dacp_share_dispose;
+
+	dmap_class->get_type_of_service  = dacp_share_get_type_of_service;
+	dmap_class->ctrl_int = dacp_share_ctrl_int;
+	dmap_class->login = dacp_share_login;
+
+	g_object_class_install_property (object_class,
+	                                 PROP_LIBRARY_NAME,
+	                                 g_param_spec_string ("library-name",
+	                                                      "Library Name",
+	                                                      "Library name as will be shown in the Remote",
+	                                                      NULL,
+	                                                      G_PARAM_READWRITE));
+
+	g_object_class_install_property (object_class,
+	                                 PROP_PLAYER,
+	                                 g_param_spec_object ("player",
+	                                                      "Player",
+	                                                      "Player",
+	                                                      G_TYPE_OBJECT,
+	                                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	
+	/**
+	 * DACPShare::remote-found
+	 * @share: the #DACPShare that received the signal.
+	 * @service_name: the remote identifier.
+	 * @remote_name: the remote friendly name.
+	 *
+	 * Signal emited when a remote is found in the local network.
+	 */
+	signals [REMOTE_FOUND] =
+		g_signal_new ("remote-found",
+			      G_TYPE_FROM_CLASS (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (DACPShareClass, remote_found),
+			      NULL,
+			      NULL,
+			      dmap_marshal_VOID__STRING_STRING,
+			      G_TYPE_NONE,
+			      2, G_TYPE_STRING, G_TYPE_STRING);
+			      
+	/**
+	 * DACPShare::remote-lost
+	 * @share: the #DACPShare that received the signal
+	 * @service_name: the remote identifier.
+	 *
+	 * Signal emited when a remote is lost in the local network.
+	 */
+	signals [REMOTE_LOST] =
+		g_signal_new ("remote-lost",
+			      G_TYPE_FROM_CLASS (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (DACPShareClass, remote_lost),
+			      NULL,
+			      NULL,
+			      g_cclosure_marshal_VOID__STRING,
+			      G_TYPE_NONE,
+			      1, G_TYPE_STRING);
+
+	/**
+	 * DACPShare::remote-paired
+	 * @share: the #DACPShare that received the signal
+	 * @service_name: the remote identifier.
+	 * @connected: indicates if the connection was succesfull or not.
+	 *
+	 * Signal emited when a remote is paired.
+	 */
+	signals [REMOTE_PAIRED] =
+		g_signal_new ("remote-paired",
+			      G_TYPE_FROM_CLASS (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (DACPShareClass, remote_paired),
+			      NULL,
+			      NULL,
+			      dmap_marshal_VOID__STRING_BOOLEAN,
+			      G_TYPE_NONE,
+			      2, G_TYPE_STRING, G_TYPE_BOOLEAN);
+
+	/**
+	 * DACPShare::lookup-guid
+	 * @share: the #DACPShare that received the signal
+	 * @guid: a string containing the guid to be validated.
+	 *
+	 * Signal emited when the remote has logged in before and wants to be
+	 * validated. An implementation must implement this signal to lookup
+	 * for guids saved by ::add-guid
+	 */
+	signals [LOOKUP_GUID] =
+		g_signal_new ("lookup-guid",
+			      G_TYPE_FROM_CLASS (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (DACPShareClass, lookup_guid),
+			      NULL,
+			      NULL,
+			      dmap_marshal_BOOLEAN__STRING,
+			      G_TYPE_BOOLEAN,
+			      1, G_TYPE_STRING);
+
+	/**
+	 * DACPShare::add-guid
+	 * @share: the #DACPShare that received the signal
+	 * @guid: a string containing the guid to be saved.
+	 *
+	 * Signal emited when the remote wants to log in and save a special guid
+	 * which will be used later when it wants to reconnect. With this guid,
+	 * we know that this remote has connected before, thus this signal must
+	 * save somewhere all guids that connected before, so that ::lookup-guid
+	 * will find this remote. The user interface probably wants to include
+	 * a button to forget previously connected remotes, so that the user may
+	 * disconnect all previously connected remotes.
+	 */
+	signals [ADD_GUID] =
+		g_signal_new ("add-guid",
+			      G_TYPE_FROM_CLASS (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (DACPShareClass, add_guid),
+			      NULL,
+			      NULL,
+			      g_cclosure_marshal_VOID__STRING,
+			      G_TYPE_NONE,
+			      1, G_TYPE_STRING);
+	
+	g_type_class_add_private (klass, sizeof (DACPSharePrivate));
+}
+
+static void
+dacp_share_init (DACPShare *share)
+{
+	share->priv = DACP_SHARE_GET_PRIVATE (share);
+
+	share->priv->current_revision = 2;
+	
+	share->priv->remotes = g_hash_table_new_full ((GHashFunc)g_str_hash,
+	                                              (GEqualFunc)g_str_equal,
+	                                              (GDestroyNotify)g_free,
+	                                              (GDestroyNotify)g_free);
+}
+
+static gchar *
+get_dbid (void) 
+{
+	static gchar *dbid;
+	if (!dbid) {
+		GString *name;
+		// Creates a service name 14 characters long concatenating the hostname
+		// hash hex value with itself.
+		// Idea taken from stereo.
+		name = g_string_new (NULL);
+		g_string_printf (name, "%.8x", g_str_hash(g_get_host_name ()));
+		g_string_ascii_up (name);
+		g_string_append_len (name, name->str, 4);
+
+		dbid = name->str;
+		
+		g_string_free (name, FALSE);
+	}
+	return dbid;
+}
+
+static void
+dacp_share_update_txt_records (DACPShare *share)
+{
+	gchar *dbid_record;
+	gchar *library_name_record;
+
+	library_name_record = g_strdup_printf ("CtlN=%s", share->priv->library_name);
+	dbid_record = g_strdup_printf("DbId=%s", get_dbid());
+	
+	gchar *txt_records[] = {"Ver=131073", 
+	                        "DvSv=2049",
+	                        dbid_record,
+	                        "DvTy=iTunes",
+	                        "OSsi=0x1F6",
+	                        "txtvers=1",
+	                        library_name_record,
+	                        NULL};
+
+	g_object_set (share, "txt-records", txt_records, NULL);
+
+	g_free (dbid_record);
+	g_free (library_name_record);
+}
+
+static void
+dacp_share_set_property (GObject *object,
+			    guint prop_id,
+			    const GValue *value,
+			    GParamSpec *pspec)
+{
+	DACPShare *share = DACP_SHARE (object);
+
+	switch (prop_id) {
+	case PROP_LIBRARY_NAME:
+		g_free (share->priv->library_name);
+		share->priv->library_name = g_value_dup_string (value);
+		dacp_share_update_txt_records (share);
+		break;
+	case PROP_PLAYER:
+		if (share->priv->player)
+			g_object_unref (share->priv->player);
+		share->priv->player = DACP_PLAYER (g_value_dup_object (value));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+dacp_share_get_property (GObject *object,
+			    guint prop_id,
+			    GValue *value,
+			    GParamSpec *pspec)
+{
+	DACPShare *share = DACP_SHARE (object);
+
+	switch (prop_id) {
+	case PROP_LIBRARY_NAME:
+		g_value_set_string (value, share->priv->library_name);
+		break;
+	case PROP_PLAYER:
+		g_value_set_object (value, G_OBJECT (share->priv->player));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+dacp_share_dispose (GObject *object)
+{
+	DACPShare *share = DACP_SHARE (object);
+
+	g_free (share->priv->library_name);
+
+	if (share->priv->mdns_browser)
+		g_object_unref (share->priv->mdns_browser);
+
+	if (share->priv->player)
+		g_object_unref (share->priv->player);
+
+	g_slist_free (share->priv->update_queue);
+	
+	g_hash_table_destroy (share->priv->remotes);
+}
+
+void 
+mdns_remote_added (DMAPMdnsBrowser *browser, 
+                   DMAPMdnsBrowserService *service,
+                   DACPShare *share) 
+{
+	DACPRemoteInfo *remote_info;
+	
+	remote_info = g_new (DACPRemoteInfo, 1);
+	remote_info->host = g_strdup (service->host);
+	remote_info->port = service->port;
+	remote_info->connection = NULL;
+	remote_info->pair_txt = g_strdup (service->pair);
+	
+	g_debug ("New Remote found: %s name=%s host=%s port=%u pair=%s",
+	         service->service_name,
+	         service->name,
+	         remote_info->host,
+	         remote_info->port,
+	         remote_info->pair_txt);
+	
+	g_hash_table_insert (share->priv->remotes,
+	                     service->service_name,
+	                     remote_info);
+	
+	g_signal_emit (share, 
+	               signals [REMOTE_FOUND], 
+	               0, 
+	               service->service_name,
+	               service->name);
+}
+
+void
+mdns_remote_removed (DMAPMdnsBrowser *browser,
+                     const char *service_name,
+                     DACPShare *share)
+{
+	g_signal_emit (share,
+	               signals [REMOTE_LOST],
+	               0,
+	               service_name);
+	               
+	g_hash_table_remove (share->priv->remotes,
+	                     service_name);
+}
+
+DACPShare *
+dacp_share_new (const gchar *library_name,
+                DACPPlayer *player,
+                DMAPDb *db,
+                DMAPContainerDb *container_db)
+{
+	DACPShare *share;
+	
+	g_object_ref (db);
+	g_object_ref (container_db);
+	
+	share = DACP_SHARE (g_object_new (TYPE_DACP_SHARE,
+	                                  "name", get_dbid (),
+	                                  "library-name", library_name,
+	                                  "password", NULL,
+	                                  "db", db,
+	                                  "container-db", container_db,
+	                                  "player", G_OBJECT (player),
+	                                  "transcode-mimetype", NULL,
+	                                  NULL));
+	
+	g_debug("Starting DACP server");
+	_dmap_share_server_start (DMAP_SHARE (share));
+	_dmap_share_publish_start (DMAP_SHARE (share));
+
+	return share;
+}
+
+void
+dacp_share_start_lookup (DACPShare *share) 
+{
+	GError *error;
+	
+	if (share->priv->mdns_browser) {
+		g_warning ("DACP browsing already started");
+		return;
+	}
+	
+	share->priv->mdns_browser = dmap_mdns_browser_new (DMAP_MDNS_BROWSER_SERVICE_TYPE_DACP);
+	
+	g_signal_connect_object (share->priv->mdns_browser,
+				 "service-added",
+				 G_CALLBACK (mdns_remote_added),
+				 share,
+				 0);
+	g_signal_connect_object (share->priv->mdns_browser,
+				 "service-removed",
+				 G_CALLBACK (mdns_remote_removed),
+				 share,
+				 0);
+	
+	error = NULL;
+	dmap_mdns_browser_start (share->priv->mdns_browser, &error);
+	if (error != NULL) {
+		g_warning ("Unable to start Remote lookup: %s", error->message);
+		g_error_free (error);
+	}
+}
+
+static gboolean
+remove_remotes_cb (gpointer service_name, gpointer remote_info, gpointer share)
+{
+	g_signal_emit ((DACPShare*) share,
+	               signals [REMOTE_LOST],
+	               0,
+	               (gchar *) service_name);
+	return TRUE;
+}
+
+void
+dacp_share_stop_lookup (DACPShare *share) 
+{
+	GError *error;
+	
+	if (!share->priv->mdns_browser) {
+		g_warning ("DACP browsing not started");
+		return;
+	}
+	
+	g_hash_table_foreach_remove (share->priv->remotes, remove_remotes_cb, share);
+	
+	error = NULL;
+	dmap_mdns_browser_stop (share->priv->mdns_browser, &error);
+	if (error != NULL) {
+		g_warning ("Unable to stop Remote lookup: %s", error->message);
+		g_error_free (error);
+	}
+	
+	share->priv->mdns_browser = NULL;
+}
+
+const char *
+dacp_share_get_type_of_service (DMAPShare *share)
+{
+	return DACP_TYPE_OF_SERVICE;
+}
+
+void
+dacp_share_player_updated (DACPShare *share)
+{
+	share->priv->current_revision++;
+	dacp_share_send_playstatusupdate (share);
+}
+
+static void
+status_update_message_finished (SoupMessage *message, DACPShare *share)
+{
+	share->priv->update_queue = g_slist_remove (share->priv->update_queue, message);
+	g_object_unref (message);
+}
+
+static void
+dacp_share_send_playstatusupdate (DACPShare *share)
+{
+	GSList *list;
+	SoupServer *server;
+	g_object_get (share, "server", &server, NULL);
+	for (list = share->priv->update_queue; list; list = list->next) {
+		dacp_share_fill_playstatusupdate (share, (SoupMessage*) list->data);
+		soup_server_unpause_message (server, (SoupMessage*) list->data);
+	}
+	g_slist_free (share->priv->update_queue);
+	share->priv->update_queue = NULL;
+	g_object_unref (server);
+}
+
+static void
+dacp_share_fill_playstatusupdate (DACPShare *share, SoupMessage *message)
+{
+	GNode *cmst;
+	DAAPRecord *record;
+	DACPPlayState play_state;
+	DACPRepeatState repeat_state;
+	gboolean shuffle_state;
+	guint playing_time;
+	
+	g_object_get (share->priv->player, 
+	              "play-state", &play_state,
+	              "repeat-state", &repeat_state,
+	              "shuffle-state", &shuffle_state,
+	              "playing-time", &playing_time,
+	              NULL);
+
+	record = dacp_player_now_playing_record (share->priv->player);
+
+	cmst = dmap_structure_add (NULL, DMAP_CC_CMST);
+	dmap_structure_add (cmst, DMAP_CC_MSTT, (gint32) DMAP_STATUS_OK);
+	dmap_structure_add (cmst, DMAP_CC_CMSR, share->priv->current_revision);
+	dmap_structure_add (cmst, DMAP_CC_CAPS, (gint32) play_state);
+	dmap_structure_add (cmst, DMAP_CC_CASH, shuffle_state ? 1 : 0);
+	dmap_structure_add (cmst, DMAP_CC_CARP, (gint32) repeat_state);
+	if (record) {
+		gchar *title;
+		gchar *artist;
+		gchar *album;
+		gint duration;
+		guint track_time;
+		g_object_get (record, 
+		              "title", &title,
+		              "songartist", &artist,
+		              "songalbum", &album,
+		              "duration", &duration,
+		              NULL);
+		track_time = duration * 1000;
+		//dmap_structure_add (cmst, DMAP_CC_CAVC, 1);
+		dmap_structure_add (cmst, DMAP_CC_CAAS, 2);
+		dmap_structure_add (cmst, DMAP_CC_CAAR, 6);
+		dmap_structure_add (cmst, DMAP_CC_CANP, (gint64) 0);
+		if (title)
+			dmap_structure_add (cmst, DMAP_CC_CANN, title);
+		if (artist)
+			dmap_structure_add (cmst, DMAP_CC_CANA, artist);
+		if (album)
+			dmap_structure_add (cmst, DMAP_CC_CANL, album);
+		dmap_structure_add (cmst, DMAP_CC_CANG, "");
+		dmap_structure_add (cmst, DMAP_CC_ASAI, 0);
+		//dmap_structure_add (cmst, DMAP_CC_AEMK, 1);
+		g_debug ("Playing time: %u, Track time: %u", playing_time, track_time);
+		dmap_structure_add (cmst, DMAP_CC_CANT, track_time - playing_time);
+		dmap_structure_add (cmst, DMAP_CC_CAST, track_time);
+
+		g_free (title);
+		g_free (artist);
+		g_free (album);
+
+		g_object_unref (record);
+	}
+	
+	_dmap_share_message_set_from_dmap_structure (DMAP_SHARE (share), message, cmst);
+	dmap_structure_destroy (cmst);
+}
+
+static void
+debug_param (gpointer key, gpointer val, gpointer user_data)
+{
+        g_debug ("%s %s", (char *) key, (char *) val);
+}
+
+void
+dacp_share_login (DMAPShare *share,
+	  SoupServer        *server,
+	  SoupMessage       *message,
+	  const char        *path,
+	  GHashTable        *query,
+	  SoupClientContext *context)
+{
+	gchar *pairing_guid;
+	
+	
+	g_debug ("(DACP) Path is %s.", path);
+	if (query) {
+		g_hash_table_foreach (query, debug_param, NULL);
+	}
+
+	pairing_guid = g_hash_table_lookup (query, "pairing-guid");
+
+	if (pairing_guid != NULL) {
+		gboolean allow_login;
+
+		g_signal_emit (share, signals [LOOKUP_GUID], 0, pairing_guid, &allow_login);
+
+		if (!allow_login) {
+			g_debug ("Unknown remote trying to connect");
+			soup_message_set_status (message, SOUP_STATUS_FORBIDDEN);
+			return;
+		}
+	}
+	
+	_dmap_share_login (share, server, message, path, query, context);
+}
+
+void
+dacp_share_ctrl_int (DMAPShare *share,
+		      SoupServer        *server,
+		      SoupMessage       *message,
+		      const char        *path,
+		      GHashTable        *query,
+		      SoupClientContext *context)
+{
+	const char *rest_of_path;
+
+	DACPShare *dacp_share = DACP_SHARE (share);
+	
+	g_debug ("Path is %s.", path);
+	if (query) {
+		g_hash_table_foreach (query, debug_param, NULL);
+	}
+		
+	rest_of_path = strchr (path + 1, '/');
+
+	/* If calling /ctrl-int without args, the client doesnt need a 
+	   session-id, otherwise it does and it should be validated. */
+	if ((rest_of_path != NULL) && (! _dmap_share_session_id_validate (share, context, message, query, NULL))) {
+		soup_message_set_status (message, SOUP_STATUS_FORBIDDEN);
+		return;
+	}
+
+	if (rest_of_path == NULL) {
+	/* CACI control-int
+	 * 	MSTT status
+	 * 	MUTY update type
+	 * 	MTCO specified total count
+	 * 	MRCO returned count
+	 * 	MLCL listing
+	 * 		MLIT listing item
+	 * 			MIID item id
+	 * 			CMIK Unknown (TRUE)
+	 * 			CMSP Unknown (TRUE)
+	 * 			CMSV Unknown (TRUE)
+	 * 			CASS Unknown (TRUE)
+	 * 			CASU Unknown (TRUE)
+	 * 			CASG Unknown (TRUE)
+	 */
+	
+		GNode *caci;
+		GNode *mlcl;
+		GNode *mlit;
+	
+		// dacp.controlint
+		caci = dmap_structure_add (NULL, DMAP_CC_CACI);
+		// dmap.status
+		dmap_structure_add (caci, DMAP_CC_MSTT, (gint32) DMAP_STATUS_OK);
+		// dmap.updatetype
+		dmap_structure_add (caci, DMAP_CC_MUTY, 0);
+		// dmap.specifiedtotalcount
+		dmap_structure_add (caci, DMAP_CC_MTCO, (gint32) 1);
+		// dmap.returnedcount
+		dmap_structure_add (caci, DMAP_CC_MRCO, (gint32) 1);
+		// dmap.listing
+		mlcl = dmap_structure_add (caci, DMAP_CC_MLCL);
+		// dmap.listingitem
+		mlit = dmap_structure_add (mlcl, DMAP_CC_MLIT);
+		// dmap.itemid
+		dmap_structure_add (mlit, DMAP_CC_MIID, (gint32) 1);
+		// Unknown (TRUE)
+		dmap_structure_add (mlit, DMAP_CC_CMIK, (gint32) 1);
+		// Unknown (TRUE)
+		dmap_structure_add (mlit, DMAP_CC_CMSP, (gint32) 1);
+		// Unknown (TRUE)
+		dmap_structure_add (mlit, DMAP_CC_CMSV, (gint32) 1);
+		// Unknown (TRUE)
+		dmap_structure_add (mlit, DMAP_CC_CASS, (gint32) 1);
+		// Unknown (TRUE)
+		dmap_structure_add (mlit, DMAP_CC_CASU, (gint32) 1);
+		// Unknown (TRUE)
+		dmap_structure_add (mlit, DMAP_CC_CASG, (gint32) 1);
+
+		_dmap_share_message_set_from_dmap_structure (share, message, caci);
+		dmap_structure_destroy (caci);
+	} else if (g_ascii_strcasecmp ("/1/getproperty", rest_of_path) == 0) {
+		gchar *properties_query, **properties, **property;
+		GNode *cmgt;
+		
+		properties_query = g_hash_table_lookup (query, "properties");
+		
+		if (!properties_query) {
+			g_warning ("No property specified");
+			return;
+		}
+
+		cmgt = dmap_structure_add (NULL, DMAP_CC_CMGT);
+		dmap_structure_add (cmgt, DMAP_CC_MSTT, DMAP_STATUS_OK);
+		
+		properties = g_strsplit (properties_query, ",", -1);
+		for (property = properties; *property; property++) {
+			if (g_ascii_strcasecmp (*property, "dmcp.volume") == 0) {
+				gulong volume;
+				g_object_get (dacp_share->priv->player, "volume", &volume, NULL);
+				//g_debug ("Sending volume: %lu", volume);
+				dmap_structure_add (cmgt, DMAP_CC_CMVO, volume);
+			} else {
+				g_warning ("Unhandled property %s", *property);
+			}
+		}
+
+		g_strfreev (properties);
+
+		_dmap_share_message_set_from_dmap_structure (share, message, cmgt);
+		dmap_structure_destroy (cmgt);
+	} else if (g_ascii_strcasecmp ("/1/setproperty", rest_of_path) == 0) {
+		if (g_hash_table_lookup (query, "dmcp.volume")) {
+			gdouble volume = strtod (g_hash_table_lookup (query, "dmcp.volume"), NULL);
+			g_object_set (dacp_share->priv->player, "volume", (gulong) volume, NULL);
+		}
+		soup_message_set_status (message, SOUP_STATUS_NO_CONTENT);
+	} else if (g_ascii_strcasecmp ("/1/getspeakers", rest_of_path) == 0) {
+		GNode *casp;
+		GNode *mdcl;
+		
+		casp = dmap_structure_add (NULL, DMAP_CC_CASP);
+		dmap_structure_add (casp, DMAP_CC_MSTT, (gint32) DMAP_STATUS_OK);
+		mdcl = dmap_structure_add (casp, DMAP_CC_MDCL);
+		
+		dmap_structure_add (casp, DMAP_CC_CAIA, TRUE);
+		dmap_structure_add (casp, DMAP_CC_MINM, "Computer");
+		dmap_structure_add (casp, DMAP_CC_MSMA, (gint32) 0);
+		
+		_dmap_share_message_set_from_dmap_structure (share, message, casp);
+		dmap_structure_destroy (casp);
+	} else if (g_ascii_strcasecmp ("/1/playstatusupdate", rest_of_path) == 0) {
+		gchar *revision = g_hash_table_lookup (query, "revision-number");
+		gint revision_number = atoi (revision);
+
+		if (revision_number >= dacp_share->priv->current_revision) {
+			g_object_ref (message);
+			dacp_share->priv->update_queue = g_slist_prepend (dacp_share->priv->update_queue, message);
+			g_signal_connect_object (message, 
+			                         "finished", 
+			                         G_CALLBACK (status_update_message_finished), 
+			                         dacp_share, 0);
+			soup_server_pause_message (server, message);
+		} else {
+			dacp_share_fill_playstatusupdate (dacp_share, message);
+		}
+	} else if (g_ascii_strcasecmp ("/1/playpause", rest_of_path) == 0) {
+		dacp_player_play_pause (dacp_share->priv->player);
+		soup_message_set_status (message, SOUP_STATUS_NO_CONTENT);
+	} else if (g_ascii_strcasecmp ("/1/pause", rest_of_path) == 0) {
+		dacp_player_pause (dacp_share->priv->player);
+		soup_message_set_status (message, SOUP_STATUS_NO_CONTENT);
+	} else if (g_ascii_strcasecmp ("/1/nextitem", rest_of_path) == 0) {
+		dacp_player_next_item (dacp_share->priv->player);
+		soup_message_set_status (message, SOUP_STATUS_NO_CONTENT);
+	} else if (g_ascii_strcasecmp ("/1/previtem", rest_of_path) == 0) {
+		dacp_player_prev_item (dacp_share->priv->player);
+		soup_message_set_status (message, SOUP_STATUS_NO_CONTENT);
+	} else if (g_ascii_strcasecmp ("/1/nowplayingartwork", rest_of_path) == 0) {
+		guint width = 320;
+		guint height = 320;
+		gchar *artwork_filename;
+		gchar *buffer;
+		gsize buffer_len;
+		
+		if (g_hash_table_lookup (query, "mw"))
+			width = atoi (g_hash_table_lookup (query, "mw"));
+		if (g_hash_table_lookup (query, "mh"))
+			height = atoi (g_hash_table_lookup (query, "mh"));
+		artwork_filename = dacp_player_now_playing_artwork (dacp_share->priv->player, width, height);
+		if (!artwork_filename) {
+			g_debug ("No artwork for currently playing song");
+			soup_message_set_status (message, SOUP_STATUS_NOT_FOUND);
+			return;
+		}
+#ifdef HAVE_GDKPIXBUF
+		GdkPixbuf *artwork = gdk_pixbuf_new_from_file_at_scale (artwork_filename, width, height, TRUE, NULL);
+		if (!artwork) {
+			g_debug ("Error loading image file");
+			g_free (artwork_filename);
+			soup_message_set_status (message, SOUP_STATUS_INTERNAL_SERVER_ERROR);
+			return;
+		}
+		if (!gdk_pixbuf_save_to_buffer (artwork, &buffer, &buffer_len, "png", NULL, NULL)) {
+			g_debug ("Error saving artwork to PNG");
+			g_object_unref (artwork);
+			g_free (artwork_filename);
+			soup_message_set_status (message, SOUP_STATUS_INTERNAL_SERVER_ERROR);
+			return;
+		}
+		g_object_unref (artwork);
+#else
+		if (!g_file_get_contents (artwork_filename, &buffer, &buffer_len, NULL)) {
+			g_debug ("Error getting artwork data");
+			g_free (artwork_filename);
+			soup_message_set_status (message, SOUP_STATUS_INTERNAL_SERVER_ERROR);
+			return;
+		}
+#endif
+		g_free (artwork_filename);
+		soup_message_set_status (message, SOUP_STATUS_OK);
+		soup_message_set_response (message, "image/png", SOUP_MEMORY_TAKE, buffer, buffer_len);
+	} else if (g_ascii_strcasecmp ("/1/cue", rest_of_path) == 0) {
+		gchar *command;
+		
+		command = g_hash_table_lookup (query, "command");
+
+		if (!command) {
+			g_debug ("No CUE command specified");
+			soup_message_set_status (message, SOUP_STATUS_NO_CONTENT);
+			return;
+		} else if (g_ascii_strcasecmp ("clear", command) == 0) {
+			dacp_player_cue_clear (dacp_share->priv->player);
+			soup_message_set_status (message, SOUP_STATUS_NO_CONTENT);
+		} else if (g_ascii_strcasecmp ("play", command) == 0) {
+			GNode *cacr;
+			gchar *record_query;
+			gchar *sort_by;
+			GHashTable *records;
+			GList *sorted_records;
+			GSList *filter_def;
+			DMAPDb *db;
+			gint index = atoi (g_hash_table_lookup (query, "index"));
+
+			g_object_get (share, "db", &db, NULL);
+			record_query = g_hash_table_lookup (query, "query");
+			filter_def = _dmap_share_build_filter (record_query);
+			records = dmap_db_apply_filter (db, filter_def);
+			sorted_records = g_hash_table_get_values (records);
+			sort_by = g_hash_table_lookup (query, "sort");
+			if (g_strcmp0 (sort_by, "album") == 0) {
+				sorted_records = g_list_sort (sorted_records, (GCompareFunc) daap_record_cmp_by_album);
+			} else if (sort_by != NULL) {
+				g_warning ("Unknown sort column: %s", sort_by);
+			}
+			
+			dacp_player_cue_play (dacp_share->priv->player, sorted_records, index);
+
+			g_list_free (sorted_records);
+			g_hash_table_unref (records);
+			dmap_share_free_filter (filter_def);
+
+			cacr = dmap_structure_add (NULL, DMAP_CC_CACR);
+			dmap_structure_add (cacr, DMAP_CC_MSTT, DMAP_STATUS_OK);
+			dmap_structure_add (cacr, DMAP_CC_MIID, index);
+
+			_dmap_share_message_set_from_dmap_structure (share, message, cacr);
+			dmap_structure_destroy (cacr);
+		} else {
+			g_warning ("Unhandled cue command: %s", command);
+			soup_message_set_status (message, SOUP_STATUS_NO_CONTENT);
+			return;
+		}
+	} else {
+		g_warning ("Unhandled ctrl-int command: %s", rest_of_path);
+		soup_message_set_status (message, SOUP_STATUS_BAD_REQUEST);
+	}
+}
+
+#define PAIR_TXT_LENGTH 16
+#define PASSCODE_LENGTH 4
+
+static gchar *
+dacp_share_pairing_code(DACPShare *share, gchar* pair_txt, gchar passcode[4]) {
+	int i;
+	GString *pairing_code;
+	gchar *pairing_string;
+	gchar *ret;
+	
+	/* The pairing code is the MD5 sum of the concatenation of pair_txt
+	   with the passcode, but the passcode takes 16-bits unicodes characters */
+	pairing_string = g_strnfill(PAIR_TXT_LENGTH + PASSCODE_LENGTH * 2, '\0');
+	g_strlcpy(pairing_string, pair_txt, PAIR_TXT_LENGTH + PASSCODE_LENGTH * 2);
+	for (i = 0; i < 4; i++) {
+		pairing_string[PAIR_TXT_LENGTH + i * 2] = passcode[i];
+	}
+	
+	pairing_code = g_string_new (
+		g_compute_checksum_for_data(G_CHECKSUM_MD5, 
+		                            (guchar*)pairing_string, 
+		                            PAIR_TXT_LENGTH + PASSCODE_LENGTH * 2));
+	g_string_ascii_up (pairing_code);
+	ret = pairing_code->str;
+	g_string_free (pairing_code, FALSE);
+	
+	return ret;
+}
+
+void
+connection_handler_cb (DMAPConnection *connection, guint status, GNode *structure, gpointer user_data) 
+{
+	gboolean connected;
+	GHashTableIter iter;
+	gpointer key, value;
+	DACPShare *share = user_data;
+	DACPRemoteInfo *remote_info = NULL;
+	gchar *service_name = NULL;
+	DMAPStructureItem *item = NULL;
+	gchar *pairing_guid;
+
+	g_debug ("Pairing returned with code %u", status);
+	if (SOUP_STATUS_IS_SUCCESSFUL (status)) {
+		connected = TRUE;
+	} else {
+		connected = FALSE;
+	}
+	
+	/* Get the pairing-guid to identify this remote in the future. */
+	if (structure)
+		item = dmap_structure_find_item (structure, DMAP_CC_CMPG);
+	if (item) {
+		guint64 guid = g_value_get_int64 (&(item->content));
+		pairing_guid = g_strdup_printf ("0x%.16" G_GINT64_MODIFIER "X", guid);
+		g_signal_emit (share, signals [ADD_GUID], 0, pairing_guid);
+		g_free (pairing_guid);
+	}
+
+	/* Find the remote that initiated this connection */
+	g_hash_table_iter_init (&iter, share->priv->remotes);
+	while (g_hash_table_iter_next (&iter, &key, &value)) 
+	{
+		if (((DACPRemoteInfo*) value)->connection == connection) {
+			service_name = (gchar *) key;
+			remote_info = (DACPRemoteInfo*) value;
+			break;
+		}
+	}
+
+	if (remote_info == NULL) {
+		g_warning ("Remote for connection not found");
+		return;
+	}
+
+	/* Frees the connection */
+	remote_info->connection = NULL;	
+	g_object_unref (connection);
+
+	/* FIXME: Send more detailed error info, such as wrong pair code, etc */
+	g_signal_emit (share, signals [REMOTE_PAIRED], 0, service_name, connected);
+}
+
+void
+dacp_share_pair (DACPShare *share, gchar *service_name, gchar passcode[4]) 
+{
+	gchar *pairing_code;
+	gchar *name;
+	gchar *path;
+	DACPRemoteInfo *remote_info;
+	
+	remote_info = g_hash_table_lookup (share->priv->remotes,
+	                                   service_name);
+	                                   
+	if (remote_info == NULL) {
+		g_warning ("Remote %s not found.", service_name);
+		return;
+	}
+
+	if (remote_info->connection != NULL) {
+		g_warning ("Already pairing remote %s.", service_name);
+		return;
+	}
+	
+	g_object_get (share, "name", &name, NULL);
+	
+	remote_info->connection = dmap_connection_new (name, 
+	                                               remote_info->host, 
+	                                               remote_info->port, 
+	                                               FALSE, 
+	                                               NULL, 
+	                                               NULL);
+	/* This is required since we don't call DMAPConnection default handler */
+	dmap_connection_setup (remote_info->connection);
+	
+	/* Get the remote path for pairing */
+	pairing_code = dacp_share_pairing_code (share, remote_info->pair_txt, passcode);
+	path = g_strdup_printf ("/pair?pairingcode=%s&servicename=%s", 
+	                        pairing_code,
+	                        name);
+	g_free (pairing_code);
+	
+	g_debug ("Pairing remote in %s:%d/%s", remote_info->host, remote_info->port, path);
+
+	/* Let DMAPConnection do the heavy work */
+	dmap_connection_get (remote_info->connection, path, FALSE, connection_handler_cb, share);
+
+	g_free (path);
+}
diff --git a/libdmapsharing/dacp-share.h b/libdmapsharing/dacp-share.h
new file mode 100644
index 0000000..6099913
--- /dev/null
+++ b/libdmapsharing/dacp-share.h
@@ -0,0 +1,169 @@
+/*
+ * Header for DACP (e.g., iTunes Remote) sharing
+ *
+ * Copyright (C) 2010 Alexandre Rosenfeld <airmind gmail com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef __DACP_SHARE_H
+#define __DACP_SHARE_H
+
+#include <glib-object.h>
+
+#include <libdmapsharing/dacp-player.h>
+#include <libdmapsharing/dmap-share.h>
+#include <libdmapsharing/dmap-db.h>
+#include <libdmapsharing/dmap-container-db.h>
+#include <libdmapsharing/daap-share.h>
+
+G_BEGIN_DECLS
+
+/**
+ * TYPE_DACP_SHARE:
+ *
+ * The type for #DACPShare.
+ */
+#define TYPE_DACP_SHARE         (dacp_share_get_type ())
+/**
+ * DACP_SHARE:
+ * @o: Object which is subject to casting.
+ * 
+ * Casts a #DACPShare or derived pointer into a (DACPShare*) pointer.
+ * Depending on the current debugging level, this function may invoke
+ * certain runtime checks to identify invalid casts.
+ */
+#define DACP_SHARE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), \
+				 TYPE_DACP_SHARE, DACPShare))
+/**
+ * DACP_SHARE_CLASS:
+ * @k: a valid #DACPShareClass
+ *
+ * Casts a derived #DACPShareClass structure into a #DACPShareClass structure.
+ */
+#define DACP_SHARE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), \
+				 TYPE_DACP_SHARE, DACPShareClass))
+/**
+ * IS_DACP_SHARE:
+ * @o: Instance to check for being a %TYPE_DACP_SHARE.
+ * 
+ * Checks whether a valid #GTypeInstance pointer is of type %TYPE_DACP_SHARE.
+ */
+#define IS_DACP_SHARE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), \
+				 TYPE_DACP_SHARE))
+/**
+ * IS_DACP_SHARE_CLASS:
+ * @k: a #DACPShareClass
+ * 
+ * Checks whether @k "is a" valid #DACPShareClass structure of type
+ * %DACP_SHARE or derived.
+ */
+#define IS_DACP_SHARE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), \
+				 TYPE_DACP_SHARE))
+/**
+ * DACP_SHARE_GET_CLASS:
+ * @o: a #DACPShare instance.
+ * 
+ * Get the class structure associated to a #DACPShare instance.
+ *
+ * Returns: pointer to object class structure.
+ */
+#define DACP_SHARE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), \
+				 TYPE_DACP_SHARE, DACPShareClass))
+
+typedef struct DACPSharePrivate DACPSharePrivate;
+
+typedef struct {
+	DAAPShare daap_share_instance;
+	DACPSharePrivate *priv;
+} DACPShare;
+
+typedef struct {
+	DAAPShareClass daap_share_class;
+	
+	gboolean (*lookup_guid)            (DACPShare *share, gchar *guid);
+	void     (*add_guid)               (DACPShare *share, gchar *guid);
+	
+	void (*remote_found)               (DACPShare *share,
+	                                    gchar *service_name,
+	                                    gchar *remote_name);
+	void (*remote_lost)                (DACPShare *share,
+	                                    gchar *service_name);
+	void (*remote_paired)              (DACPShare *share,
+	                                    gchar *service_name,
+	                                    gboolean connected);
+} DACPShareClass;
+
+GType      dacp_share_get_type (void);
+
+/**
+ * dacp_share_new:
+ * @library_name: The library name that will be shown in the remote.
+ * @player: A #DACPPlayer instance, used to retrieve information from a player
+ *          implementation.
+ * @db: a media database represented by a #DMAPDb instance.
+ * @container_db: a container (album) database represented by a #DMAPContainerDb
+ *                instance.
+ * 
+ * Creates a new DACP share and publishes it using mDNS.
+ *
+ * Returns: a pointer to a #DACPShare.
+ */
+DACPShare *dacp_share_new (const gchar *library_name, DACPPlayer *player, DMAPDb *db, DMAPContainerDb *container_db);
+
+/**
+ * dacp_share_pair:
+ * @share: a #DACPShare 
+ * @service_name: DACP client (remote) service identifier.
+ * @passcode: 4-Digit PIN code entered by the user.
+ * 
+ * Pairs a DACP client (Remote) with this server. If the passcode is 
+ * correct (the same as shown on the remote), the remote will start connecting
+ * to this server.
+ */
+void dacp_share_pair (DACPShare *share, gchar *service_name, gchar passcode[4]);
+
+/**
+ * dacp_share_start_lookup:
+ * @share: A #DACPShare.
+ *     
+ * Start looking up for DACP remotes. Connect to #DACPShare::remote-found signal
+ * to detect new remotes. Be aware that when a #DACPShare is created, only 
+ * after calling this function is that it starts looking up for Remotes on the
+ * network.
+ */
+void dacp_share_start_lookup (DACPShare *share);
+
+/**
+ * dacp_share_stop_lookup:
+ * @share: A #DACPShare.
+ *     
+ * Stop looking up for DACP remotes.
+ */
+void dacp_share_stop_lookup (DACPShare *share);
+
+/**
+ * dacp_share_player_update:
+ * @share: A #DACPShare.
+ * 
+ * Signals that the player has been updated (different track playing, playing
+ * state changed, suffle state changed, etc).
+ */ 
+void dacp_share_player_updated (DACPShare *share);
+
+#endif /* __DACP_SHARE_H */
+
+G_END_DECLS
diff --git a/libdmapsharing/dmap-connection.c b/libdmapsharing/dmap-connection.c
index 9aa3c1b..da1d052 100644
--- a/libdmapsharing/dmap-connection.c
+++ b/libdmapsharing/dmap-connection.c
@@ -90,7 +90,6 @@ struct DMAPConnectionPrivate {
 	DMAPRecordFactory *record_factory;
 
 	DMAPConnectionState state;
-	DMAPResponseHandler response_handler;
 	gboolean use_response_handler_thread;
 	float progress;
 
@@ -430,6 +429,9 @@ typedef struct {
 	SoupMessage *message;
 	int status;
 	DMAPConnection *connection;
+
+	DMAPResponseHandler response_handler;
+	gpointer user_data;
 } DAAPResponseData;
 
 static void
@@ -556,14 +558,15 @@ actual_http_response_handler (DAAPResponseData *data)
 		} else {
 			int dmap_status = 0;
 			item = dmap_structure_find_item (structure, DMAP_CC_MSTT);
-			if (item)
+			if (item) {
 				dmap_status = g_value_get_int (&(item->content));
 
-			if (dmap_status != 200) {
-				g_debug ("Error, dmap.status is not 200 in response from %s",
-					  message_path);
+				if (dmap_status != 200) {
+					g_debug ("Error, dmap.status is not 200 in response from %s",
+						  message_path);
 
-				data->status = SOUP_STATUS_MALFORMED;
+					data->status = SOUP_STATUS_MALFORMED;
+				}
 			}
 		}
 		if (/* FIXME: ! rb_is_main_thread () */ TRUE) {
@@ -581,10 +584,8 @@ actual_http_response_handler (DAAPResponseData *data)
 		connection_set_error_message (data->connection, data->message->reason_phrase);
 	}
 
-	if (priv->response_handler) {
-		DMAPResponseHandler h = priv->response_handler;
-		priv->response_handler = NULL;
-		(*h) (data->connection, data->status, structure);
+	if (data->response_handler) {
+		(*(data->response_handler)) (data->connection, data->status, structure, data->user_data);
 	}
 
 	if (structure) {
@@ -601,23 +602,19 @@ actual_http_response_handler (DAAPResponseData *data)
 static void
 http_response_handler (SoupSession      *session,
 		       SoupMessage      *message,
-		       DMAPConnection *connection)
+		       DAAPResponseData *data)
 {
-	DAAPResponseData *data;
 	int response_length;
 
 	if (message->status_code == SOUP_STATUS_CANCELLED) {
 		g_debug ("Message cancelled");
+		g_free (data);
 		return;
 	}
 
-	data = g_new0 (DAAPResponseData, 1);
 	data->status = message->status_code;
 	response_length = message->response_body->length;
 
-	g_object_ref (G_OBJECT (connection));
-	data->connection = connection;
-
 	g_object_ref (G_OBJECT (message));
 	data->message = message;
 
@@ -629,7 +626,7 @@ http_response_handler (SoupSession      *session,
 	}
 
 	/* to avoid blocking the UI, handle big responses in a separate thread */
-	if (SOUP_STATUS_IS_SUCCESSFUL (data->status) && connection->priv->use_response_handler_thread) {
+	if (SOUP_STATUS_IS_SUCCESSFUL (data->status) && data->connection->priv->use_response_handler_thread) {
 		GError *error = NULL;
 		g_debug ("creating thread to handle daap response");
 		g_thread_create ((GThreadFunc) actual_http_response_handler,
@@ -651,10 +648,12 @@ http_get (DMAPConnection     *connection,
 	  gdouble               version,
 	  gint                  req_id,
 	  gboolean              send_close,
-	  DMAPResponseHandler handler,
+	  DMAPResponseHandler   handler,
+	  gpointer              user_data,
 	  gboolean              use_thread)
 {
 	DMAPConnectionPrivate *priv = connection->priv;
+	DAAPResponseData *data;
 	SoupMessage *message;
 
 	message = dmap_connection_build_message (connection, path, need_hash, version, req_id, send_close);
@@ -667,10 +666,17 @@ http_get (DMAPConnection     *connection,
 	}
 
 	priv->use_response_handler_thread = use_thread;
-	priv->response_handler = handler;
+	
+	data = g_new0 (DAAPResponseData, 1);
+	data->response_handler = handler;
+	data->user_data = user_data;
+	
+	g_object_ref (G_OBJECT (connection));
+	data->connection = connection;
+	
 	soup_session_queue_message (priv->session, message,
 				    (SoupSessionCallback) http_response_handler,
-				    connection);
+				    data);
 	g_debug ("Queued message for http://%s:%d/%s";,
 		  priv->base_uri->host,
 		  priv->base_uri->port,
@@ -685,10 +691,9 @@ dmap_connection_get (DMAPConnection *self,
                      DMAPResponseHandler handler,
                      gpointer user_data)
 {
-    /* FIXME: used to pass user_data as 8th argument, but this is not right. */
     return http_get (self, path, need_hash,
             self->priv->dmap_version, 0, FALSE,
-            (DMAPResponseHandler) handler, FALSE);
+            (DMAPResponseHandler) handler, user_data, FALSE);
 }
 
 static gboolean
@@ -708,7 +713,8 @@ emit_progress_idle (DMAPConnection *connection)
 static void
 handle_server_info (DMAPConnection *connection,
 		    guint             status,
-		    GNode            *structure)
+		    GNode            *structure,
+		    gpointer          user_data)
 {
 	DMAPConnectionPrivate *priv = connection->priv;
 	DMAPStructureItem *item = NULL;
@@ -732,7 +738,8 @@ handle_server_info (DMAPConnection *connection,
 static void
 handle_login (DMAPConnection *connection,
 	      guint             status,
-	      GNode            *structure)
+	      GNode            *structure,
+	      gpointer          user_data)
 {
 	DMAPConnectionPrivate *priv = connection->priv;
 	DMAPStructureItem *item = NULL;
@@ -769,7 +776,8 @@ handle_login (DMAPConnection *connection,
 static void
 handle_update (DMAPConnection *connection,
 	       guint             status,
-	       GNode            *structure)
+	       GNode            *structure,
+	       gpointer          user_data)
 {
 	DMAPConnectionPrivate *priv = connection->priv;
 	DMAPStructureItem *item;
@@ -794,7 +802,8 @@ handle_update (DMAPConnection *connection,
 static void
 handle_database_info (DMAPConnection *connection,
 		      guint             status,
-		      GNode            *structure)
+		      GNode            *structure,
+		      gpointer          user_data)
 {
 	DMAPConnectionPrivate *priv = connection->priv;
 	DMAPStructureItem *item = NULL;
@@ -841,7 +850,8 @@ handle_database_info (DMAPConnection *connection,
 static void
 handle_song_listing (DMAPConnection *connection,
 		     guint             status,
-		     GNode            *structure)
+		     GNode            *structure,
+		     gpointer          user_data)
 {
 	DMAPConnectionPrivate *priv = connection->priv;
 	DMAPStructureItem *item = NULL;
@@ -1061,7 +1071,8 @@ compare_playlists_by_name(gconstpointer a, gconstpointer b)
 static void
 handle_playlists (DMAPConnection *connection,
 		  guint             status,
-		  GNode            *structure)
+		  GNode            *structure,
+		  gpointer          user_data)
 {
 	DMAPConnectionPrivate *priv = connection->priv;
 	GNode *listing_node;
@@ -1126,7 +1137,8 @@ handle_playlists (DMAPConnection *connection,
 static void
 handle_playlist_entries (DMAPConnection *connection,
 			 guint             status,
-			 GNode            *structure)
+			 GNode            *structure,
+			 gpointer          user_data)
 {
 	DMAPConnectionPrivate *priv = connection->priv;
 	DMAPPlaylist *playlist;
@@ -1183,7 +1195,8 @@ handle_playlist_entries (DMAPConnection *connection,
 static void
 handle_logout (DMAPConnection *connection,
 	       guint             status,
-	       GNode            *structure)
+	       GNode            *structure,
+	       gpointer          user_data)
 {
 	connection_disconnected (connection);
 
@@ -1266,6 +1279,17 @@ connected_cb (DMAPConnection       *connection,
 	}
 }
 
+void 
+dmap_connection_setup (DMAPConnection *connection) 
+{
+	connection->priv->session = soup_session_async_new ();
+
+	connection->priv->base_uri = soup_uri_new (NULL);
+	soup_uri_set_scheme (connection->priv->base_uri, SOUP_URI_SCHEME_HTTP);
+	soup_uri_set_host (connection->priv->base_uri, connection->priv->host);
+	soup_uri_set_port (connection->priv->base_uri, connection->priv->port);
+}
+
 void
 dmap_connection_connect (DMAPConnection        *connection,
 			    DMAPConnectionCallback callback,
@@ -1278,12 +1302,7 @@ dmap_connection_connect (DMAPConnection        *connection,
 
 	g_debug ("Creating new DAAP connection to %s:%d", connection->priv->host, connection->priv->port);
 
-	connection->priv->session = soup_session_async_new ();
-
-	connection->priv->base_uri = soup_uri_new (NULL);
-	soup_uri_set_scheme (connection->priv->base_uri, SOUP_URI_SCHEME_HTTP);
-	soup_uri_set_host (connection->priv->base_uri, connection->priv->host);
-	soup_uri_set_port (connection->priv->base_uri, connection->priv->port);
+	dmap_connection_setup (connection);
 
 	if (connection->priv->base_uri == NULL) {
 		g_debug ("Error parsing http://%s:%d";, connection->priv->host, connection->priv->port);
@@ -1464,7 +1483,7 @@ dmap_connection_do_something (DMAPConnection *connection)
 	case DMAP_GET_INFO:
 		g_debug ("Getting DAAP server info");
 		if (! http_get (connection, "/server-info", FALSE, 0.0, 0, FALSE,
-				(DMAPResponseHandler) handle_server_info, FALSE)) {
+				(DMAPResponseHandler) handle_server_info, NULL, FALSE)) {
 			g_debug ("Could not get DAAP connection info");
 			dmap_connection_state_done (connection, FALSE);
 		}
@@ -1499,7 +1518,7 @@ dmap_connection_do_something (DMAPConnection *connection)
 	case DMAP_LOGIN:
 		g_debug ("Logging into DAAP server");
 		if (! http_get (connection, "/login", FALSE, 0.0, 0, FALSE,
-			       (DMAPResponseHandler) handle_login, FALSE)) {
+			       (DMAPResponseHandler) handle_login, NULL, FALSE)) {
 			g_debug ("Could not login to DAAP server");
 			/* FIXME: set state back to GET_PASSWORD to try again */
 			dmap_connection_state_done (connection, FALSE);
@@ -1511,7 +1530,7 @@ dmap_connection_do_something (DMAPConnection *connection)
 		g_debug ("Getting DAAP server database revision number");
 		path = g_strdup_printf ("/update?session-id=%u&revision-number=1", priv->session_id);
 		if (! http_get (connection, path, TRUE, priv->dmap_version, 0, FALSE,
-			       (DMAPResponseHandler) handle_update, FALSE)) {
+			       (DMAPResponseHandler) handle_update, NULL, FALSE)) {
 			g_debug ("Could not get server database revision number");
 			dmap_connection_state_done (connection, FALSE);
 		}
@@ -1523,7 +1542,7 @@ dmap_connection_do_something (DMAPConnection *connection)
 		path = g_strdup_printf ("/databases?session-id=%u&revision-number=%d",
 					priv->session_id, priv->revision_number);
 		if (! http_get (connection, path, TRUE, priv->dmap_version, 0, FALSE,
-			       (DMAPResponseHandler) handle_database_info, FALSE)) {
+			       (DMAPResponseHandler) handle_database_info, NULL, FALSE)) {
 			g_debug ("Could not get DAAP database info");
 			dmap_connection_state_done (connection, FALSE);
 		}
@@ -1543,7 +1562,7 @@ dmap_connection_do_something (DMAPConnection *connection)
 					priv->session_id,
 					priv->revision_number);
 		if (! http_get (connection, path, TRUE, priv->dmap_version, 0, FALSE,
-			       (DMAPResponseHandler) handle_song_listing, TRUE)) {
+			       (DMAPResponseHandler) handle_song_listing, NULL, TRUE)) {
 			g_debug ("Could not get DAAP song listing");
 			dmap_connection_state_done (connection, FALSE);
 		}
@@ -1557,7 +1576,7 @@ dmap_connection_do_something (DMAPConnection *connection)
 					priv->session_id,
 					priv->revision_number);
 		if (! http_get (connection, path, TRUE, priv->dmap_version, 0, FALSE,
-			       (DMAPResponseHandler) handle_playlists, TRUE)) {
+			       (DMAPResponseHandler) handle_playlists, NULL, TRUE)) {
 			g_debug ("Could not get DAAP playlists");
 			dmap_connection_state_done (connection, FALSE);
 		}
@@ -1576,7 +1595,7 @@ dmap_connection_do_something (DMAPConnection *connection)
 						playlist->id,
 						priv->session_id, priv->revision_number);
 			if (! http_get (connection, path, TRUE, priv->dmap_version, 0, FALSE,
-				       (DMAPResponseHandler) handle_playlist_entries, TRUE)) {
+				       (DMAPResponseHandler) handle_playlist_entries, NULL, TRUE)) {
 				g_debug ("Could not get entries for DAAP playlist %d",
 					  priv->reading_playlist);
 				dmap_connection_state_done (connection, FALSE);
@@ -1589,7 +1608,7 @@ dmap_connection_do_something (DMAPConnection *connection)
 		g_debug ("Logging out of DAAP server");
 		path = g_strdup_printf ("/logout?session-id=%u", priv->session_id);
 		if (! http_get (connection, path, TRUE, priv->dmap_version, 0, FALSE,
-			       (DMAPResponseHandler) handle_logout, FALSE)) {
+			       (DMAPResponseHandler) handle_logout, NULL, FALSE)) {
 			g_debug ("Could not log out of DAAP server");
 			dmap_connection_state_done (connection, FALSE);
 		}
diff --git a/libdmapsharing/dmap-connection.h b/libdmapsharing/dmap-connection.h
index de29001..0e824aa 100644
--- a/libdmapsharing/dmap-connection.h
+++ b/libdmapsharing/dmap-connection.h
@@ -135,7 +135,8 @@ typedef gboolean (* DMAPConnectionCallback)  (DMAPConnection *connection,
 
 typedef void (* DMAPResponseHandler) (DMAPConnection *connection,
                                       guint status,
-				      GNode *structure);
+                                      GNode *structure,
+                                      gpointer user_data);
 
 GType              dmap_connection_get_type        (void);
 
@@ -147,6 +148,7 @@ DMAPConnection * dmap_connection_new             (const char              *name,
 						       DMAPRecordFactory *factory);
 
 gboolean           dmap_connection_is_connected    (DMAPConnection        *connection);
+void               dmap_connection_setup           (DMAPConnection        *connection);
 void               dmap_connection_connect         (DMAPConnection        *connection,
 						       DMAPConnectionCallback callback,
 						       gpointer                 user_data);
diff --git a/libdmapsharing/dmap-db.c b/libdmapsharing/dmap-db.c
index 9c74a7f..1c2ff3c 100644
--- a/libdmapsharing/dmap-db.c
+++ b/libdmapsharing/dmap-db.c
@@ -105,24 +105,6 @@ dmap_db_count (const DMAPDb *db)
 	return DMAP_DB_GET_INTERFACE (db)->count (db);
 }
 
-/* Change "\'" to "'". */
-static gchar *
-unescape (const gchar *str)
-{
-	gchar *fnval;
-	gsize j, i, size;
-
-	size = strlen (str) + 1;
-	fnval = g_new0 (gchar, size);
-
-	j = 0;
-	for (i = 0; i < size; i++)
-		if (str[i] != '\\' || str[i+1] != '\'')
-			fnval[j++] = str[i];
-
-	return fnval;
-}
-
 gchar **
 _dmap_db_strsplit_using_quotes (const gchar *str)
 {
@@ -168,44 +150,142 @@ _dmap_db_strsplit_using_quotes (const gchar *str)
         return fnval;
 }
 
+static gboolean
+compare_record_property (DMAPRecord *record, const gchar *property_name, const gchar *property_value)
+{
+	GParamSpec *pspec;
+	GValue value = {0,};
+	/* Note that this string belongs to value and will not be freed explicitely.*/
+	const gchar *str_value;
+	gboolean accept;
+	
+	pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (record), property_name);
+
+	if (pspec == NULL)
+		// Can't find the property in this record, so don't accept it.
+		return FALSE;
+
+	// Get the property value as a GValue set to the type of this
+	// property.
+	g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+	g_object_get_property (G_OBJECT (record), property_name, &value);
+
+	if (G_VALUE_HOLDS_STRING (&value)) {
+		str_value = g_value_get_string (&value);
+	} else if (G_VALUE_HOLDS_BOOLEAN (&value)) {
+		accept = (g_value_get_boolean (&value) && \
+		          g_strcmp0 (property_value, "1") == 0);
+		g_value_unset (&value);
+		return accept;
+	} else if (g_value_type_transformable (G_VALUE_TYPE (&value), G_TYPE_LONG)) {
+		// Prefer integer conversion.
+		GValue dest = {0,};
+		g_value_init (&dest, G_TYPE_LONG);
+		if (!g_value_transform (&value, &dest)) {
+			g_warning ("Failed to convert value into long for property %s", property_name);
+			g_value_unset (&value);
+			return FALSE;
+		}
+		accept = (g_value_get_long (&dest) == atoi (property_value));
+		g_value_unset (&value);
+		return accept;
+	} else if (g_value_type_transformable (G_VALUE_TYPE (&value), G_TYPE_STRING)) {
+		// Use standard transform functions from GLib (note that these
+		// functions are unreliable and known cases should be handled
+		// above).
+		GValue dest;
+		g_value_init (&dest, G_TYPE_STRING);
+		if (!g_value_transform (&value, &dest)) {
+			g_warning ("Failed to convert value into string for property %s", property_name);
+			g_value_unset (&value);
+			return FALSE;
+		}
+		str_value = g_value_dup_string (&dest);
+		g_value_reset (&value);
+		//Sets the string to value so that it will be freed later.
+		g_value_take_string (&value, (gchar *)str_value);
+		g_value_unset (&dest);
+	} else {
+		g_value_unset (&value);
+		return FALSE;
+	}
+
+	// Only arrive here if we are handling strings.
+	if (str_value != NULL && property_value != NULL && \
+	    g_ascii_strcasecmp (str_value, property_value) == 0) {
+		accept = TRUE;
+	} else if (str_value == NULL && property_value == NULL) {
+		accept = TRUE;
+	} else {
+		accept = FALSE;
+	}
+
+	// This will destroy str_value since it belongs to value.
+	g_value_unset (&value);
+
+	return accept;
+}
+
 static void
 apply_filter (gpointer id, DMAPRecord *record, gpointer data)
 {
 	FilterData *fd;
+	gboolean accept = FALSE;
+
+	const gchar *query_key;
+	const gchar *query_value;
+
+	g_return_if_fail (record != NULL);
+	g_return_if_fail (G_IS_OBJECT (record));
 	
 	fd = data;
 	if (fd->filter_def == NULL) {
-		g_hash_table_insert (fd->ht, GUINT_TO_POINTER (id), record);
-	} else {
-		GSList *ptr1, *ptr2;
-		gboolean accepted = TRUE;
-		for (ptr1 = fd->filter_def; ptr1 != NULL; ptr1 = ptr1->next) {
-			for (ptr2 = ptr1->data; ptr2 != NULL; ptr2 = ptr2->next) {
-				gchar *value1 = unescape (((FilterDefinition *) ptr2->data)->value);
-
-				if (((FilterDefinition *) ptr2->data)->is_string == FALSE) {
-					if (GPOINTER_TO_UINT (id) == atoi (value1))
-						g_hash_table_insert (fd->ht, id, dmap_db_lookup_by_id (fd->db, GPOINTER_TO_UINT (id)));
-					accepted = FALSE; /* Not really, but we've already added if required. */
-				} else {
-					gchar *value2;
-					GParamSpec *pspec = g_object_class_find_property (G_OBJECT_CLASS (record), ((FilterDefinition *) ptr2->data)->key);
-					if (G_IS_PARAM_SPEC_STRING (pspec)) {
-						g_object_get (record, ((FilterDefinition *) ptr2->data)->key, &value2, NULL); 
-						if (value2 == NULL || g_strcasecmp (value1, value2) != 0) {
-							accepted = FALSE;
-						}
-					} else {
-						/* FIXME: unhandled filter (non-string): */
-						accepted = FALSE;
-					}
-					g_free (value2);
+		g_hash_table_insert (fd->ht, GUINT_TO_POINTER (id), g_object_ref (record));
+		return;
+	} 
+	
+	GSList *list, *filter;
+	for (list = fd->filter_def; list != NULL; list = list->next) {
+		for (filter = list->data; filter != NULL; filter = filter->next) {
+			FilterDefinition *def = filter->data;
+			const gchar *property_name;
+
+			query_key   = def->key;
+			query_value = def->value;
+
+			if (g_strcmp0 (query_key, "dmap.itemid") == 0) {
+				if (GPOINTER_TO_UINT (id) == atoi (query_value)) {
+					accept = TRUE;
+					continue;
 				}
-				g_free (value1);
-			}
+			};
+
+			// Use only the part after the last dot.
+			// For instance, dmap.songgenre becomes songgenre.
+			property_name = strrchr (query_key, '.');
+			if (property_name == NULL)
+				property_name = query_key;
+			else
+				//Don't include the dot in the property name.
+				property_name++;
+
+			accept = compare_record_property (record, property_name, query_value);
+			
+			if (def->negate)
+				accept = !accept;
+			
+			// If we accept this value, then quit looking at this 
+			// group (groups are always OR)
+			if (accept)
+				break;
 		}
-		if (accepted == TRUE)
-			g_hash_table_insert (fd->ht, GUINT_TO_POINTER (id), record);
+		// Don't look any further, because groups are AND between 
+		// each other, the first FALSE means FALSE at the end.
+		if (!accept)
+			break;
+	}
+	if (accept) {
+		g_hash_table_insert (fd->ht, GUINT_TO_POINTER (id), g_object_ref (record));
 	}
 }
 
@@ -215,7 +295,7 @@ dmap_db_apply_filter (DMAPDb *db, GSList *filter_def)
 	GHashTable *ht;
 	FilterData data;
 
-	ht = g_hash_table_new (g_direct_hash, g_direct_equal);
+	ht = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
 	data.db = db;
 	data.filter_def = filter_def;
 	data.ht = ht;
diff --git a/libdmapsharing/dmap-db.h b/libdmapsharing/dmap-db.h
index 686a4d2..b001fb9 100644
--- a/libdmapsharing/dmap-db.h
+++ b/libdmapsharing/dmap-db.h
@@ -84,12 +84,10 @@ struct _DMAPDbInterface {
 
 typedef const char *(*RecordGetValueFunc) (DMAPRecord *record);
 
-/* FIXME: This is in transition: how to keep this safe when value might 
- * be compared to a string, int, etc.? */
 typedef struct FilterDefinition {
 	gchar *key;
 	gchar *value;
-	gboolean is_string;
+	gboolean negate;
 } FilterDefinition;
 
 GType dmap_db_get_type		    (void);
diff --git a/libdmapsharing/dmap-marshal.list b/libdmapsharing/dmap-marshal.list
index 99c4268..a25b0e7 100644
--- a/libdmapsharing/dmap-marshal.list
+++ b/libdmapsharing/dmap-marshal.list
@@ -1,2 +1,7 @@
 STRING:STRING
 VOID:ULONG,FLOAT
+STRING:ULONG,ULONG
+ULONG:VOID
+VOID:STRING,STRING
+VOID:STRING,BOOLEAN
+BOOLEAN:STRING
diff --git a/libdmapsharing/dmap-mdns-browser-avahi.c b/libdmapsharing/dmap-mdns-browser-avahi.c
index bf67bd8..8b1098f 100644
--- a/libdmapsharing/dmap-mdns-browser-avahi.c
+++ b/libdmapsharing/dmap-mdns-browser-avahi.c
@@ -109,6 +109,13 @@ static guint dmap_mdns_browser_signals [LAST_SIGNAL] = { 0, };
 
 G_DEFINE_TYPE (DMAPMdnsBrowser, dmap_mdns_browser, G_TYPE_OBJECT)
 
+static gchar *service_type_name [] = {
+	NULL,
+	"_daap._tcp",
+	"_dpap._tcp",
+	"_touch-remote._tcp"
+};
+
 GQuark
 dmap_mdns_browser_error_quark (void)
 {
@@ -236,7 +243,7 @@ dmap_mdns_browser_start (DMAPMdnsBrowser *browser,
     browser->priv->service_browser = avahi_service_browser_new (browser->priv->client,
             AVAHI_IF_UNSPEC,
             AVAHI_PROTO_UNSPEC,
-            (browser->priv->service_type == DMAP_MDNS_BROWSER_SERVICE_TYPE_DAAP ? "_daap._tcp" :  "_dpap._tcp"),
+            service_type_name [browser->priv->service_type],
             NULL,
 #ifdef HAVE_AVAHI_0_6
             0,
@@ -366,6 +373,7 @@ resolve_cb (AvahiServiceResolver *service_resolver,
 {
     if (event == AVAHI_RESOLVER_FOUND) {
         gchar *name = NULL;
+        gchar *pair = NULL;
         gchar host[AVAHI_ADDRESS_STR_MAX];
         gboolean pp = FALSE;
         DMAPMdnsBrowserService *service;
@@ -391,7 +399,16 @@ resolve_cb (AvahiServiceResolver *service_resolver,
                         pp = TRUE;
 		    }
                 } else if (strcmp (key, "Machine Name") == 0) {
-                    name = g_strdup (value);
+                    if (name == NULL)
+                        name = g_strdup (value);
+                } else if (strcmp (key, "DvNm") == 0) {
+                    if (name != NULL)
+                        g_free (name);
+                    /* Remote's name is presented as DvNm in DACP */
+                    name = g_strdup(value);
+                } else if (strcmp (key, "Pair") == 0) {
+                    /* Pair is used when first connecting to a DACP remote */
+                    pair = g_strdup(value);
                 }
 
                 g_free (key);
@@ -410,6 +427,7 @@ resolve_cb (AvahiServiceResolver *service_resolver,
         service->name = name;
         service->host = g_strdup (host);
         service->port = port;
+        service->pair = pair;
         service->password_protected = pp;
         browser->priv->services = g_slist_append (browser->priv->services, service);
         g_signal_emit (browser,
@@ -431,7 +449,7 @@ dmap_mdns_browser_resolve (DMAPMdnsBrowser *browser,
             AVAHI_IF_UNSPEC,
             AVAHI_PROTO_INET,
             name,
-            (browser->priv->service_type == DMAP_MDNS_BROWSER_SERVICE_TYPE_DAAP ? "_daap._tcp" :  "_dpap._tcp"),
+            service_type_name [browser->priv->service_type],
             domain,
             AVAHI_PROTO_UNSPEC,
 #ifdef HAVE_AVAHI_0_6
@@ -506,5 +524,6 @@ free_service (DMAPMdnsBrowserService *service)
     g_free (service->service_name);
     g_free (service->name);
     g_free (service->host);
+	g_free (service->pair);
     g_free (service);
 }
diff --git a/libdmapsharing/dmap-mdns-browser-howl.c b/libdmapsharing/dmap-mdns-browser-howl.c
index 1a4f6c0..6c8ec65 100644
--- a/libdmapsharing/dmap-mdns-browser-howl.c
+++ b/libdmapsharing/dmap-mdns-browser-howl.c
@@ -210,6 +210,7 @@ resolve_cb (sw_discovery discovery,
 {
     char                   *host;
     char                   *name;
+    char                   *pair;
     sw_text_record_iterator it;
     gboolean                pp = FALSE;
     DMAPMdnsBrowserService *service;
@@ -239,6 +240,16 @@ resolve_cb (sw_discovery discovery,
                 if (name != NULL)
                     g_free (name);
                 name = g_strdup ((char *)val);
+            } else if (strcmp ((char *)key, "DvNm") == 0) {
+                if (name != NULL)
+                    g_free (name);
+                /* Remote's name is presented as DvNm in DACP */
+                name = g_strdup ((char *)val);
+            } else if (strcmp ((char *)key, "Pair") == 0) {
+                if (pair != NULL)
+                    g_free (pair);
+                /* Pair is used when first connecting to a DACP remote */
+                pair = g_strdup ((char *)val);
             }
         }
 
@@ -255,6 +266,7 @@ resolve_cb (sw_discovery discovery,
     service->host = g_strdup (host);
     service->port = port;
     service->password_protected = pp;
+    service->pair = pair;
     browser->priv->services = g_slist_append (browser->priv->services, service);
 
     g_signal_emit (browser,
diff --git a/libdmapsharing/dmap-mdns-browser.h b/libdmapsharing/dmap-mdns-browser.h
index 1a5cf8a..a24387a 100644
--- a/libdmapsharing/dmap-mdns-browser.h
+++ b/libdmapsharing/dmap-mdns-browser.h
@@ -84,7 +84,8 @@ typedef enum
     DMAP_MDNS_BROWSER_SERVICE_TYPE_INVALID = 0,
     DMAP_MDNS_BROWSER_SERVICE_TYPE_DAAP,
     DMAP_MDNS_BROWSER_SERVICE_TYPE_DPAP,
-    DMAP_MDNS_BROWSER_SERVICE_TYPE_LAST = DMAP_MDNS_BROWSER_SERVICE_TYPE_DPAP
+    DMAP_MDNS_BROWSER_SERVICE_TYPE_DACP,
+    DMAP_MDNS_BROWSER_SERVICE_TYPE_LAST = DMAP_MDNS_BROWSER_SERVICE_TYPE_DACP
 } DMAPMdnsBrowserServiceType;
 
 typedef enum
@@ -100,6 +101,7 @@ struct _DMAPMdnsBrowserService
     gchar *host;
     guint port;
     gboolean password_protected;
+    gchar *pair;
 };
 
 struct _DMAPMdnsBrowserClass
diff --git a/libdmapsharing/dmap-mdns-publisher-avahi.c b/libdmapsharing/dmap-mdns-publisher-avahi.c
index 487f293..7f690e6 100644
--- a/libdmapsharing/dmap-mdns-publisher-avahi.c
+++ b/libdmapsharing/dmap-mdns-publisher-avahi.c
@@ -55,6 +55,7 @@ struct DmapMdnsPublisherPrivate
 	guint            port;
 	char		*type_of_service;
 	gboolean         password_required;
+	gchar          **txt_records;
 };
 
 enum {
@@ -104,7 +105,8 @@ create_service (DmapMdnsPublisher *publisher,
 		GError             **error)
 {
 	int         ret;
-	const char *txt_record;
+	const char *password_record;
+	AvahiStringList *txt_records;
 
 	if (publisher->priv->entry_group == NULL) {
 		publisher->priv->entry_group = avahi_entry_group_new (publisher->priv->client,
@@ -133,12 +135,23 @@ create_service (DmapMdnsPublisher *publisher,
 #endif
 
 	if (publisher->priv->password_required) {
-		txt_record = "Password=true";
+		password_record = "Password=true";
 	} else {
-		txt_record = "Password=false";
+		password_record = "Password=false";
+	}
+	
+	txt_records = avahi_string_list_new(password_record, NULL);
+	
+	if (publisher->priv->txt_records) {
+		//g_debug("Number of txt records: %d", publisher->priv->txt_records);
+		gchar **txt_record = publisher->priv->txt_records;
+		while (*txt_record) {
+			txt_records = avahi_string_list_add(txt_records, *txt_record);
+			txt_record++;
+		}
 	}
 
-	ret = avahi_entry_group_add_service (publisher->priv->entry_group,
+	ret = avahi_entry_group_add_service_strlst (publisher->priv->entry_group,
 					     AVAHI_IF_UNSPEC,
 					     AVAHI_PROTO_UNSPEC,
 					     0,
@@ -147,8 +160,9 @@ create_service (DmapMdnsPublisher *publisher,
 					     NULL,
 					     NULL,
 					     publisher->priv->port,
-					     txt_record,
-					     NULL);
+					     txt_records);
+					     
+	avahi_string_list_free(txt_records);
 
 	if (ret < 0) {
 		g_set_error (error,
@@ -287,12 +301,40 @@ dmap_mdns_publisher_set_password_required (DmapMdnsPublisher *publisher,
 	return TRUE;
 }
 
+
+static gboolean
+publisher_set_txt_records_internal (DmapMdnsPublisher *publisher,
+					  gchar              **txt_records,
+					  GError             **error)
+{
+	g_strfreev (publisher->priv->txt_records);
+	publisher->priv->txt_records = g_strdupv (txt_records);
+	return TRUE;
+}
+
+gboolean
+dmap_mdns_publisher_set_txt_records (DmapMdnsPublisher *publisher,
+					      gchar              **txt_records,
+					      GError             **error)
+{
+        g_return_val_if_fail (publisher != NULL, FALSE);
+
+	publisher_set_txt_records_internal (publisher, txt_records, error);
+
+	if (publisher->priv->entry_group) {
+		refresh_service (publisher, error);
+	}
+
+	return TRUE;
+}
+
 gboolean
 dmap_mdns_publisher_publish (DmapMdnsPublisher *publisher,
 				const char          *name,
 				guint                port,
 				const char          *type_of_service,
 				gboolean             password_required,
+				gchar              **txt_records,
 				GError             **error)
 {
 	if (publisher->priv->client == NULL) {
@@ -308,6 +350,7 @@ dmap_mdns_publisher_publish (DmapMdnsPublisher *publisher,
 	publisher_set_port_internal (publisher, port, NULL);
 	publisher_set_type_of_service_internal (publisher, type_of_service, NULL);
 	publisher_set_password_required_internal (publisher, password_required, NULL);
+	publisher_set_txt_records_internal (publisher, txt_records, NULL);
 
 	return create_service (publisher, error);
 }
@@ -429,6 +472,8 @@ dmap_mdns_publisher_finalize (GObject *object)
 
 	g_free (publisher->priv->name);
 	g_free (publisher->priv->type_of_service);
+	
+	g_strfreev (publisher->priv->txt_records);
 
 	G_OBJECT_CLASS (dmap_mdns_publisher_parent_class)->finalize (object);
 }
diff --git a/libdmapsharing/dmap-mdns-publisher.h b/libdmapsharing/dmap-mdns-publisher.h
index 9ad83e6..ba5976a 100644
--- a/libdmapsharing/dmap-mdns-publisher.h
+++ b/libdmapsharing/dmap-mdns-publisher.h
@@ -75,6 +75,7 @@ gboolean             dmap_mdns_publisher_publish                (DmapMdnsPublish
 								    guint                port,
 								    const char          *type_of_service,
 								    gboolean             password_required,
+								    gchar              **txt_records,
 								    GError             **error);
 gboolean             dmap_mdns_publisher_set_name               (DmapMdnsPublisher *publisher,
 								    const char          *name,
@@ -85,6 +86,9 @@ gboolean             dmap_mdns_publisher_set_port               (DmapMdnsPublish
 gboolean             dmap_mdns_publisher_set_password_required  (DmapMdnsPublisher *publisher,
 								    gboolean             required,
 								    GError             **error);
+gboolean             dmap_mdns_publisher_set_txt_records        (DmapMdnsPublisher *publisher,
+								    gchar          **txt_records,
+								    GError         **error);
 gboolean             dmap_mdns_publisher_withdraw               (DmapMdnsPublisher *publisher,
 								    GError             **error);
 
diff --git a/libdmapsharing/dmap-share.c b/libdmapsharing/dmap-share.c
index dd2490f..2e6991b 100644
--- a/libdmapsharing/dmap-share.c
+++ b/libdmapsharing/dmap-share.c
@@ -49,13 +49,15 @@ typedef enum {
 
 enum {
 	PROP_0,
+	PROP_SERVER,
 	PROP_NAME,
 	PROP_PASSWORD,
 	PROP_REVISION_NUMBER,
 	PROP_AUTH_METHOD,
 	PROP_DB,
 	PROP_CONTAINER_DB,
-	PROP_TRANSCODE_MIMETYPE
+	PROP_TRANSCODE_MIMETYPE,
+	PROP_TXT_RECORDS
 };
 
 struct DMAPSharePrivate {
@@ -82,6 +84,9 @@ struct DMAPSharePrivate {
 	/* The media database */
 	DMAPDb *db;
 	DMAPContainerDb *container_db;
+	
+	/* TXT-RECORDS published by mDNS */
+	gchar **txt_records;
 
 	GHashTable *session_ids;
 };
@@ -203,6 +208,21 @@ static void databases_adapter (SoupServer *server,
 						 context);
 }
 
+static void ctrl_int_adapter (SoupServer *server,
+                              SoupMessage *message,
+                              const char *path,
+                              GHashTable *query,
+                              SoupClientContext *context,
+                              DMAPShare *share)
+{
+	DMAP_SHARE_GET_CLASS (share)->ctrl_int (share,
+	                                        server,
+	                                        message,
+	                                        path,
+	                                        query,
+	                                        context);
+}
+
 gboolean
 _dmap_share_server_start (DMAPShare *share)
 {
@@ -259,6 +279,9 @@ _dmap_share_server_start (DMAPShare *share)
 	soup_server_add_handler (share->priv->server, "/databases",
 				 (SoupServerCallback) databases_adapter,
 				 share, NULL);
+	soup_server_add_handler (share->priv->server, "/ctrl-int",
+				 (SoupServerCallback) ctrl_int_adapter,
+				 share, NULL);
 	soup_server_run_async (share->priv->server);
 
 	/* using direct since there is no g_uint_hash or g_uint_equal */
@@ -309,6 +332,7 @@ _dmap_share_publish_start (DMAPShare *share)
 					      share->priv->port,
 					      DMAP_SHARE_GET_CLASS (share)->get_type_of_service (share),
 					      password_required,
+					      share->priv->txt_records,
 					      &error);
 
 	if (res == FALSE) {
@@ -438,6 +462,9 @@ _dmap_share_set_property (GObject *object,
 		/* FIXME: get or dup? */
                 share->priv->transcode_mimetype = g_value_dup_string (value);
                 break;
+        case PROP_TXT_RECORDS:
+                share->priv->txt_records = g_value_dup_boxed (value);
+                break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -453,6 +480,9 @@ _dmap_share_get_property (GObject *object,
 	DMAPShare *share = DMAP_SHARE (object);
 
 	switch (prop_id) {
+	case PROP_SERVER:
+		g_value_set_object (value, share->priv->server);
+		return;
 	case PROP_NAME:
 		g_value_set_string (value, share->priv->name);
 		break;
@@ -478,6 +508,9 @@ _dmap_share_get_property (GObject *object,
         case PROP_TRANSCODE_MIMETYPE:
                 g_value_set_string (value, share->priv->transcode_mimetype);
                 break;
+        case PROP_TXT_RECORDS:
+                g_value_set_boxed (value, share->priv->txt_records);
+                break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -504,6 +537,8 @@ _dmap_share_finalize (GObject *object)
 
 	g_object_unref (share->priv->db);
 	g_object_unref (share->priv->container_db);
+	
+	g_strfreev (share->priv->txt_records);
 
 	if (share->priv->publisher) {
 		g_object_unref (share->priv->publisher);
@@ -538,8 +573,17 @@ dmap_share_class_init (DMAPShareClass *klass)
 	klass->published      = _dmap_share_published;
 	klass->name_collision = _dmap_share_name_collision;
 	klass->databases      = _dmap_share_databases;
+	klass->ctrl_int       = _dmap_share_ctrl_int;
 
 	g_object_class_install_property (object_class,
+					 PROP_SERVER,
+					 g_param_spec_object ("server",
+							      "Soup Server",
+							      "Soup server",
+							      SOUP_TYPE_SERVER,
+							      G_PARAM_READABLE));
+	
+	g_object_class_install_property (object_class,
 					 PROP_NAME,
 					 g_param_spec_string ("name",
 						 	    "Name",
@@ -594,6 +638,15 @@ dmap_share_class_init (DMAPShareClass *klass)
                                                              "Set mimetype of stream after transcoding",
                                                              NULL,
                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+                                                             
+
+	g_object_class_install_property (object_class,
+                                         PROP_TXT_RECORDS,
+                                         g_param_spec_boxed ("txt-records",
+                                                             "TXT-Records",
+                                                             "Set TXT-Records used for MDNS publishing",
+                                                             G_TYPE_STRV,
+                                                             G_PARAM_READWRITE));
 
 	g_type_class_add_private (klass, sizeof (DMAPSharePrivate));
 }
@@ -1102,57 +1155,179 @@ _dmap_share_build_filter (gchar *filterstr)
 	 * 'daap.songgenre:Foo'+'daap.songartist:Bar'+'daap.songalbum:Baz'
 	 * or (iPhoto '09)
 	 * ('daap.idemid:1000','dmap:itemid:1001')
+	 * or (DACP):
+	 * ('com.apple.itunes.mediakind:1','com.apple.itunes.mediakind:32') 'daap.songartist!:'
          */
 
-	size_t len;
+	gchar *next_char;
+	GString *value;
+	//gboolean save_value;
+	gboolean is_key;
+	gboolean is_value;
+	gboolean new_group;
+	gboolean accept;
+	gboolean negate;
+	gint parentheses_count;
+	gint quotes_count;
+	FilterDefinition *def;
+	
 	GSList *list = NULL;
+	GSList *filter = NULL;
 
 	g_debug ("Filter string is %s.", filterstr);
 
-	len = strlen (filterstr);
-	filterstr = *filterstr == '(' ? filterstr + 1 : filterstr;
-	/* FIXME: I thought this should be -1, but there is a trailing ' ' in iPhoto '09: */
-	filterstr[len - 2] = filterstr[len - 2] == ')' ? 0x00 : filterstr[len - 2];
-
-	if (filterstr != NULL) {
-		int i;
-		gchar **t1 = g_strsplit (filterstr, ",", 0);
-
-		for (i = 0; t1[i]; i++) {
-			int j;
-			GSList *filter = NULL;
-			gchar **t2;
-
-			t2 = _dmap_db_strsplit_using_quotes (t1[i]);
-
-			for (j = 0; t2[j]; j++) {
-				FilterDefinition *def;
-				gchar **t3;
-
-				t3 = g_strsplit (t2[j], ":", 0);
-
+	if (filterstr == NULL)
+		return NULL;
+	
+	next_char = filterstr;
+	
+	parentheses_count = 0;
+	quotes_count = 0;
+	is_key = TRUE;
+	is_value = FALSE;
+	new_group = FALSE;
+	negate = FALSE;
+	value = NULL;
+	def = NULL;
+	
+	// The query string is divided in groups of AND separated by a space,
+	// each group containing queries of OR separated by comma enclosed in
+	// parentheses. Queries are key, value pairs separated by ':' enclosed 
+	// in quotes or not.
+	// The result from this is a list of lists. Here is an example:
+	// String is ('com.apple.itunes.mediakind:1','com.apple.itunes.mediakind:32') 'daap.songartist!:'
+	// list:
+	// |-> filter1: -> com.apple.itunes.mediakind = 1
+	// |   |        -> com.apple.itunes.mediakind = 32
+	// |-> filter1: -> daap.songartist! = (null)
+	// This basically means that the query is (filter1 AND filter2), and
+	// filter1 is (com.apple.itunes.mediakind = 1) OR (com.apple.itunes.mediakind = 32)
+	while (TRUE) {
+		// We check each character to see if it should be included in
+		// the current value (the current value will become the query key
+		// or the query value). Anything that is not a special character
+		// handled below will be accepted (this means weird characters
+		// might appear in names).
+		// Handling of unicode characters is unknown, but as long it 
+		// doesn't appear as a character handled below, it will be
+		// included.
+		// This parser will result in unknown behaviour on bad-formatted
+		// queries (it does not check for syntax errors), but should not 
+		// crash. In this way it may accept much more syntaxes then
+		// other parsers.
+		accept = FALSE;
+		// A slash can escape characters such as ', so add the character
+		// after the slash.
+		if (*next_char == '\\') {
+			accept = TRUE;
+			next_char++;
+		} else {
+			switch (*next_char) {
+				case '(':
+					parentheses_count++;
+					break;
+				case ')':
+					parentheses_count--;
+					break;
+				case '\'':
+					if (quotes_count > 0) {
+						quotes_count = 0;
+					} else {
+						quotes_count = 1;
+					}
+					break;
+				case ' ':
+					if (quotes_count > 0) {
+						accept = TRUE;
+					} else {
+						new_group = TRUE;
+					}
+					break;
+				case ':':
+					// Inside values, they will be included in the 
+					// query string, otherwise it indicates there
+					// will be a value next.
+					if (is_value) {
+						accept = TRUE;
+					} else if (is_key) {
+						is_value = TRUE;
+					}
+					break;
+				case ',':
+				case '+':
+				case '\0':
+					// Don't accept these caracters
+					break;
+				case '!':
+					if (is_key && value) {
+						negate = TRUE;
+						break;
+					}
+				default:
+					accept = TRUE;
+			}
+		}
+		//g_debug ("Char: %c, Accept: %s", *next_char, accept?"TRUE":"FALSE");
+		//Is the next character to be accepted?
+		if (accept) {
+			if (!value) {
+				value = g_string_new ("");
+			}
+			g_string_append_c (value, *next_char);
+		} else if (value != NULL && *next_char != '!') {
+			// If we won't accept this character, we are ending a 
+			// query key or value, so we should save them in a new
+			// FilterDefinition. If is_value is TRUE, we will still
+			// parse the query value, so we must keep our def around.
+			// Otherwise, save it in the list of filters.
+			if (!def) {
 				def = g_new0 (FilterDefinition, 1);
-				def->key   = g_strdup (t3[0]);
-				def->value = g_strdup (t3[1]);
-
-				if (g_strcasecmp ("dmap.itemid", t3[0]) == 0) {
-					def->is_string = FALSE;
-				} else {
-					def->is_string = TRUE;
-				}
-
-				if (def != NULL)
-					filter = g_slist_append (filter, def);
-
-				g_strfreev (t3);
 			}
-
+			if (is_key) {
+				def->key = value->str;
+				g_string_free (value, FALSE);
+				def->negate = negate;
+				negate = FALSE;
+				is_key = FALSE;
+			} else {
+				def->value = value->str;
+				g_string_free (value, FALSE);
+				is_value = FALSE;
+				is_key = TRUE;
+			}
+			value = NULL;
+			if (!is_value) {
+				filter = g_slist_append (filter, def);
+				def = NULL;
+			}
+		}
+		if (new_group && filter) {
 			list = g_slist_append (list, filter);
-
-			g_strfreev (t2);
+			filter = NULL;
+			new_group = FALSE;
 		}
-		g_strfreev (t1);
+		// Only handle \0 here so we can handle remaining values above.
+		if (*next_char == '\0')
+			break;
+		next_char++;
+	};
+
+	// Any remaining def or filter must still be handled here.
+	if (def) {
+		filter = g_slist_append (filter, def);
 	}
+	
+	if (filter) {
+		list = g_slist_append (list, filter);
+	}	
+
+	GSList *ptr1, *ptr2;
+
+        for (ptr1 = list; ptr1 != NULL; ptr1 = ptr1->next) {
+                for (ptr2 = ptr1->data; ptr2 != NULL; ptr2 = ptr2->next) {
+                	g_debug ("%s = %s", ((FilterDefinition *) ptr2->data)->key, ((FilterDefinition *) ptr2->data)->value);
+                }
+        }
 
         return list;
 }
@@ -1170,6 +1345,46 @@ dmap_share_free_filter (GSList *filter)
         }
 }
 
+typedef struct {
+	gchar *name;
+	gint64 group_id;
+	gchar *artist;
+	int count;
+} GroupInfo;
+
+static void
+group_items (gpointer key, DMAPRecord *record, GHashTable *groups)
+{
+	gchar *album, *artist;
+	GroupInfo *group_info;
+	gint64 group_id;
+	
+	g_object_get (record, "songartist", &artist, "songalbum", &album, "songalbumid", &group_id, NULL);
+	if (!album) {
+		g_free (artist);
+		return;
+	}
+	group_info = g_hash_table_lookup (groups, album);
+	if (!group_info) {
+		group_info = g_new0 (GroupInfo, 1);
+		g_hash_table_insert (groups, album, group_info);
+		// They will be freed when the hash table is freed.
+		group_info->name = album;
+		group_info->artist = artist;
+		group_info->group_id = group_id;
+	} else {
+		g_free (album);
+		g_free (artist);
+	}
+	(group_info->count)++;
+}
+
+static gint
+group_info_cmp (gconstpointer group1, gconstpointer group2) 
+{
+	return g_ascii_strcasecmp (((GroupInfo*)group1)->name, ((GroupInfo*)group2)->name);
+}
+
 static void
 debug_param (gpointer key, gpointer val, gpointer user_data)
 {
@@ -1177,6 +1392,22 @@ debug_param (gpointer key, gpointer val, gpointer user_data)
 }
 
 void
+_dmap_share_ctrl_int (DMAPShare *share,
+		      SoupServer        *server,
+		      SoupMessage       *message,
+		      const char        *path,
+		      GHashTable        *query,
+		      SoupClientContext *context)
+{
+	g_debug ("Path is %s.", path);
+	if (query) {
+		g_hash_table_foreach (query, debug_param, NULL);
+	}
+	
+	g_debug ("ctrl-int not implemented");
+}
+
+void
 _dmap_share_databases (DMAPShare *share,
 		       SoupServer        *server,
 		       SoupMessage       *message,
@@ -1235,6 +1466,87 @@ _dmap_share_databases (DMAPShare *share,
 		dmap_structure_destroy (avdb);
 
 		g_free (nameprop);
+	} else if (g_ascii_strcasecmp ("/1/groups", rest_of_path) == 0) {
+	/* ADBS database songs
+	 * 	MSTT status
+	 * 	MUTY update type
+	 * 	MTCO specified total count
+	 * 	MRCO returned count
+	 * 	MLCL listing
+	 * 		MLIT
+	 * 			attrs
+	 * 		MLIT
+	 * 		...
+	 */		 
+
+		GSList *filter_def;
+		gchar *record_query;
+		GHashTable *records = NULL;
+		GHashTable *groups;
+		GList *values;
+		GList *value;
+		gchar *sort_by;
+		GroupInfo *group_info;
+		GNode *agal;
+		GNode *mlcl;
+		GNode *mlit;
+		gint num;
+
+		if (g_strcmp0 (g_hash_table_lookup (query, "group-type"), "albums") != 0) {
+			g_warning ("Unsupported grouping");
+			soup_message_set_status (message, SOUP_STATUS_INTERNAL_SERVER_ERROR);
+			return;
+		}
+
+		record_query = g_hash_table_lookup (query, "query");
+		filter_def = _dmap_share_build_filter (record_query);
+		records = dmap_db_apply_filter (DMAP_DB (share->priv->db), filter_def);
+
+		groups = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+		g_hash_table_foreach (records, (GHFunc) group_items, groups);
+		
+		agal = dmap_structure_add (NULL, DMAP_CC_AGAL);
+		dmap_structure_add (agal, DMAP_CC_MSTT, (gint32) DMAP_STATUS_OK);
+		dmap_structure_add (agal, DMAP_CC_MUTY, 0);
+		
+		num = g_hash_table_size (groups);
+		dmap_structure_add (agal, DMAP_CC_MTCO, (gint32) num);
+		dmap_structure_add (agal, DMAP_CC_MRCO, (gint32) num);
+		
+		mlcl = dmap_structure_add (agal, DMAP_CC_MLCL);
+
+		values = g_hash_table_get_values (groups);
+		if (g_hash_table_lookup (query, "include-sort-headers")) {
+			sort_by = g_hash_table_lookup (query, "sort");
+			if (g_strcmp0 (sort_by, "album") == 0) {
+				values = g_list_sort (values, group_info_cmp);
+			} else {
+				g_warning ("Unknown sort column: %s", sort_by);
+			}
+		}
+
+		for (value = values; value; value = g_list_next (value)) {
+			group_info = (GroupInfo*) value->data;
+			mlit = dmap_structure_add (mlcl, DMAP_CC_MLIT);
+			dmap_structure_add (mlit, DMAP_CC_MIID, (gint) group_info->group_id);
+			dmap_structure_add (mlit, DMAP_CC_MPER, group_info->group_id);
+			dmap_structure_add (mlit, DMAP_CC_MINM, group_info->name);
+			dmap_structure_add (mlit, DMAP_CC_ASAA, group_info->artist);
+			dmap_structure_add (mlit, DMAP_CC_MIMC, (gint32) group_info->count);
+
+			// Free this now, since the hash free func won't.
+			// Name will be freed when the hash table keys are freed.
+			g_free (group_info->artist);
+		}
+
+		g_list_free (values);
+		dmap_share_free_filter (filter_def);
+
+		_dmap_share_message_set_from_dmap_structure (share, message, agal);
+
+		g_hash_table_destroy (records);
+		g_hash_table_destroy (groups);
+		dmap_structure_destroy (agal);
 	} else if (g_ascii_strcasecmp ("/1/items", rest_of_path) == 0) {
 	/* ADBS database songs
 	 * 	MSTT status
@@ -1262,6 +1574,7 @@ _dmap_share_databases (DMAPShare *share,
 			g_debug ("Found %d records", g_hash_table_size (records));
 			num_songs = g_hash_table_size (records);
 			dmap_share_free_filter (filter_def);
+			g_hash_table_destroy (records);
 		} else {
 			num_songs = dmap_db_count (share->priv->db);
 		}
@@ -1278,7 +1591,6 @@ _dmap_share_databases (DMAPShare *share,
 
 		if (record_query) {
 			g_hash_table_foreach (records, (GHFunc) DMAP_SHARE_GET_CLASS (share)->add_entry_to_mlcl, &mb);
-			/* Free hash table but not data: */
 			g_hash_table_destroy (records);
 		} else {
 			dmap_db_foreach (share->priv->db, (GHFunc) DMAP_SHARE_GET_CLASS (share)->add_entry_to_mlcl, &mb);
@@ -1348,7 +1660,12 @@ _dmap_share_databases (DMAPShare *share,
 		GNode *apso;
 		struct DMAPMetaDataMap *map;
 		struct MLCL_Bits mb = {NULL,0};
-		gint pl_id = atoi (rest_of_path + 14);
+		gint pl_id;
+		gchar *record_query;
+		gchar *sort_by;
+		GSList *filter_def;
+		GHashTable *records;
+		GList *sorted_records;
 
 		map = DMAP_SHARE_GET_CLASS (share)->get_meta_data_map (share);
 		mb.bits = _dmap_share_parse_meta (query, map);
@@ -1356,31 +1673,62 @@ _dmap_share_databases (DMAPShare *share,
 		apso = dmap_structure_add (NULL, DMAP_CC_APSO);
 		dmap_structure_add (apso, DMAP_CC_MSTT, (gint32) DMAP_STATUS_OK);
 		dmap_structure_add (apso, DMAP_CC_MUTY, 0);
+		
+		if (g_ascii_strcasecmp ("/1/items", rest_of_path + 13) == 0) {
+			record_query = g_hash_table_lookup (query, "query");
+			filter_def = _dmap_share_build_filter (record_query);
+			records = dmap_db_apply_filter (DMAP_DB (share->priv->db), filter_def);
+			g_debug ("Found %d records", g_hash_table_size (records));
+			gint32 num_songs = g_hash_table_size (records);
+			dmap_share_free_filter (filter_def);
 
-		if (pl_id == 1) {
-			gint32 num_songs = dmap_db_count (share->priv->db);
 			dmap_structure_add (apso, DMAP_CC_MTCO, (gint32) num_songs);
 			dmap_structure_add (apso, DMAP_CC_MRCO, (gint32) num_songs);
 			mb.mlcl = dmap_structure_add (apso, DMAP_CC_MLCL);
 
-			dmap_db_foreach (share->priv->db, (GHFunc) DMAP_SHARE_GET_CLASS (share)->add_entry_to_mlcl, &mb);
+			sorted_records = g_hash_table_get_values (records);
+			sort_by = g_hash_table_lookup (query, "sort");
+			if (g_strcmp0 (sort_by, "album") == 0) {
+				sorted_records = g_list_sort (sorted_records, (GCompareFunc) daap_record_cmp_by_album);
+			} else if (sort_by != NULL) {
+				g_warning ("Unknown sort column: %s", sort_by);
+			}
+
+			GList *record;
+			for (record = sorted_records; record; record = record->next) {
+				(*(DMAP_SHARE_GET_CLASS (share)->add_entry_to_mlcl)) (0, record->data, &mb);
+			}
+
+			//g_hash_table_foreach (records, (GHFunc) DMAP_SHARE_GET_CLASS (share)->add_entry_to_mlcl, &mb);
+			g_list_free (sorted_records);
+			g_hash_table_destroy (records);
 		} else {
-			DMAPContainerRecord *record;
-			DMAPDb *entries;
-			guint num_songs;
+			pl_id = atoi (rest_of_path + 14);
+			if (pl_id == 1) {
+				gint32 num_songs = dmap_db_count (share->priv->db);
+				dmap_structure_add (apso, DMAP_CC_MTCO, (gint32) num_songs);
+				dmap_structure_add (apso, DMAP_CC_MRCO, (gint32) num_songs);
+				mb.mlcl = dmap_structure_add (apso, DMAP_CC_MLCL);
+
+				dmap_db_foreach (share->priv->db, (GHFunc) DMAP_SHARE_GET_CLASS (share)->add_entry_to_mlcl, &mb);
+			} else {
+				DMAPContainerRecord *record;
+				DMAPDb *entries;
+				guint num_songs;
 			
-			record = dmap_container_db_lookup_by_id (share->priv->container_db, pl_id);
-			entries = dmap_container_record_get_entries (record);
-			num_songs = dmap_db_count (entries);
+				record = dmap_container_db_lookup_by_id (share->priv->container_db, pl_id);
+				entries = dmap_container_record_get_entries (record);
+				num_songs = dmap_db_count (entries);
 			
-			dmap_structure_add (apso, DMAP_CC_MTCO, (gint32) num_songs);
-			dmap_structure_add (apso, DMAP_CC_MRCO, (gint32) num_songs);
-			mb.mlcl = dmap_structure_add (apso, DMAP_CC_MLCL);
+				dmap_structure_add (apso, DMAP_CC_MTCO, (gint32) num_songs);
+				dmap_structure_add (apso, DMAP_CC_MRCO, (gint32) num_songs);
+				mb.mlcl = dmap_structure_add (apso, DMAP_CC_MLCL);
 
-			dmap_db_foreach (entries, (GHFunc) DMAP_SHARE_GET_CLASS (share)->add_entry_to_mlcl, &mb);
+				dmap_db_foreach (entries, (GHFunc) DMAP_SHARE_GET_CLASS (share)->add_entry_to_mlcl, &mb);
 
-			g_object_unref (entries);
-			g_object_unref (record);
+				g_object_unref (entries);
+				g_object_unref (record);
+			}
 		}
 
 		_dmap_share_message_set_from_dmap_structure (share, message, apso);
diff --git a/libdmapsharing/dmap-share.h b/libdmapsharing/dmap-share.h
index 3da79d5..3385713 100644
--- a/libdmapsharing/dmap-share.h
+++ b/libdmapsharing/dmap-share.h
@@ -148,6 +148,10 @@ typedef struct {
 	void	  (*update)        (DMAPShare *share, SoupServer *server,
 				    SoupMessage *message, const char *path,
 				    GHashTable *query, SoupClientContext *ctx);
+				    
+	void	  (*ctrl_int)        (DMAPShare *share, SoupServer *server,
+				    SoupMessage *message, const char *path,
+				    GHashTable *query, SoupClientContext *ctx);
 
 	/* Virtual methods: MDNS callbacks */
 	void	  (*published)	   (DMAPShare         *share,
@@ -252,6 +256,20 @@ void _dmap_share_update (DMAPShare *share,
 		  GHashTable        *query,
 		  SoupClientContext *context);
 
+void
+_dmap_share_databases (DMAPShare *share,
+                       SoupServer        *server,
+		       SoupMessage       *message,
+		       const char        *path,
+		       GHashTable        *query,
+		       SoupClientContext *context);
+
+void _dmap_share_ctrl_int (DMAPShare         *share,
+		           SoupServer        *server,
+		           SoupMessage       *message,
+		           const char        *path,
+		           GHashTable        *query,
+		           SoupClientContext *context);
 
 /* Virtual methods: MDNS callbacks */
 void _dmap_share_published     (DMAPShare         *share,
@@ -262,13 +280,6 @@ void _dmap_share_name_collision(DMAPShare         *share,
 			       DmapMdnsPublisher *publisher,
 			       const char        *name);
 
-void
-_dmap_share_databases (DMAPShare *share,
-                       SoupServer        *server,
-		       SoupMessage       *message,
-		       const char        *path,
-		       GHashTable        *query,
-		       SoupClientContext *context);
 
 #endif /* __DMAP_SHARE_H */
 
diff --git a/libdmapsharing/dmap-structure.c b/libdmapsharing/dmap-structure.c
index 5e9470d..f36491c 100644
--- a/libdmapsharing/dmap-structure.c
+++ b/libdmapsharing/dmap-structure.c
@@ -76,6 +76,8 @@ static const DMAPContentCodeDefinition cc_defs[] = {
     {DMAP_CC_MUSR, MAKE_CONTENT_CODE('m','u','s','r'), "dmap.serverrevision", "musr", DMAP_TYPE_INT},
     {DMAP_CC_MUTY, MAKE_CONTENT_CODE('m','u','t','y'), "dmap.updatetype", "muty", DMAP_TYPE_BYTE},
     {DMAP_CC_MUDL, MAKE_CONTENT_CODE('m','u','d','l'), "dmap.deletedidlisting", "mudl", DMAP_TYPE_CONTAINER},
+    {DMAP_CC_MSMA, MAKE_CONTENT_CODE('m','s','m','a'), "dmap.speakermachineaddress", "msma", DMAP_TYPE_INT},
+
     {DMAP_CC_APRO, MAKE_CONTENT_CODE('a','p','r','o'), "daap.protocolversion", "apro", DMAP_TYPE_VERSION},
     {DMAP_CC_AVDB, MAKE_CONTENT_CODE('a','v','d','b'), "daap.serverdatabases", "avdb", DMAP_TYPE_CONTAINER},
     {DMAP_CC_ABRO, MAKE_CONTENT_CODE('a','b','r','o'), "daap.databasebrowse", "abro", DMAP_TYPE_CONTAINER},
@@ -85,6 +87,8 @@ static const DMAPContentCodeDefinition cc_defs[] = {
     {DMAP_CC_ABGN, MAKE_CONTENT_CODE('a','b','g','n'), "daap.browsegenrelisting", "abgn", DMAP_TYPE_CONTAINER},
     {DMAP_CC_ADBS, MAKE_CONTENT_CODE('a','d','b','s'), "daap.returndatabasesongs", "adbs", DMAP_TYPE_CONTAINER},
     {DMAP_CC_ASAL, MAKE_CONTENT_CODE('a','s','a','l'), "daap.songalbum", "asal", DMAP_TYPE_STRING},
+    {DMAP_CC_ASAI, MAKE_CONTENT_CODE('a','s','a','i'), "daap.songalbumid", "asai", DMAP_TYPE_INT},
+    {DMAP_CC_ASAA, MAKE_CONTENT_CODE('a','s','a','a'), "daap.songalbumartist", "asaa", DMAP_TYPE_STRING},
     {DMAP_CC_ASAR, MAKE_CONTENT_CODE('a','s','a','r'), "daap.songartist", "asar", DMAP_TYPE_STRING},
     {DMAP_CC_ASBT, MAKE_CONTENT_CODE('a','s','b','t'), "daap.songsbeatsperminute", "asbt", DMAP_TYPE_SHORT},
     {DMAP_CC_ASBR, MAKE_CONTENT_CODE('a','s','b','r'), "daap.songbitrate", "asbr", DMAP_TYPE_SHORT},
@@ -121,6 +125,7 @@ static const DMAPContentCodeDefinition cc_defs[] = {
     {DMAP_CC_AESV, MAKE_CONTENT_CODE('a','e','S','V'), "com.applie.itunes.music-sharing-version", "aesv", DMAP_TYPE_INT},
     {DMAP_CC_MSAS, MAKE_CONTENT_CODE('m','s','a','s'), "daap.authentication.schemes", "msas", DMAP_TYPE_BYTE},
     {DMAP_CC_AGRP, MAKE_CONTENT_CODE('a','g','r','p'), "daap.songgrouping", "agrp", DMAP_TYPE_STRING},
+    {DMAP_CC_AGAL, MAKE_CONTENT_CODE('a','g','a','l'), "daap.albumgrouping", "agal", DMAP_TYPE_CONTAINER},
     {DMAP_CC_ASCP, MAKE_CONTENT_CODE('a','s','c','p'), "daap.songcomposer", "ascp", DMAP_TYPE_STRING},
     {DMAP_CC_PPRO, MAKE_CONTENT_CODE('p','p','r','o'), "dpap.protocolversion", "ppro", DMAP_TYPE_VERSION},
     {DMAP_CC_PASP, MAKE_CONTENT_CODE('p','a','s','p'), "dpap.aspectratio", "pasp", DMAP_TYPE_STRING},
@@ -135,7 +140,44 @@ static const DMAPContentCodeDefinition cc_defs[] = {
     {DMAP_CC_PRAT, MAKE_CONTENT_CODE('p','r','a','t'), "dpap.imagerating", "prat", DMAP_TYPE_INT},
     {DMAP_CC_PCMT, MAKE_CONTENT_CODE('p','c','m','t'), "dpap.imagecomments", "pcmt", DMAP_TYPE_STRING},
     {DMAP_CC_PRET, MAKE_CONTENT_CODE('p','r','e','t'), "dpap.pret", "pret", DMAP_TYPE_STRING},
-    {DMAP_CC_AEHV, MAKE_CONTENT_CODE('a','e','H','V'), "com.apple.itunes.has-video", "aeHV", DMAP_TYPE_BYTE}
+    {DMAP_CC_AEHV, MAKE_CONTENT_CODE('a','e','H','V'), "com.apple.itunes.has-video", "aeHV", DMAP_TYPE_BYTE},
+    
+    /* DACP */
+    {DMAP_CC_CMPA, MAKE_CONTENT_CODE('c','m','p','a'), "dacp.contentcontainer", "cmpa", DMAP_TYPE_CONTAINER},
+    {DMAP_CC_CMNM, MAKE_CONTENT_CODE('c','m','n','m'), "dacp.contentname", "cmnm", DMAP_TYPE_STRING},
+    {DMAP_CC_CMTY, MAKE_CONTENT_CODE('c','m','t','y'), "dacp.contentvalue", "cmty", DMAP_TYPE_STRING},
+    {DMAP_CC_CMPG, MAKE_CONTENT_CODE('c','m','p','g'), "dacp.passguid", "cmpy", DMAP_TYPE_INT64},
+    
+    {DMAP_CC_CACI, MAKE_CONTENT_CODE('c','a','c','i'), "dacp.controlint", "caci", DMAP_TYPE_CONTAINER},
+    {DMAP_CC_CAPS, MAKE_CONTENT_CODE('c','a','p','s'), "dacp.playstatus", "caci", DMAP_TYPE_BYTE},
+    {DMAP_CC_CASH, MAKE_CONTENT_CODE('c','a','s','h'), "dacp.shufflestate", "caci", DMAP_TYPE_BYTE},
+    {DMAP_CC_CARP, MAKE_CONTENT_CODE('c','a','r','p'), "dacp.repeatstate", "caci", DMAP_TYPE_BYTE},
+    {DMAP_CC_CAAS, MAKE_CONTENT_CODE('c','a','a','s'), "dacp.albumshuffle", "caas", DMAP_TYPE_INT},
+    {DMAP_CC_CAAR, MAKE_CONTENT_CODE('c','a','a','r'), "dacp.albumrepeat", "caci", DMAP_TYPE_INT},    
+    {DMAP_CC_CAIA, MAKE_CONTENT_CODE('c','a','i','a'), "dacp.isavailiable", "caia", DMAP_TYPE_BYTE},
+    {DMAP_CC_CANP, MAKE_CONTENT_CODE('c','a','n','p'), "dacp.nowplaying", "canp", DMAP_TYPE_INT64},
+    {DMAP_CC_CANN, MAKE_CONTENT_CODE('c','a','n','n'), "dacp.nowplayingtrack", "cann", DMAP_TYPE_STRING},
+    {DMAP_CC_CANA, MAKE_CONTENT_CODE('c','a','n','a'), "dacp.nowplayingartist", "cana", DMAP_TYPE_STRING},
+    {DMAP_CC_CANL, MAKE_CONTENT_CODE('c','a','n','l'), "dacp.nowplayingalbum", "canl", DMAP_TYPE_STRING},
+    {DMAP_CC_CANG, MAKE_CONTENT_CODE('c','a','n','g'), "dacp.nowplayinggenre", "cang", DMAP_TYPE_STRING},
+    {DMAP_CC_CANT, MAKE_CONTENT_CODE('c','a','n','t'), "dacp.remainingtime", "cant", DMAP_TYPE_INT},
+    {DMAP_CC_CASP, MAKE_CONTENT_CODE('c','a','s','p'), "dacp.speakers", "casp", DMAP_TYPE_CONTAINER},
+    {DMAP_CC_CASS, MAKE_CONTENT_CODE('c','a','s','s'), "dacp.ss", "cass", DMAP_TYPE_BYTE},
+    {DMAP_CC_CAST, MAKE_CONTENT_CODE('c','a','s','t'), "dacp.tracklength", "cast", DMAP_TYPE_INT},
+    {DMAP_CC_CASU, MAKE_CONTENT_CODE('c','a','s','u'), "dacp.su", "casu", DMAP_TYPE_BYTE},
+    {DMAP_CC_CASG, MAKE_CONTENT_CODE('c','a','s','g'), "dacp.sg", "caSG", DMAP_TYPE_BYTE},
+    {DMAP_CC_CACR, MAKE_CONTENT_CODE('c','a','c','r'), "dacp.cacr", "cacr", DMAP_TYPE_CONTAINER},
+    
+    {DMAP_CC_CMCP, MAKE_CONTENT_CODE('c','m','c','p'), "dmcp.controlprompt", "cmcp", DMAP_TYPE_CONTAINER},
+    {DMAP_CC_CMGT, MAKE_CONTENT_CODE('c','m','g','t'), "dmcp.getpropertyresponse", "cmgt", DMAP_TYPE_CONTAINER},
+    {DMAP_CC_CMIK, MAKE_CONTENT_CODE('c','m','i','k'), "dmcp.ik", "cmik", DMAP_TYPE_BYTE},
+    {DMAP_CC_CMSP, MAKE_CONTENT_CODE('c','m','s','p'), "dmcp.ik", "cmsp", DMAP_TYPE_BYTE},
+    {DMAP_CC_CMST, MAKE_CONTENT_CODE('c','m','s','t'), "dmcp.status", "cmst", DMAP_TYPE_CONTAINER},
+    {DMAP_CC_CMSV, MAKE_CONTENT_CODE('c','m','s','v'), "dmcp.sv", "cmsv", DMAP_TYPE_BYTE},
+    {DMAP_CC_CMSR, MAKE_CONTENT_CODE('c','m','s','r'), "dmcp.mediarevision", "cmsr", DMAP_TYPE_INT},
+    {DMAP_CC_CMMK, MAKE_CONTENT_CODE('c','m','m','k'), "dmcp.mediakind", "cmmk", DMAP_TYPE_INT},
+    {DMAP_CC_CMVO, MAKE_CONTENT_CODE('c','m','v','o'), "dmcp.volume", "cmvo", DMAP_TYPE_INT},
+    
 };
 
 const gchar * 
@@ -300,6 +342,8 @@ dmap_content_code_read_from_buffer (const gchar *buf)
             return cc_defs[i].code;
         }
     }
+    
+    g_warning ("Content code %4s is invalid.", buf);
 
     return DMAP_CC_INVALID;
 }
@@ -338,7 +382,7 @@ dmap_structure_parse_container_buffer (GNode *parent,
         
         cc = dmap_content_code_read_from_buffer ((const gchar*)&(buf[l]));
         if (cc == DMAP_CC_INVALID) {
-            g_debug ("Invalid/Unknown content_code recieved\n");
+            //g_debug ("Invalid/Unknown content_code recieved\n");
             return;
         }
         l += 4;
@@ -415,7 +459,7 @@ dmap_structure_parse_container_buffer (GNode *parent,
                 gint64 i = 0;
         
                 if (codesize == 8) {
-                    i = DMAP_READ_UINT16_BE(&(buf[l]));
+                    i = DMAP_READ_UINT64_BE(&(buf[l]));
                 }
                 
                 item->size = 8;
diff --git a/libdmapsharing/dmap-structure.h b/libdmapsharing/dmap-structure.h
index dbd6000..c618e97 100644
--- a/libdmapsharing/dmap-structure.h
+++ b/libdmapsharing/dmap-structure.h
@@ -75,6 +75,8 @@ typedef enum {
 	DMAP_CC_MUSR,
 	DMAP_CC_MUTY, 
 	DMAP_CC_MUDL,
+	DMAP_CC_MSMA,
+	
 	DMAP_CC_APRO,
 	DMAP_CC_AVDB,
 	DMAP_CC_ABRO,
@@ -84,6 +86,8 @@ typedef enum {
 	DMAP_CC_ABGN,
 	DMAP_CC_ADBS,
 	DMAP_CC_ASAL,
+	DMAP_CC_ASAI,
+	DMAP_CC_ASAA,
 	DMAP_CC_ASAR, 
 	DMAP_CC_ASBT,
 	DMAP_CC_ASBR,
@@ -120,6 +124,7 @@ typedef enum {
 	DMAP_CC_AESV,
 	DMAP_CC_MSAS,
 	DMAP_CC_AGRP,
+	DMAP_CC_AGAL,
 	DMAP_CC_ASCP,
 	DMAP_CC_PPRO, 
 	DMAP_CC_PASP,
@@ -136,7 +141,43 @@ typedef enum {
 	DMAP_CC_PRET,
         
 	/* iTunes 6.02+ */
-	DMAP_CC_AEHV
+	DMAP_CC_AEHV,
+	
+	/* DACP */
+	DMAP_CC_CMPA,
+	DMAP_CC_CMNM,
+	DMAP_CC_CMTY,
+	DMAP_CC_CMPG,
+	
+	DMAP_CC_CACI,
+	DMAP_CC_CAPS,
+	DMAP_CC_CASH,
+	DMAP_CC_CARP,
+	DMAP_CC_CAAS,
+	DMAP_CC_CAAR,
+	DMAP_CC_CAIA,
+	DMAP_CC_CANP,
+	DMAP_CC_CANN,
+	DMAP_CC_CANA,
+	DMAP_CC_CANL,
+	DMAP_CC_CANG,
+	DMAP_CC_CANT,
+	DMAP_CC_CASP,
+	DMAP_CC_CASS,
+	DMAP_CC_CAST,
+	DMAP_CC_CASU,
+	DMAP_CC_CASG,
+	DMAP_CC_CACR,
+
+	DMAP_CC_CMCP,
+	DMAP_CC_CMGT,
+	DMAP_CC_CMIK,
+	DMAP_CC_CMSP,
+	DMAP_CC_CMST,
+	DMAP_CC_CMSV,
+	DMAP_CC_CMSR,
+	DMAP_CC_CMMK,
+	DMAP_CC_CMVO
 } DMAPContentCode;
 
 typedef struct _DMAPStructureItem DMAPStructureItem;
diff --git a/libdmapsharing/dmap-utils.h b/libdmapsharing/dmap-utils.h
index ec76004..6cc8df4 100644
--- a/libdmapsharing/dmap-utils.h
+++ b/libdmapsharing/dmap-utils.h
@@ -31,6 +31,7 @@ G_BEGIN_DECLS
 #define _DMAP_GET(__data, __size, __end) \
     (GUINT##__size##_FROM_##__end (* ((guint##__size *) (__data))))
 
+#define DMAP_READ_UINT64_BE(data) _DMAP_GET (data, 64, BE)
 #define DMAP_READ_UINT32_BE(data) _DMAP_GET (data, 32, BE)
 #define DMAP_READ_UINT16_BE(data) _DMAP_GET (data, 16, BE)
 #define DMAP_READ_UINT8(data)     (* ((guint8 *) (data)))
@@ -40,6 +41,15 @@ G_BEGIN_DECLS
 #define _DMAP_GET(__data, __idx, __size, __shift) \
     (((guint##__size) (((guint8 *) (__data))[__idx])) << __shift)
 
+#define DMAP_READ_UINT64_BE(data)  \
+    (_DMAP_GET (data, 0, 64, 56) | \
+     _DMAP_GET (data, 1, 64, 48) | \
+     _DMAP_GET (data, 2, 64, 40) | \
+     _DMAP_GET (data, 3, 64, 32) | \
+     _DMAP_GET (data, 4, 64, 24) | \
+     _DMAP_GET (data, 5, 64, 16) | \
+     _DMAP_GET (data, 6, 64,  8) | \
+     _DMAP_GET (data, 7, 64,  0))
 #define DMAP_READ_UINT32_BE(data) \
     (_DMAP_GET (data, 0, 32, 24) | \
      _DMAP_GET (data, 1, 32, 16) | \
diff --git a/libdmapsharing/dmap.h b/libdmapsharing/dmap.h
index c3aecbc..81fcfc0 100644
--- a/libdmapsharing/dmap.h
+++ b/libdmapsharing/dmap.h
@@ -15,5 +15,7 @@
 #include <libdmapsharing/dmap-share.h>
 #include <libdmapsharing/dpap-record.h>
 #include <libdmapsharing/dpap-share.h>
+#include <libdmapsharing/dacp-share.h>
+#include <libdmapsharing/dacp-player.h>
 
 #endif /* __DMAP_H */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 87b981c..acf0ee5 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -13,6 +13,7 @@ test_dmap_client_LDADD = \
 	$(GLIB_LIBS) \
 	$(GTHREAD_LIBS) \
 	$(GOBJECT_LIBS) \
+	$(GDKPIXBUF_LIBS) \
 	$(SOUP_LIBS) \
 	$(IMAGEMAGICK_LIBS) \
 	$(MDNS_LIBS)
@@ -32,6 +33,7 @@ test_dmap_server_LDADD = \
 	$(GLIB_LIBS) \
 	$(GTHREAD_LIBS) \
 	$(GOBJECT_LIBS) \
+	$(GDKPIXBUF_LIBS) \
 	$(SOUP_LIBS) \
 	$(IMAGEMAGICK_LIBS) \
 	$(MDNS_LIBS)
@@ -41,6 +43,7 @@ AM_CPPFLAGS = \
 	$(GLIB_CFLAGS) \
 	$(GTHREAD_CFLAGS) \
 	$(GOBJECT_CFLAGS) \
+	$(GDKPIXBUF_CFLAGS) \
 	$(SOUP_CFLAGS) \
 	$(MDNS_CFLAGS)
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]