[grilo-plugins/0.2.x: 24/30] dmap: Add DPAP plugin



commit 35282097d5b050fda7cc8bba6aa0363203477069
Author: W. Michael Petullo <mike flyn org>
Date:   Sat Sep 5 08:57:34 2015 -0400

    dmap: Add DPAP plugin
    
    https://bugzilla.gnome.org/show_bug.cgi?id=746722

 configure.ac                       |    4 +
 src/dmap/Makefile.am               |   34 +++-
 src/dmap/grl-dpap-db.c             |  368 ++++++++++++++++++++++++++++++
 src/dmap/grl-dpap-db.h             |   90 ++++++++
 src/dmap/grl-dpap-record-factory.c |   58 +++++
 src/dmap/grl-dpap-record-factory.h |   72 ++++++
 src/dmap/grl-dpap-record.c         |  247 ++++++++++++++++++++
 src/dmap/grl-dpap-record.h         |   78 +++++++
 src/dmap/grl-dpap.c                |  439 ++++++++++++++++++++++++++++++++++++
 src/dmap/grl-dpap.h                |   73 ++++++
 src/dmap/grl-dpap.xml              |   10 +
 11 files changed, 1471 insertions(+), 2 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 0d1bfaa..a0768d5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1079,6 +1079,10 @@ DAAP_PLUGIN_ID="grl-daap"
 AC_SUBST(DAAP_PLUGIN_ID)
 AC_DEFINE_UNQUOTED([DAAP_PLUGIN_ID], ["$DAAP_PLUGIN_ID"], [DAAP plugin ID])
 
+DPAP_PLUGIN_ID="grl-dpap"
+AC_SUBST(DPAP_PLUGIN_ID)
+AC_DEFINE_UNQUOTED([DPAP_PLUGIN_ID], ["$DPAP_PLUGIN_ID"], [DPAP plugin ID])
+
 DEPS_DMAP_CFLAGS="$DEPS_CFLAGS $DMAP_CFLAGS $XML_CFLAGS"
 AC_SUBST(DEPS_DMAP_CFLAGS)
 DEPS_DMAP_LIBS="$DEPS_LIBS $DMAP_LIBS $XML_LIBS"
diff --git a/src/dmap/Makefile.am b/src/dmap/Makefile.am
index c89bb0f..e6f3cf0 100644
--- a/src/dmap/Makefile.am
+++ b/src/dmap/Makefile.am
@@ -7,7 +7,8 @@
 
 include $(top_srcdir)/gtester.mk
 
-ext_LTLIBRARIES        = libgrldaap.la
+ext_LTLIBRARIES        = libgrldaap.la libgrldpap.la
+extdir                 = $(GRL_PLUGINS_DIR)
 
 libgrldaap_la_CFLAGS = \
        $(DEPS_DMAP_CFLAGS)     \
@@ -34,10 +35,39 @@ libgrldaap_la_SOURCES =                             \
        grl-daap-db.c                                   \
        grl-daap-db.h
 
-extdir                 = $(GRL_PLUGINS_DIR)
 daapxmldir             = $(GRL_PLUGINS_DIR)
 daapxml_DATA   = $(DAAP_PLUGIN_ID).xml
 
 EXTRA_DIST    += $(daapxml_DATA)
 
+libgrldpap_la_CFLAGS = \
+       $(DEPS_DMAP_CFLAGS)     \
+       -DG_LOG_DOMAIN=\"GrlDmap\" \
+       -DLOCALEDIR=\"$(localedir)\"
+
+libgrldpap_la_LIBADD = \
+       $(DEPS_DMAP_LIBS)
+
+libgrldpap_la_LDFLAGS =        \
+       -no-undefined                   \
+       -module                                 \
+       -avoid-version
+
+libgrldpap_la_SOURCES =                                \
+       grl-common.c                                            \
+       grl-common.h                                            \
+       grl-dpap.c                                                      \
+       grl-dpap.h                                                      \
+       grl-dpap-record.c                               \
+       grl-dpap-record.h                               \
+       grl-dpap-record-factory.c       \
+       grl-dpap-record-factory.h       \
+       grl-dpap-db.c                                   \
+       grl-dpap-db.h
+
+dpapxmldir             = $(GRL_PLUGINS_DIR)
+dpapxml_DATA   = $(DPAP_PLUGIN_ID).xml
+
+EXTRA_DIST    += $(dpapxml_DATA)
+
 -include $(top_srcdir)/git.mk
diff --git a/src/dmap/grl-dpap-db.c b/src/dmap/grl-dpap-db.c
new file mode 100644
index 0000000..ea418d2
--- /dev/null
+++ b/src/dmap/grl-dpap-db.c
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2012 W. Michael Petullo.
+ *
+ * Contact: W. Michael Petullo <mike flyn org>
+ *
+ * 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; 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
+ */
+
+/* See grl-daap-db.c for a description of this database. */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib/gi18n-lib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <glib.h>
+#include <string.h>
+
+#include "grl-dpap-db.h"
+
+#define PHOTOS_ID     "photos"
+#define PHOTOS_NAME _("Photos")
+
+/* Media IDs start at max and go down. Container IDs start at 1 and go up. */
+static guint nextid = G_MAXINT; /* NOTE: this should be G_MAXUINT, but iPhoto can't handle it. */
+
+struct GrlDPAPDbPrivate {
+  /* Contains each picture box (tracked with photos hash table) */
+  GrlMediaBox *photos_box;
+
+  GHashTable  *root;
+  GHashTable  *photos;
+};
+
+enum {
+  PROP_0,
+  PROP_RECORD_FACTORY,
+};
+
+static guint
+box_hash (gconstpointer a)
+{
+  return g_str_hash (grl_media_get_id (GRL_MEDIA (a)));
+}
+
+static gboolean
+box_equal (gconstpointer a, gconstpointer b)
+{
+  return g_str_equal (grl_media_get_id (GRL_MEDIA (a)), grl_media_get_id (GRL_MEDIA (b)));
+}
+
+GrlDPAPDb *
+grl_dpap_db_new (void)
+{
+  GrlDPAPDb *db = g_object_new (TYPE_GRL_DPAP_DB, NULL);
+
+  return db;
+}
+
+static DMAPRecord *
+grl_dpap_db_lookup_by_id (const DMAPDb *db, guint id)
+{
+  g_warning ("Not implemented");
+  return NULL;
+}
+
+static void
+grl_dpap_db_foreach (const DMAPDb *db,
+                        GHFunc func,
+                        gpointer data)
+{
+  g_warning ("Not implemented");
+}
+
+static gint64
+grl_dpap_db_count (const DMAPDb *db)
+{
+  g_warning ("Not implemented");
+  return 0;
+}
+
+static void
+set_insert (GHashTable *category, const char *category_name, char *set_name, GrlMedia *media)
+{
+  gchar      *id = NULL;
+  GrlMedia   *box;
+  GHashTable *set;
+
+  id = g_strdup_printf ("%s-%s", category_name, set_name);
+
+  box = g_object_new (GRL_TYPE_MEDIA_BOX, NULL);
+  grl_media_set_id    (box, id);
+  grl_media_set_title (box, set_name);
+
+  set = g_hash_table_lookup (category, box);
+  if (set == NULL) {
+    set = g_hash_table_new_full (box_hash, box_equal, g_object_unref, NULL);
+    g_hash_table_insert (category, g_object_ref (box), set);
+  }
+
+  g_hash_table_insert (set, g_object_ref (media), NULL);
+
+  g_free (id);
+  g_object_unref (box);
+}
+
+static guint
+grl_dpap_db_add (DMAPDb *_db, DMAPRecord *_record)
+{
+  g_assert (IS_GRL_DPAP_DB (_db));
+  g_assert (IS_DPAP_RECORD (_record));
+
+  GrlDPAPDb *db = GRL_DPAP_DB (_db);
+  DPAPRecord *record = DPAP_RECORD (_record);
+
+  gint        height        = 0,
+              width         = 0,
+              largefilesize = 0,
+              creationdate  = 0,
+              rating        = 0;
+  GByteArray *thumbnail     = NULL;
+  gchar      *id_s          = NULL,
+             *filename      = NULL,
+             *aspectratio   = NULL,
+             *format        = NULL,
+             *comments      = NULL,
+             *url           = NULL;
+  GrlMedia   *media;
+
+  g_object_get (record,
+               "large-filesize",
+               &largefilesize,
+               "creation-date",
+               &creationdate,
+               "rating",
+               &rating,
+               "filename",
+               &filename,
+               "aspect-ratio",
+               &aspectratio,
+               "pixel-height",
+               &height,
+               "pixel-width",
+               &width,
+               "format",
+               &format,
+               "comments",
+               &comments,
+               "thumbnail",
+               &thumbnail,
+               "location",
+               &url,
+                NULL);
+
+  id_s = g_strdup_printf ("%u", nextid);
+
+  media = grl_media_image_new ();
+
+  grl_media_set_id           (media, id_s);
+
+  if (filename)
+    grl_media_set_title (media, filename);
+
+  if (url) {
+    /* Replace URL's dpap:// with http:// */
+    memcpy (url, "http", 4);
+    grl_media_set_url (media, url);
+  }
+
+  GrlMediaImage *media_image = GRL_MEDIA_IMAGE (media);
+
+  grl_media_image_set_width  (media_image, width);
+  grl_media_image_set_height (media_image, height);
+ 
+  set_insert (db->priv->photos,  PHOTOS_ID, "Unknown",  media);
+
+  g_free (id_s);
+  g_object_unref (media);
+
+  return --nextid;
+}
+
+static gboolean
+same_media (GrlMedia *a, GrlMedia *b)
+{
+  return (strcmp (grl_media_get_id (a), grl_media_get_id (b)) == 0);
+}
+
+void
+grl_dpap_db_browse (GrlDPAPDb *db,
+                    GrlMedia *container,
+                    GrlSource *source,
+                    guint op_id,
+                    guint skip,
+                    guint count,
+                    GrlSourceResultCb func,
+                    gpointer user_data)
+{
+  g_assert (IS_GRL_DPAP_DB (db));
+
+  int i;
+  guint remaining;
+  GHashTable *hash_table;
+  GHashTableIter iter;
+  gpointer key, val;
+
+  const gchar *box_id = grl_media_get_id (container);
+  if (box_id == NULL) {
+    hash_table = db->priv->root;
+  } else if (same_media (container, GRL_MEDIA (db->priv->photos_box))) {
+    hash_table = db->priv->photos;
+  } else {
+    hash_table = g_hash_table_lookup (db->priv->photos, container);
+  }
+
+  /* Should not be NULL; this means the container requested
+     does not exist in the database. */
+  if (hash_table == NULL) {
+    GError *error = g_error_new (GRL_CORE_ERROR,
+                                 GRL_CORE_ERROR_BROWSE_FAILED,
+                                 "Invalid container identifier %s",
+                                 box_id);
+    func (source, op_id, NULL, 0, user_data, error);
+    goto done;
+  }
+
+  remaining = g_hash_table_size (hash_table) - skip;
+  remaining = remaining < count ? remaining : count;
+  g_hash_table_iter_init (&iter, hash_table);
+  for (i = 0; g_hash_table_iter_next (&iter, &key, &val) && i < skip + count; i++) {
+    if (i < skip)
+      continue;
+    if (GRL_IS_MEDIA_BOX (key))
+      grl_media_box_set_childcount (GRL_MEDIA_BOX (key), g_hash_table_size (val));
+    func (source, op_id, GRL_MEDIA (g_object_ref (key)), --remaining, user_data, NULL);
+  }
+done:
+  return;
+}
+
+void
+grl_dpap_db_search (GrlDPAPDb *db,
+                    GrlSource *source,
+                    guint op_id,
+                    GHRFunc predicate,
+                    gpointer pred_user_data,
+                    GrlSourceResultCb func,
+                    gpointer user_data)
+{
+  g_assert (IS_GRL_DPAP_DB (db));
+
+  gint i, j, k;
+  guint remaining = 0;
+  gpointer key1, val1, key2, val2;
+  GHashTable *hash_tables[] = { db->priv->photos };
+
+  /* Use hash table to avoid duplicates */
+  GHashTable *results = NULL;
+  GHashTableIter iter1, iter2;
+
+  results = g_hash_table_new (g_str_hash, g_str_equal);
+
+  /* For photos ... */
+  for (i = 0; i < G_N_ELEMENTS (hash_tables); i++) {
+    g_hash_table_iter_init (&iter1, hash_tables[i]);
+    /* For each album or artist in above... */
+    for (j = 0; g_hash_table_iter_next (&iter1, &key1, &val1); j++) {
+      if (GRL_IS_MEDIA_BOX (key1)) {
+        g_hash_table_iter_init (&iter2, val1);
+        /* For each media item in above... */
+        for (k = 0; g_hash_table_iter_next (&iter2, &key2, &val2); k++) {
+          const char *id = grl_media_get_id (GRL_MEDIA (key2));
+          /* If the predicate returns true, add to results set. */
+          if (predicate (key2, val2, pred_user_data)
+           && ! g_hash_table_contains (results, id)) {
+            remaining++;
+            g_hash_table_insert (results, (gpointer) id, key2);
+          }
+        }
+      }
+    }
+  }
+
+  /* Process results set. */
+  g_hash_table_iter_init (&iter1, results);
+  for (i = 0; g_hash_table_iter_next (&iter1, &key1, &val1); i++) {
+    func (source, op_id, GRL_MEDIA (g_object_ref (val1)), --remaining, user_data, NULL);
+  }
+}
+
+static void
+dmap_db_interface_init (gpointer iface, gpointer data)
+{
+  DMAPDbIface *dpap_db = iface;
+
+  g_assert (G_TYPE_FROM_INTERFACE (dpap_db) == DMAP_TYPE_DB);
+
+  dpap_db->add = grl_dpap_db_add;
+  dpap_db->lookup_by_id = grl_dpap_db_lookup_by_id;
+  dpap_db->foreach = grl_dpap_db_foreach;
+  dpap_db->count = grl_dpap_db_count;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GrlDPAPDb, grl_dpap_db, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (DMAP_TYPE_DB, dmap_db_interface_init))
+
+static GObject*
+grl_dpap_db_constructor (GType type, guint n_construct_params, GObjectConstructParam *construct_params)
+{
+  GObject *object;
+
+  object = G_OBJECT_CLASS (grl_dpap_db_parent_class)->constructor (type, n_construct_params, 
construct_params);
+
+  return object;
+}
+
+static void
+grl_dpap_db_init (GrlDPAPDb *db)
+{
+  db->priv = GRL_DPAP_DB_GET_PRIVATE (db);
+
+  db->priv->photos_box  = g_object_new (GRL_TYPE_MEDIA_BOX, NULL);
+
+  grl_media_set_id    (GRL_MEDIA (db->priv->photos_box), PHOTOS_ID);
+  grl_media_set_title (GRL_MEDIA (db->priv->photos_box), PHOTOS_NAME);
+
+  db->priv->root   = g_hash_table_new_full (box_hash, box_equal, g_object_unref, (GDestroyNotify) 
g_hash_table_destroy);
+  db->priv->photos = g_hash_table_new_full (box_hash, box_equal, g_object_unref, (GDestroyNotify) 
g_hash_table_destroy);
+
+  g_hash_table_insert (db->priv->root, g_object_ref (db->priv->photos_box), db->priv->photos);
+}
+
+static void
+grl_dpap_db_finalize (GObject *object)
+{
+  GrlDPAPDb *db = GRL_DPAP_DB (object);
+
+  GRL_DEBUG ("Finalizing GrlDPAPDb");
+
+  g_object_unref (db->priv->photos_box);
+
+  g_hash_table_destroy (db->priv->photos);
+}
+
+static void
+grl_dpap_db_class_init (GrlDPAPDbClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (GrlDPAPDbPrivate));
+
+  object_class->finalize = grl_dpap_db_finalize;
+  object_class->constructor = grl_dpap_db_constructor;
+}
diff --git a/src/dmap/grl-dpap-db.h b/src/dmap/grl-dpap-db.h
new file mode 100644
index 0000000..4c17d1a
--- /dev/null
+++ b/src/dmap/grl-dpap-db.h
@@ -0,0 +1,90 @@
+/*
+ *  Database class for DPAP sharing
+ *
+ *  Copyright (C) 2008 W. Michael Petullo <mike flyn org>
+ *
+ * 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 __GRL_DPAP_DB
+#define __GRL_DPAP_DB
+
+#include <libdmapsharing/dmap.h>
+#include <grilo.h>
+
+G_BEGIN_DECLS
+
+#define TYPE_GRL_DPAP_DB                     \
+  (grl_dpap_db_get_type ())
+
+#define GRL_DPAP_DB(o)                             \
+  (G_TYPE_CHECK_INSTANCE_CAST ((o),                   \
+                               TYPE_GRL_DPAP_DB,   \
+                               GrlDPAPDb))
+
+#define GRL_DPAP_DB_CLASS(k)                 \
+  (G_TYPE_CHECK_CLASS_CAST((k),                 \
+                           TYPE_GRL_DPAP_DB, \
+                           GrlDPAPDbClass))
+#define IS_GRL_DPAP_DB(o)                          \
+  (G_TYPE_CHECK_INSTANCE_TYPE((o),                    \
+                              TYPE_GRL_DPAP_DB))
+#define IS_GRL_DPAP_DB_CLASS(k)                       \
+  (G_TYPE_CHECK_CLASS_TYPE((k),                          \
+                           TYPE_GRL_DPAP_DB_CLASS))
+
+#define GRL_DPAP_DB_GET_CLASS(o)                \
+  (G_TYPE_INSTANCE_GET_CLASS((o),                  \
+                             TYPE_GRL_DPAP_DB,  \
+                             GrlDPAPDbClass))
+
+#define GRL_DPAP_DB_GET_PRIVATE(o)                 \
+  (G_TYPE_INSTANCE_GET_PRIVATE((o),                   \
+                               TYPE_GRL_DPAP_DB,   \
+                               GrlDPAPDbPrivate))
+
+typedef struct GrlDPAPDbPrivate GrlDPAPDbPrivate;
+
+typedef struct {
+  GObject parent;
+  GrlDPAPDbPrivate *priv;
+} GrlDPAPDb;
+
+typedef struct {
+  GObjectClass parent;
+} GrlDPAPDbClass;
+
+GrlDPAPDb *grl_dpap_db_new (void);
+void grl_dpap_db_browse (GrlDPAPDb *_db,
+                         GrlMedia *container,
+                         GrlSource *source,
+                         guint op_id,
+                         guint skip,
+                         guint count,
+                         GrlSourceResultCb func,
+                         gpointer user_data);
+void grl_dpap_db_search (GrlDPAPDb *_db,
+                         GrlSource *source,
+                         guint op_id,
+                         GHRFunc predicate,
+                         gpointer pred_user_data,
+                         GrlSourceResultCb func,
+                         gpointer user_data);
+
+GType grl_dpap_db_get_type (void);
+
+#endif /* __GRL_DPAP_DB */
+
+G_END_DECLS
diff --git a/src/dmap/grl-dpap-record-factory.c b/src/dmap/grl-dpap-record-factory.c
new file mode 100644
index 0000000..8174338
--- /dev/null
+++ b/src/dmap/grl-dpap-record-factory.c
@@ -0,0 +1,58 @@
+/*
+ * DPAPRecord factory class
+ *
+ * Copyright (C) 2008 W. Michael Petullo <mike flyn org>
+ *
+ * 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 "grl-dpap-record-factory.h"
+#include "grl-dpap-record.h"
+
+DMAPRecord *
+grl_dpap_record_factory_create  (DMAPRecordFactory *factory, gpointer user_data)
+{
+       return DMAP_RECORD (grl_dpap_record_new ());
+}
+
+static void
+grl_dpap_record_factory_init (GrlDPAPRecordFactory *factory)
+{
+}
+
+static void
+grl_dpap_record_factory_class_init (GrlDPAPRecordFactoryClass *klass)
+{
+}
+
+static void
+grl_dpap_record_factory_interface_init (gpointer iface, gpointer data)
+{
+       DMAPRecordFactoryIface *factory = iface;
+
+       g_assert (G_TYPE_FROM_INTERFACE (factory) == DMAP_TYPE_RECORD_FACTORY);
+
+       factory->create = grl_dpap_record_factory_create;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GrlDPAPRecordFactory, grl_dpap_record_factory, G_TYPE_OBJECT,
+                        G_IMPLEMENT_INTERFACE (DMAP_TYPE_RECORD_FACTORY,
+                                               grl_dpap_record_factory_interface_init))
+
+GrlDPAPRecordFactory *
+grl_dpap_record_factory_new (void)
+{
+       return SIMPLE_DPAP_RECORD_FACTORY (g_object_new (TYPE_SIMPLE_DPAP_RECORD_FACTORY, NULL));
+}
diff --git a/src/dmap/grl-dpap-record-factory.h b/src/dmap/grl-dpap-record-factory.h
new file mode 100644
index 0000000..c2106b1
--- /dev/null
+++ b/src/dmap/grl-dpap-record-factory.h
@@ -0,0 +1,72 @@
+/*
+ * GrlDPAPRecord factory class
+ *
+ * Copyright (C) 2008 W. Michael Petullo <mike flyn org>
+ *
+ * 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 __SIMPLE_DPAP_RECORD_FACTORY
+#define __SIMPLE_DPAP_RECORD_FACTORY
+
+#include <libdmapsharing/dmap.h>
+
+G_BEGIN_DECLS
+
+#define TYPE_SIMPLE_DPAP_RECORD_FACTORY         \
+  (grl_dpap_record_factory_get_type ())
+
+#define SIMPLE_DPAP_RECORD_FACTORY(o)                          \
+  (G_TYPE_CHECK_INSTANCE_CAST((o),                             \
+                              TYPE_SIMPLE_DPAP_RECORD_FACTORY, \
+                              GrlDPAPRecordFactory))
+
+#define SIMPLE_DPAP_RECORD_FACTORY_CLASS(k)                 \
+  (G_TYPE_CHECK_CLASS_CAST((k),                             \
+                           TYPE_SIMPLE_DPAP_RECORD_FACTORY, \
+                           GrlDPAPRecordFactoryClass))
+
+#define IS_SIMPLE_DPAP_RECORD_FACTORY(o)                          \
+  (G_TYPE_CHECK_INSTANCE_TYPE((o),                                \
+                              TYPE_SIMPLE_DPAP_RECORD_FACTORY))
+
+#define IS_SIMPLE_DPAP_RECORD_FACTORY_CLASS(k)                       \
+  (G_TYPE_CHECK_CLASS_TYPE((k),                                      \
+                           TYPE_SIMPLE_DPAP_RECORD_FACTORY_CLASS))
+
+#define SIMPLE_DPAP_RECORD_FACTORY_GET_CLASS(o)                \
+  (G_TYPE_INSTANCE_GET_CLASS((o),                              \
+                             TYPE_SIMPLE_DPAP_RECORD_FACTORY,  \
+                             GrlDPAPRecordFactoryClass))
+
+typedef struct GrlDPAPRecordFactoryPrivate GrlDPAPRecordFactoryPrivate;
+
+typedef struct {
+  GObject parent;
+} GrlDPAPRecordFactory;
+
+typedef struct {
+  GObjectClass parent;
+} GrlDPAPRecordFactoryClass;
+
+GType                    grl_dpap_record_factory_get_type (void);
+
+GrlDPAPRecordFactory *grl_dpap_record_factory_new      (void);
+
+DMAPRecord              *grl_dpap_record_factory_create   (DMAPRecordFactory *factory, gpointer user_data);
+
+#endif /* __SIMPLE_DPAP_RECORD_FACTORY */
+
+G_END_DECLS
diff --git a/src/dmap/grl-dpap-record.c b/src/dmap/grl-dpap-record.c
new file mode 100644
index 0000000..c4a73db
--- /dev/null
+++ b/src/dmap/grl-dpap-record.c
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2012 W. Michael Petullo.
+ *
+ * Contact: W. Michael Petullo <mike flyn org>
+ *
+ * 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; 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 "grl-dpap-record.h"
+
+struct GrlDPAPRecordPrivate {
+  char *location;
+  gint largefilesize;
+  gint creationdate;
+  gint rating;
+  char *filename;
+  GByteArray *thumbnail;
+  char *aspectratio;
+  gint height;
+  gint width;
+  char *format;
+  char *comments;
+};
+
+enum {
+  PROP_0,
+  PROP_LOCATION,
+  PROP_LARGE_FILESIZE,
+  PROP_CREATION_DATE,
+  PROP_RATING,
+  PROP_FILENAME,
+  PROP_ASPECT_RATIO,
+  PROP_PIXEL_HEIGHT,
+  PROP_PIXEL_WIDTH,
+  PROP_FORMAT,
+  PROP_COMMENTS,
+  PROP_THUMBNAIL
+};
+
+static void
+grl_dpap_record_set_property (GObject *object,
+                                guint prop_id,
+                                const GValue *value,
+                                GParamSpec *pspec)
+{
+  GrlDPAPRecord *record = SIMPLE_DPAP_RECORD (object);
+
+  switch (prop_id) {
+  case PROP_LOCATION:
+    g_free (record->priv->location);
+    record->priv->location = g_value_dup_string (value);
+    break;
+  case PROP_LARGE_FILESIZE:
+    record->priv->largefilesize = g_value_get_int (value);
+    break;
+  case PROP_CREATION_DATE:
+    record->priv->creationdate = g_value_get_int (value);
+    break;
+  case PROP_RATING:
+    record->priv->rating = g_value_get_int (value);
+    break;
+  case PROP_FILENAME:
+    g_free (record->priv->filename);
+    record->priv->filename = g_value_dup_string (value);
+    break;
+  case PROP_ASPECT_RATIO:
+    g_free (record->priv->aspectratio);
+    record->priv->aspectratio = g_value_dup_string (value);
+    break;
+  case PROP_PIXEL_HEIGHT:
+    record->priv->height = g_value_get_int (value);
+    break;
+  case PROP_PIXEL_WIDTH:
+    record->priv->width = g_value_get_int (value);
+    break;
+  case PROP_FORMAT:
+    g_free (record->priv->format);
+    record->priv->format = g_value_dup_string (value);
+    break;
+  case PROP_COMMENTS:
+    g_free (record->priv->comments);
+    record->priv->comments = g_value_dup_string (value);
+    break;
+  case PROP_THUMBNAIL:
+    if (record->priv->thumbnail)
+      g_byte_array_unref (record->priv->thumbnail);
+    record->priv->thumbnail = g_byte_array_ref (g_value_get_pointer (value));
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    break;
+  }
+}
+
+static void
+grl_dpap_record_get_property (GObject *object,
+                                 guint prop_id,
+                                 GValue *value,
+                                 GParamSpec *pspec)
+{
+  GrlDPAPRecord *record = SIMPLE_DPAP_RECORD (object);
+
+  switch (prop_id) {
+  case PROP_LOCATION:
+    g_value_set_static_string (value, record->priv->location);
+    break;
+  case PROP_LARGE_FILESIZE:
+    g_value_set_int (value, record->priv->largefilesize);
+    break;
+  case PROP_CREATION_DATE:
+    g_value_set_int (value, record->priv->creationdate);
+    break;
+  case PROP_RATING:
+    g_value_set_int (value, record->priv->rating);
+    break;
+  case PROP_FILENAME:
+    g_value_set_static_string (value, record->priv->filename);
+    break;
+  case PROP_ASPECT_RATIO:
+    g_value_set_static_string (value, record->priv->aspectratio);
+    break;
+  case PROP_PIXEL_HEIGHT:
+    g_value_set_int (value, record->priv->height);
+    break;
+  case PROP_PIXEL_WIDTH:
+    g_value_set_int (value, record->priv->width);
+    break;
+  case PROP_FORMAT:
+    g_value_set_static_string (value, record->priv->format);
+    break;
+  case PROP_COMMENTS:
+    g_value_set_static_string (value, record->priv->comments);
+    break;
+  case PROP_THUMBNAIL:
+    g_value_set_pointer (value, record->priv->thumbnail);
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    break;
+  }
+}
+
+GrlDPAPRecord *
+grl_dpap_record_new (void)
+{
+  return SIMPLE_DPAP_RECORD (g_object_new (TYPE_SIMPLE_DPAP_RECORD, NULL));
+}
+
+GInputStream *
+grl_dpap_record_read (DPAPRecord *record, GError **error)
+{
+  GFile *file;
+  GInputStream *stream;
+
+  file = g_file_new_for_uri (SIMPLE_DPAP_RECORD (record)->priv->location);
+  stream = G_INPUT_STREAM (g_file_read (file, NULL, error));
+
+  g_object_unref (file);
+
+  return stream;
+}
+
+static void
+grl_dpap_record_init (GrlDPAPRecord *record)
+{
+  record->priv = SIMPLE_DPAP_RECORD_GET_PRIVATE (record);
+}
+
+static void grl_dpap_record_finalize (GObject *object);
+
+static void
+grl_dpap_record_class_init (GrlDPAPRecordClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (GrlDPAPRecordPrivate));
+
+  gobject_class->set_property = grl_dpap_record_set_property;
+  gobject_class->get_property = grl_dpap_record_get_property;
+  gobject_class->finalize     = grl_dpap_record_finalize;
+
+  g_object_class_override_property (gobject_class, PROP_LOCATION, "location");
+  g_object_class_override_property (gobject_class, PROP_LARGE_FILESIZE, "large-filesize");
+  g_object_class_override_property (gobject_class, PROP_CREATION_DATE, "creation-date");
+  g_object_class_override_property (gobject_class, PROP_RATING, "rating");
+  g_object_class_override_property (gobject_class, PROP_FILENAME, "filename");
+  g_object_class_override_property (gobject_class, PROP_ASPECT_RATIO, "aspect-ratio");
+  g_object_class_override_property (gobject_class, PROP_PIXEL_HEIGHT, "pixel-height");
+  g_object_class_override_property (gobject_class, PROP_PIXEL_WIDTH, "pixel-width");
+  g_object_class_override_property (gobject_class, PROP_FORMAT, "format");
+  g_object_class_override_property (gobject_class, PROP_COMMENTS, "comments");
+  g_object_class_override_property (gobject_class, PROP_THUMBNAIL, "thumbnail");
+}
+
+static void
+grl_dpap_record_dpap_iface_init (gpointer iface, gpointer data)
+{
+  DPAPRecordIface *dpap_record = iface;
+
+  g_assert (G_TYPE_FROM_INTERFACE (dpap_record) == DPAP_TYPE_RECORD);
+
+  dpap_record->read = grl_dpap_record_read;
+}
+
+static void
+grl_dpap_record_dmap_iface_init (gpointer iface, gpointer data)
+{
+  DMAPRecordIface *dmap_record = iface;
+
+  g_assert (G_TYPE_FROM_INTERFACE (dmap_record) == DMAP_TYPE_RECORD);
+}
+
+
+G_DEFINE_TYPE_WITH_CODE (GrlDPAPRecord, grl_dpap_record, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (DPAP_TYPE_RECORD, grl_dpap_record_dpap_iface_init)
+                         G_IMPLEMENT_INTERFACE (DMAP_TYPE_RECORD, grl_dpap_record_dmap_iface_init))
+
+static void
+grl_dpap_record_finalize (GObject *object)
+{
+  GrlDPAPRecord *record = SIMPLE_DPAP_RECORD (object);
+
+  g_free (record->priv->location);
+  g_free (record->priv->filename);
+  g_free (record->priv->aspectratio);
+  g_free (record->priv->format);
+  g_free (record->priv->comments);
+
+  if (record->priv->thumbnail)
+    g_byte_array_unref (record->priv->thumbnail);
+
+  G_OBJECT_CLASS (grl_dpap_record_parent_class)->finalize (object);
+}
diff --git a/src/dmap/grl-dpap-record.h b/src/dmap/grl-dpap-record.h
new file mode 100644
index 0000000..4441740
--- /dev/null
+++ b/src/dmap/grl-dpap-record.h
@@ -0,0 +1,78 @@
+/*
+ *  Database record class for DPAP sharing
+ *
+ *  Copyright (C) 2008 W. Michael Petullo <mike flyn org>
+ *
+ * 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 __SIMPLE_DPAP_RECORD
+#define __SIMPLE_DPAP_RECORD
+
+#include <libdmapsharing/dmap.h>
+
+G_BEGIN_DECLS
+
+#define TYPE_SIMPLE_DPAP_RECORD                 \
+  (grl_dpap_record_get_type ())
+
+#define SIMPLE_DPAP_RECORD(o)                            \
+  (G_TYPE_CHECK_INSTANCE_CAST((o),                       \
+                              TYPE_SIMPLE_DPAP_RECORD,   \
+                              GrlDPAPRecord))
+
+#define SIMPLE_DPAP_RECORD_CLASS(k)                   \
+  (G_TYPE_CHECK_CLASS_CAST((k),                       \
+                           TYPE_SIMPLE_DPAP_RECORD,   \
+                           GrlDPAPRecordClass))
+
+#define IS_SIMPLE_DPAP_RECORD(o)                         \
+  (G_TYPE_CHECK_INSTANCE_TYPE((o),                       \
+                              TYPE_SIMPLE_DPAP_RECORD))
+
+#define IS_SIMPLE_DPAP_RECORD_CLASS(k)                      \
+  (G_TYPE_CHECK_CLASS_TYPE((k),                             \
+                           TYPE_SIMPLE_DPAP_RECORD_CLASS))
+
+#define SIMPLE_DPAP_RECORD_GET_CLASS(o)               \
+  (G_TYPE_INSTANCE_GET_CLASS((o),                     \
+                             TYPE_SIMPLE_DPAP_RECORD, \
+                             GrlDPAPRecordClass))
+
+#define SIMPLE_DPAP_RECORD_GET_PRIVATE(o)                \
+  (G_TYPE_INSTANCE_GET_PRIVATE((o),                      \
+                               TYPE_SIMPLE_DPAP_RECORD,  \
+                               GrlDPAPRecordPrivate))
+
+typedef struct GrlDPAPRecordPrivate GrlDPAPRecordPrivate;
+
+typedef struct {
+  GObject parent;
+  GrlDPAPRecordPrivate *priv;
+} GrlDPAPRecord;
+
+typedef struct {
+  GObjectClass parent;
+} GrlDPAPRecordClass;
+
+GType grl_dpap_record_get_type (void);
+
+GrlDPAPRecord    *grl_dpap_record_new    (void);
+GInputStream     *grl_dpap_record_read   (DPAPRecord *record, GError **error);
+gint              grl_dpap_record_get_id (DPAPRecord *record);
+
+#endif /* __SIMPLE_DPAP_RECORD */
+
+G_END_DECLS
diff --git a/src/dmap/grl-dpap.c b/src/dmap/grl-dpap.c
new file mode 100644
index 0000000..007e220
--- /dev/null
+++ b/src/dmap/grl-dpap.c
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2011 W. Michael Petullo.
+ * Copyright (C) 2012 Igalia S.L.
+ *
+ * Contact: W. Michael Petullo <mike flyn org>
+ *
+ * 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; 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
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <errno.h>
+#include <grilo.h>
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <libdmapsharing/dmap.h>
+
+#include "grl-common.h"
+#include "grl-dpap.h"
+#include "grl-dpap-db.h"
+#include "grl-dpap-record.h"
+#include "grl-dpap-record-factory.h"
+
+/* --------- Logging  -------- */
+
+#define GRL_LOG_DOMAIN_DEFAULT dmap_log_domain
+GRL_LOG_DOMAIN_STATIC(dmap_log_domain);
+
+/* --- Plugin information --- */
+
+#define PLUGIN_ID   DPAP_PLUGIN_ID
+
+#define SOURCE_ID_TEMPLATE   "grl-dpap-%s"
+#define SOURCE_DESC_TEMPLATE _("A source for browsing the DPAP server '%s'")
+
+/* --- Grilo DPAP Private --- */
+
+#define GRL_DPAP_SOURCE_GET_PRIVATE(object)           \
+  (G_TYPE_INSTANCE_GET_PRIVATE((object),              \
+                               GRL_DPAP_SOURCE_TYPE,  \
+                               GrlDpapSourcePrivate))
+
+struct _GrlDpapSourcePrivate {
+  DMAPMdnsBrowserService *service;
+};
+
+/* --- Data types --- */
+
+static GrlDpapSource *grl_dpap_source_new (DMAPMdnsBrowserService *service);
+
+static void grl_dpap_source_finalize (GObject *object);
+
+gboolean grl_dpap_plugin_init (GrlRegistry *registry,
+                               GrlPlugin *plugin,
+                               GList *configs);
+
+static const GList *grl_dpap_source_supported_keys (GrlSource *source);
+
+static void grl_dpap_source_browse (GrlSource *source,
+                                    GrlSourceBrowseSpec *bs);
+
+static void grl_dpap_source_search (GrlSource *source,
+                                    GrlSourceSearchSpec *ss);
+
+
+static void grl_dpap_service_added_cb (DMAPMdnsBrowser *browser,
+                                       DMAPMdnsBrowserService *service,
+                                       GrlPlugin *plugin);
+
+static void grl_dpap_service_removed_cb (DMAPMdnsBrowser *browser,
+                                         const gchar *service_name,
+                                         GrlPlugin *plugin);
+
+/* ===================== Globals  ======================= */
+static DMAPMdnsBrowser *browser;
+/* Maps URIs to DBs */
+static GHashTable *connections;
+/* Map DPAP services to Grilo media sources */
+static GHashTable *sources;
+
+/* =================== DPAP Plugin ====================== */
+
+gboolean
+grl_dpap_plugin_init (GrlRegistry *registry,
+                      GrlPlugin *plugin,
+                      GList *configs)
+{
+  GError *error = NULL;
+
+  GRL_LOG_DOMAIN_INIT (dmap_log_domain, "dmap");
+
+  GRL_DEBUG ("dmap_plugin_init");
+
+  /* Initialize i18n */
+  bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+  browser     = dmap_mdns_browser_new (DMAP_MDNS_BROWSER_SERVICE_TYPE_DPAP);
+  connections = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+  sources     = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+
+  g_signal_connect (G_OBJECT (browser),
+                    "service-added",
+                    G_CALLBACK (grl_dpap_service_added_cb),
+                    (gpointer) plugin);
+
+  g_signal_connect (G_OBJECT (browser),
+                    "service-removed",
+                    G_CALLBACK (grl_dpap_service_removed_cb),
+                    (gpointer) plugin);
+
+  if (!dmap_mdns_browser_start (browser, &error)) {
+    GRL_DEBUG ("error starting browser. code: %d message: %s",
+               error->code,
+               error->message);
+    g_error_free (error);
+
+    g_hash_table_unref (connections);
+    g_hash_table_unref (sources);
+    g_object_unref (browser);
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+GRL_PLUGIN_REGISTER (grl_dpap_plugin_init,
+                     NULL,
+                     PLUGIN_ID);
+
+/* ================== DMAP GObject ====================== */
+
+G_DEFINE_TYPE (GrlDpapSource,
+               grl_dpap_source,
+               GRL_TYPE_SOURCE);
+
+static GrlDpapSource *
+grl_dpap_source_new (DMAPMdnsBrowserService *service)
+{
+  gchar *source_desc;
+  gchar *source_id;
+
+  GrlDpapSource *source;
+
+  GRL_DEBUG ("grl_dpap_source_new");
+
+  source_desc = g_strdup_printf (SOURCE_DESC_TEMPLATE, service->name);
+  source_id = g_strdup_printf (SOURCE_ID_TEMPLATE, service->name);
+
+  source = g_object_new (GRL_DPAP_SOURCE_TYPE,
+                         "source-id",   source_id,
+                         "source-name", service->name,
+                         "source-desc", source_desc,
+                         NULL);
+
+  source->priv->service = service;
+
+  g_free (source_desc);
+  g_free (source_id);
+
+  return source;
+}
+
+static void
+grl_dpap_source_class_init (GrlDpapSourceClass * klass)
+{
+  GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass);
+
+  source_class->browse = grl_dpap_source_browse;
+  source_class->search = grl_dpap_source_search;
+  source_class->supported_keys = grl_dpap_source_supported_keys;
+
+  G_OBJECT_CLASS (source_class)->finalize = grl_dpap_source_finalize;
+
+  g_type_class_add_private (klass, sizeof (GrlDpapSourcePrivate));
+}
+
+static void
+grl_dpap_source_init (GrlDpapSource *source)
+{
+  source->priv = GRL_DPAP_SOURCE_GET_PRIVATE (source);
+}
+
+static void
+grl_dpap_source_finalize (GObject *object)
+{
+  G_OBJECT_CLASS (grl_dpap_source_parent_class)->finalize (object);
+}
+
+/* ======================= Utilities ==================== */
+
+static void
+grl_dpap_do_browse (ResultCbAndArgsAndDb *cb_and_db)
+{
+  grl_dpap_db_browse(GRL_DPAP_DB(cb_and_db->db),
+                     cb_and_db->cb.container,
+                     cb_and_db->cb.source,
+                     cb_and_db->cb.op_id,
+                     cb_and_db->cb.skip,
+                     cb_and_db->cb.count,
+                     cb_and_db->cb.callback,
+                     cb_and_db->cb.user_data);
+
+  g_free (cb_and_db);
+}
+
+static void
+grl_dpap_do_search (ResultCbAndArgsAndDb *cb_and_db)
+{
+  grl_dpap_db_search(GRL_DPAP_DB(cb_and_db->db),
+                     cb_and_db->cb.source,
+                     cb_and_db->cb.op_id,
+                     (GHRFunc) cb_and_db->cb.predicate,
+                     cb_and_db->cb.predicate_data,
+                     cb_and_db->cb.callback,
+                     cb_and_db->cb.user_data);
+
+  g_free (cb_and_db);
+}
+
+static void
+browse_connected_cb (DMAPConnection       *connection,
+                     gboolean              result,
+                     const char           *reason,
+                     ResultCbAndArgsAndDb *cb_and_db)
+{
+  GError *error;
+
+  /* NOTE: connection argument is required by API but ignored in this case. */
+  if (!result) {
+    error = g_error_new_literal (GRL_CORE_ERROR,
+                                 GRL_CORE_ERROR_BROWSE_FAILED,
+                                 reason);
+    cb_and_db->cb.callback (cb_and_db->cb.source,
+                            cb_and_db->cb.op_id,
+                            NULL,
+                            0,
+                            cb_and_db->cb.user_data,
+                            error);
+    g_error_free (error);
+  } else {
+    grl_dpap_do_browse (cb_and_db);
+  }
+}
+
+static void
+search_connected_cb (DMAPConnection       *connection,
+                     gboolean              result,
+                     const char           *reason,
+                     ResultCbAndArgsAndDb *cb_and_db)
+{
+  GError *error;
+
+  /* NOTE: connection argument is required by API but ignored in this case. */
+  if (!result) {
+    error = g_error_new_literal (GRL_CORE_ERROR,
+                                 GRL_CORE_ERROR_BROWSE_FAILED,
+                                 reason);
+    cb_and_db->cb.callback (cb_and_db->cb.source,
+                            cb_and_db->cb.op_id,
+                            NULL,
+                            0,
+                            cb_and_db->cb.user_data,
+                            error);
+    g_error_free (error);
+  } else {
+    grl_dpap_do_search (cb_and_db);
+  }
+}
+
+static void
+grl_dpap_service_added_cb (DMAPMdnsBrowser *browser,
+                           DMAPMdnsBrowserService *service,
+                           GrlPlugin *plugin)
+{
+  GrlRegistry   *registry = grl_registry_get_default ();
+  GrlDpapSource *source   = grl_dpap_source_new (service);
+
+  GRL_DEBUG (__FUNCTION__);
+
+  g_object_add_weak_pointer (G_OBJECT (source), (gpointer *) &source);
+  grl_registry_register_source (registry,
+                                plugin,
+                                GRL_SOURCE (source),
+                                NULL);
+  if (source != NULL) {
+    g_hash_table_insert (sources, g_strdup (service->name), g_object_ref (source));
+    g_object_remove_weak_pointer (G_OBJECT (source), (gpointer *) &source);
+  }
+}
+
+static void
+grl_dpap_service_removed_cb (DMAPMdnsBrowser *browser,
+                             const gchar *service_name,
+                             GrlPlugin *plugin)
+{
+  GrlRegistry   *registry = grl_registry_get_default ();
+  GrlDpapSource *source   = g_hash_table_lookup (sources, service_name);
+
+  GRL_DEBUG (__FUNCTION__);
+
+  if (source) {
+    grl_registry_unregister_source (registry, GRL_SOURCE (source), NULL);
+    g_hash_table_remove (sources, service_name);
+  }
+}
+
+static void
+grl_dpap_connect (gchar *name, gchar *host, guint port, ResultCbAndArgsAndDb *cb_and_db, 
DMAPConnectionCallback callback)
+{
+  DMAPRecordFactory *factory;
+  DMAPConnection *connection;
+
+  factory = DMAP_RECORD_FACTORY (grl_dpap_record_factory_new ());
+  connection = DMAP_CONNECTION (dpap_connection_new (name, host, port, DMAP_DB (cb_and_db->db), factory));
+  dmap_connection_connect (connection, (DMAPConnectionCallback) callback, cb_and_db);
+}
+
+static gboolean
+grl_dpap_match (GrlMedia *media, gpointer val, gpointer user_data)
+{
+  g_assert (GRL_IS_MEDIA_IMAGE (media));
+
+  if (user_data == NULL)
+    return TRUE;
+
+  const char *title = grl_media_get_title (media);
+  return strstr (title, user_data) != NULL;
+}
+
+/* ================== API Implementation ================ */
+
+static const GList *
+grl_dpap_source_supported_keys (GrlSource *source)
+{
+  static GList *keys = NULL;
+
+  GRL_DEBUG (__func__);
+
+  if (!keys)
+    keys = grl_metadata_key_list_new (GRL_METADATA_KEY_HEIGHT,
+                                      GRL_METADATA_KEY_THUMBNAIL,
+                                      GRL_METADATA_KEY_WIDTH,
+                                      GRL_METADATA_KEY_ID,
+                                      GRL_METADATA_KEY_URL,
+                                      NULL);
+
+  return keys;
+}
+
+static void
+grl_dpap_source_browse (GrlSource *source,
+                        GrlSourceBrowseSpec *bs)
+{
+  GrlDpapSource *dmap_source = GRL_DPAP_SOURCE (source);
+  gchar *url = grl_dmap_build_url (dmap_source->priv->service);
+
+  GRL_DEBUG (__func__);
+
+  ResultCbAndArgsAndDb *cb_and_db;
+
+  cb_and_db = g_new (ResultCbAndArgsAndDb, 1);
+
+  cb_and_db->cb.callback       = bs->callback;
+  cb_and_db->cb.source         = bs->source;
+  cb_and_db->cb.container      = bs->container;
+  cb_and_db->cb.op_id          = bs->operation_id;
+  cb_and_db->cb.skip           = grl_operation_options_get_skip (bs->options);
+  cb_and_db->cb.count          = grl_operation_options_get_count (bs->options);
+  cb_and_db->cb.user_data      = bs->user_data;
+
+  if ((cb_and_db->db = g_hash_table_lookup (connections, url))) {
+    /* Just call directly; already connected, already populated database. */
+    browse_connected_cb (NULL, TRUE, NULL, cb_and_db);
+  } else {
+    /* Connect */
+    cb_and_db->db = DMAP_DB (grl_dpap_db_new ());
+
+    grl_dpap_connect (dmap_source->priv->service->name,
+                      dmap_source->priv->service->host,
+                      dmap_source->priv->service->port,
+                      cb_and_db,
+                      (DMAPConnectionCallback) browse_connected_cb);
+
+    g_hash_table_insert (connections, g_strdup (url), cb_and_db->db);
+  }
+
+  g_free (url);
+}
+
+static void grl_dpap_source_search (GrlSource *source,
+                                    GrlSourceSearchSpec *ss)
+{
+  GrlDpapSource *dmap_source = GRL_DPAP_SOURCE (source);
+
+  ResultCbAndArgsAndDb *cb_and_db;
+  DMAPMdnsBrowserService *service = dmap_source->priv->service;
+  gchar *url = grl_dmap_build_url (service);
+
+  cb_and_db = g_new (ResultCbAndArgsAndDb, 1);
+
+  cb_and_db->cb.callback       = ss->callback;
+  cb_and_db->cb.source         = ss->source;
+  cb_and_db->cb.container      = NULL;
+  cb_and_db->cb.op_id          = ss->operation_id;
+  cb_and_db->cb.predicate      = (GHRFunc) grl_dpap_match;
+  cb_and_db->cb.predicate_data = ss->text;
+  cb_and_db->cb.user_data      = ss->user_data;
+
+  if ((cb_and_db->db = g_hash_table_lookup (connections, url))) {
+    /* Just call directly; already connected, already populated database */
+    search_connected_cb (NULL, TRUE, NULL, cb_and_db);
+  } else {
+    /* Connect */
+    cb_and_db->db = DMAP_DB (grl_dpap_db_new ());
+    grl_dpap_connect (service->name, service->host, service->port, cb_and_db, (DMAPConnectionCallback) 
search_connected_cb);
+    g_hash_table_insert (connections, g_strdup (url), cb_and_db->db);
+  }
+
+  g_free (url);
+}
diff --git a/src/dmap/grl-dpap.h b/src/dmap/grl-dpap.h
new file mode 100644
index 0000000..1f36cfc
--- /dev/null
+++ b/src/dmap/grl-dpap.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2011 W. Michael Petullo
+ * Copyright (C) 2012 Igalia S.L.
+ *
+ * Contact: W. Michael Petullo <mike flyn org>
+ *
+ * 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; 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 _GRL_DPAP_SOURCE_H_
+#define _GRL_DPAP_SOURCE_H_
+
+#include <grilo.h>
+
+#define GRL_DPAP_SOURCE_TYPE                    \
+  (grl_dpap_source_get_type ())
+
+#define GRL_DPAP_SOURCE(obj)                          \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj),                 \
+                               GRL_DPAP_SOURCE_TYPE,  \
+                               GrlDpapSource))
+
+#define GRL_IS_DPAP_SOURCE(obj)                       \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj),                 \
+                               GRL_DPAP_SOURCE_TYPE))
+
+#define GRL_DPAP_SOURCE_CLASS(klass)               \
+  (G_TYPE_CHECK_CLASS_CAST((klass),                \
+                           GRL_DPAP_SOURCE_TYPE,   \
+                           GrlDpapSourceClass))
+
+#define GRL_IS_DPAP_SOURCE_CLASS(klass)            \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),                \
+                           GRL_DPAP_SOURCE_TYPE))
+
+#define GRL_DPAP_SOURCE_GET_CLASS(obj)                \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj),                  \
+                              GRL_DPAP_SOURCE_TYPE,   \
+                              GrlDpapSourceClass))
+
+typedef struct _GrlDpapSourcePrivate GrlDpapSourcePrivate;
+typedef struct _GrlDpapSource  GrlDpapSource;
+
+struct _GrlDpapSource {
+  GrlSource parent;
+
+  /*< private >*/
+  GrlDpapSourcePrivate *priv;
+};
+
+typedef struct _GrlDpapSourceClass GrlDpapSourceClass;
+
+struct _GrlDpapSourceClass {
+  GrlSourceClass parent_class;
+};
+
+GType grl_dpap_source_get_type (void);
+
+#endif /* _GRL_DPAP_SOURCE_H_ */
diff --git a/src/dmap/grl-dpap.xml b/src/dmap/grl-dpap.xml
new file mode 100644
index 0000000..2e38ae4
--- /dev/null
+++ b/src/dmap/grl-dpap.xml
@@ -0,0 +1,10 @@
+<plugin>
+  <info>
+    <name>DPAP</name>
+    <module>libgrldpap</module>
+    <description>A plugin for browsing DPAP servers</description>
+    <author>W. Michael Petullo</author>
+    <license>LGPL</license>
+    <site>http://www.flyn.org</site>
+  </info>
+</plugin>


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