Re: Future direction of DMAP plugin



Now that I am writing my own application, it seems like it might be
more convenient/consistent if the DMAP plugin imposed some
(artificial,
with respect to the DMAP protocol itself) structure on the media it
finds. That is, I like the way the Jamendo plugin presents Artists,
Albums, Feeds; then a list of each; and then the songs themselves.

Would it make sense if I modified the DMAP plugin to behave more like
Jamendo?
 
For me, it would make sense.

Also, you can still allow the flat list through the search: doing a
search of a NULL text means "search all the content". If a source
implements it, it just return the list of all media.

Please see below for an initial patch. With this patch, the DMAP plugin
will present: 

        (Albums, Artists) -> 
        (Album_1, Album_2, ..., Album_n) or (Artist_1, ...) -> 
        (Track_1, Track_2, ..., Track_n)

I have found this much more convenient than a flat listing from the
point of view of the application programmer.

As I mentioned earlier, I am working on an application, so you can expect
continued bugfixes for this plugin.

From c2b1db5c76ce860a3802a56c763ad13da9eb29ff Mon Sep 17 00:00:00 2001
From: "W. Michael Petullo" <mike flyn org>
Date: Sun, 2 Feb 2014 22:00:02 -0500
Subject: [PATCH] dmap: provide a hierarchical view of media database

Signed-off-by: W. Michael Petullo <mike flyn org>
---
 src/dmap/grl-dmap.c       | 191 ++++++++------------------
 src/dmap/simple-dmap-db.c | 333 ++++++++++++++++++++++++++++++++++++++++------
 src/dmap/simple-dmap-db.h |  24 +++-
 3 files changed, 368 insertions(+), 180 deletions(-)

diff --git a/src/dmap/grl-dmap.c b/src/dmap/grl-dmap.c
index b31c235..bcc8b50 100644
--- a/src/dmap/grl-dmap.c
+++ b/src/dmap/grl-dmap.c
@@ -66,13 +66,12 @@ struct _GrlDmapSourcePrivate {
 typedef struct _ResultCbAndArgs {
   GrlSourceResultCb callback;
   GrlSource *source;
+  GrlMedia *container;
   guint op_id;
-  gint code_error;
   GHRFunc predicate;
   gchar *predicate_data;
   guint skip;
   guint count;
-  guint remaining;
   gpointer user_data;
 } ResultCbAndArgs;
 
@@ -129,7 +128,7 @@ grl_dmap_plugin_init (GrlRegistry *registry,
   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
 
   browser     = dmap_mdns_browser_new (DMAP_MDNS_BROWSER_SERVICE_TYPE_DAAP);
-  connections = g_hash_table_new (g_str_hash, g_str_equal);
+  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),
@@ -232,140 +231,71 @@ build_url (DMAPMdnsBrowserService *service)
 }
 
 static void
-add_media_from_service (gpointer id,
-                        DAAPRecord *record,
-                        ResultCbAndArgs *cb)
+do_browse (ResultCbAndArgsAndDb *cb_and_db)
 {
-  gint   duration = 0;
-  gint32  bitrate = 0,
-            track = 0;
-  gchar  *id_s    = NULL,
-         *title   = NULL,
-         *album   = NULL,
-         *artist  = NULL,
-         *genre   = NULL,
-         *url     = NULL;
-  gboolean has_video;
-  GrlMedia *media;
-
-  g_object_get (record,
-               "songalbum",
-               &album,
-               "songartist",
-               &artist,
-               "bitrate",
-               &bitrate,
-               "duration",
-               &duration,
-               "songgenre",
-               &genre,
-               "title",
-               &title,
-               "track",
-               &track,
-               "location",
-               &url,
-               "has-video",
-               &has_video,
-                NULL);
-
-  id_s = g_strdup_printf ("%u", GPOINTER_TO_UINT (id));
-
-  if (has_video == TRUE) {
-    media = grl_media_video_new ();
-  } else {
-    media = grl_media_audio_new ();
-  }
-
-  grl_media_set_id           (media, id_s);
-  grl_media_set_duration     (media, duration);
-
-  if (title) {
-    grl_media_set_title (media, title);
-  }
-
-  if (url) {
-    // Replace URL's daap:// with http://.
-    url[0] = 'h'; url[1] = 't'; url[2] = 't'; url[3] = 'p';
-    grl_media_set_url (media, url);
-  }
-
-  if (has_video == FALSE) {
-    GrlMediaAudio *media_audio = GRL_MEDIA_AUDIO (media);
-
-    grl_media_audio_set_bitrate      (media_audio, bitrate);
-    grl_media_audio_set_track_number (media_audio, track);
+  simple_dmap_db_browse (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);
 
-    if (album) {
-      grl_media_audio_set_album (media_audio, album);
-    }
-
-    if (artist) {
-      grl_media_audio_set_artist (media_audio, artist);
-    }
-
-    if (genre) {
-      grl_media_audio_set_genre (media_audio, genre);
-    }
-  }
-
-  g_free (id_s);
-
-  cb->callback (cb->source,
-                cb->op_id,
-                media,
-                --cb->remaining,
-                cb->user_data,
-                NULL);
+  g_free (cb_and_db);
 }
 
 static void
-add_to_hash_table (gpointer key, gpointer value, GHashTable *hash_table)
+do_search (ResultCbAndArgsAndDb *cb_and_db)
 {
-  g_hash_table_insert (hash_table, key, value);
+  simple_dmap_db_search (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
-add_filtered_media_from_service (ResultCbAndArgsAndDb *cb_and_db)
+browse_connected_cb (DMAPConnection       *connection,
+                     gboolean              result,
+                     const char           *reason,
+                     ResultCbAndArgsAndDb *cb_and_db)
 {
-  GHashTable *hash_table;
-  hash_table = g_hash_table_new (g_direct_hash, g_direct_equal);
-
-  simple_dmap_db_filtered_foreach (cb_and_db->db,
-                                   cb_and_db->cb.skip,
-                                   cb_and_db->cb.count,
-                                   (GHRFunc) cb_and_db->cb.predicate,
-                                   cb_and_db->cb.predicate_data,
-                                   (GHFunc) add_to_hash_table,
-                                   hash_table);
-
-  cb_and_db->cb.remaining = g_hash_table_size (hash_table);
-  if (cb_and_db->cb.remaining > 0) {
-    g_hash_table_foreach (hash_table, (GHFunc) add_media_from_service, &cb_and_db->cb);
-  } else {
+  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,
-                            NULL);
+                            error);
+    g_error_free (error);
+  } else {
+    do_browse (cb_and_db);
   }
-  g_hash_table_destroy (hash_table);
-  g_free (cb_and_db);
 }
 
 static void
-connected_cb (DMAPConnection       *connection,
-              gboolean              result,
-              const char           *reason,
-              ResultCbAndArgsAndDb *cb_and_db)
+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,
-                                 cb_and_db->cb.code_error,
+                                 GRL_CORE_ERROR_BROWSE_FAILED,
                                  reason);
     cb_and_db->cb.callback (cb_and_db->cb.source,
                             cb_and_db->cb.op_id,
@@ -375,7 +305,7 @@ connected_cb (DMAPConnection       *connection,
                             error);
     g_error_free (error);
   } else {
-    add_filtered_media_from_service (cb_and_db);
+    do_search (cb_and_db);
   }
 }
 
@@ -428,16 +358,15 @@ grl_dmap_connect (gchar *name, gchar *host, guint port, ResultCbAndArgsAndDb *cb
 }
 
 static gboolean
-always_true (gpointer key, gpointer value, gpointer user_data)
+match (GrlMedia *media, gpointer val, gpointer user_data)
 {
-  return TRUE;
-}
+  g_assert (GRL_IS_MEDIA_AUDIO (media) || GRL_IS_MEDIA_VIDEO (media));
 
-static gboolean
-match (gpointer key, DAAPRecord *record, gpointer user_data)
-{
-  char *title;
-  g_object_get (record, "title", &title, NULL);
+  if (NULL == user_data) {
+    return TRUE;
+  }
+
+  const char *title = grl_media_get_title (media);
   return strstr (title, user_data) != NULL;
 }
 
@@ -480,17 +409,15 @@ grl_dmap_source_browse (GrlSource *source,
 
   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.code_error     = GRL_CORE_ERROR_BROWSE_FAILED;
-  cb_and_db->cb.predicate      = always_true;
-  cb_and_db->cb.predicate_data = NULL;
   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.
-    connected_cb (NULL, TRUE, NULL, cb_and_db);
+    browse_connected_cb (NULL, TRUE, NULL, cb_and_db);
   } else {
     // Connect.
     cb_and_db->db = simple_dmap_db_new ();
@@ -499,9 +426,9 @@ grl_dmap_source_browse (GrlSource *source,
                       dmap_source->priv->service->host,
                       dmap_source->priv->service->port,
                       cb_and_db,
-                      (DMAPConnectionCallback) connected_cb);
+                      (DMAPConnectionCallback) browse_connected_cb);
 
-    g_hash_table_insert (connections, (gpointer) url, cb_and_db->db);
+    g_hash_table_insert (connections, g_strdup (url), cb_and_db->db);
   }
 
   g_free (url);
@@ -520,22 +447,20 @@ static void grl_dmap_source_search (GrlSource *source,
 
   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.code_error     = GRL_CORE_ERROR_SEARCH_FAILED;
   cb_and_db->cb.predicate      = (GHRFunc) match;
   cb_and_db->cb.predicate_data = ss->text;
-  cb_and_db->cb.skip           = grl_operation_options_get_skip (ss->options);
-  cb_and_db->cb.count          = grl_operation_options_get_count (ss->options);
   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.
-    connected_cb (NULL, TRUE, NULL, cb_and_db);
+    search_connected_cb (NULL, TRUE, NULL, cb_and_db);
   } else {
     // Connect.
     cb_and_db->db = simple_dmap_db_new ();
-    grl_dmap_connect (service->name, service->host, service->port, cb_and_db, (DMAPConnectionCallback) 
connected_cb);
-    g_hash_table_insert (connections, url, cb_and_db->db);
+    grl_dmap_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/simple-dmap-db.c b/src/dmap/simple-dmap-db.c
index f49ee6a..63b5f4a 100644
--- a/src/dmap/simple-dmap-db.c
+++ b/src/dmap/simple-dmap-db.c
@@ -15,23 +15,64 @@
  *
  * 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
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* This DMAP database implementation maintains a series of hash tables that
+ * represent sets of media. The tables include: root, albums, and artists.
+ * Root contains albums and artists, and albums and artists each contain
+ * the set of albums and artists in the database, respectively. Thus this
+ * database implementation imposes a hierarchical structure, whereas DMAP
+ * normally provides a flat structure.
+ *
+ * Each hash table/set is a mapping between a GrlMediaBox and a series of
+ * GrlMedia objects (either more GrlMediaBox objects, or, in the case of a 
+ * leaf, GrlMediaAudio objects). The constant GrlMediaBox objects (e.g.,
+ * albums_box and artists_box) facilitate this, along with additional
+ * GrlMediaAudio objects that the simple_dmap_db_add function creates.
+ *
+ * An application will normally first browse using the NULL container,
+ * and thus will first receive albums_box and artists_box. Browsing
+ * albums_box will provide the application the GrlMediaBox objects in
+ * albums. Further browsing one of these objects will provide the 
+ * application with the songs contained therein.
+ *
+ * Grilo IDs must be unique. Here the convention is:
  *
+ *     1. Top-level IDs resemble their name (e.g., Album's ID is "albums").
+ *     2. The ID for albums, artists, etc. is the item name prefixed by 
+ *        the category name (e.g., albums-NAME-OF-ALBUM).
+ *     3. The ID for songs is the string form of the integer identifier used
+ *        to identify the song to libdmapsharing.
  */
 
-#include <string.h>
+#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 "simple-dmap-db.h"
 
 /* Media ID's start at max and go down. Container ID's start at 1 and go up. */
 static guint nextid = G_MAXINT; /* NOTE: this should be G_MAXUINT, but iPhoto can't handle it. */
 
+static const gchar *ALBUMS_ID    =  "albums";
+static const gchar *ALBUMS_NAME  =  "Albums";
+static const gchar *ARTISTS_ID   = "artists";
+static const gchar *ARTISTS_NAME = "Artists";
+
 struct SimpleDMAPDbPrivate {
-  GHashTable *db;
+  GrlMediaBox *albums_box;  // Contains each album box (tracked with albums hash table).
+  GrlMediaBox *artists_box; // Contains each artist box (tracked with artist hash table).
+
+  GHashTable  *root;
+  GHashTable  *albums;
+  GHashTable  *artists;
 };
 
 enum {
@@ -39,21 +80,31 @@ enum {
   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)));
+}
+
 SimpleDMAPDb *
 simple_dmap_db_new (void)
 {
-  return SIMPLE_DMAP_DB (g_object_new (TYPE_SIMPLE_DMAP_DB, NULL));
+  SimpleDMAPDb *db = g_object_new (TYPE_SIMPLE_DMAP_DB, NULL);
+
+  return db;
 }
 
 static DMAPRecord *
 simple_dmap_db_lookup_by_id (const DMAPDb *db, guint id)
 {
-  DMAPRecord *record;
-
-  record = g_hash_table_lookup (SIMPLE_DMAP_DB (db)->priv->db, GUINT_TO_POINTER (id));
-  g_object_ref (record);
-
-  return record;
+  g_error ("Not implemented");
+  return NULL;
 }
 
 static void
@@ -61,22 +112,125 @@ simple_dmap_db_foreach (const DMAPDb *db,
                         GHFunc func,
                         gpointer data)
 {
-  g_hash_table_foreach (SIMPLE_DMAP_DB (db)->priv->db, func, data);
+  g_error ("Not implemented");
 }
 
 static gint64
 simple_dmap_db_count (const DMAPDb *db)
 {
-  return g_hash_table_size (SIMPLE_DMAP_DB (db)->priv->db);
+  g_error ("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 (NULL == set) {
+    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
-simple_dmap_db_add (DMAPDb *db, DMAPRecord *record)
+simple_dmap_db_add (DMAPDb *_db, DMAPRecord *record)
 {
-  g_object_ref (record);
-  g_hash_table_insert (SIMPLE_DMAP_DB (db)->priv->db, GUINT_TO_POINTER (nextid--), record);
+  SimpleDMAPDb *db = SIMPLE_DMAP_DB (_db);
+  gint   duration = 0;
+  gint32  bitrate = 0,
+            track = 0;
+  gchar  *id_s    = NULL,
+         *title   = NULL,
+         *album   = NULL,
+         *artist  = NULL,
+         *genre   = NULL,
+         *url     = NULL;
+  gboolean has_video;
+  GrlMedia *media;
+
+  g_object_get (record,
+               "songalbum",
+               &album,
+               "songartist",
+               &artist,
+               "bitrate",
+               &bitrate,
+               "duration",
+               &duration,
+               "songgenre",
+               &genre,
+               "title",
+               &title,
+               "track",
+               &track,
+               "location",
+               &url,
+               "has-video",
+               &has_video,
+                NULL);
+
+  id_s = g_strdup_printf ("%u", nextid);
+
+  if (has_video == TRUE) {
+    media = grl_media_video_new ();
+  } else {
+    media = grl_media_audio_new ();
+  }
+
+  grl_media_set_id           (media, id_s);
+  grl_media_set_duration     (media, duration);
+
+  if (title) {
+    grl_media_set_title (media, title);
+  }
+
+  if (url) {
+    // Replace URL's daap:// with http://.
+    url[0] = 'h'; url[1] = 't'; url[2] = 't'; url[3] = 'p';
+    grl_media_set_url (media, url);
+  }
+
+  if (has_video == FALSE) {
+    GrlMediaAudio *media_audio = GRL_MEDIA_AUDIO (media);
 
-  return nextid;
+    grl_media_audio_set_bitrate      (media_audio, bitrate);
+    grl_media_audio_set_track_number (media_audio, track);
+
+    if (album) {
+      grl_media_audio_set_album (media_audio, album);
+    }
+
+    if (artist) {
+      grl_media_audio_set_artist (media_audio, artist);
+    }
+
+    if (genre) {
+      grl_media_audio_set_genre (media_audio, genre);
+    }
+  }
+
+  set_insert (db->priv->artists, ARTISTS_ID, artist, media);
+  set_insert (db->priv->albums,  ALBUMS_ID,  album,  media);
+
+  g_free (id_s);
+  g_object_unref (media);
+
+  return --nextid;
 }
 
 static void
@@ -92,31 +246,113 @@ simple_dmap_db_interface_init (gpointer iface, gpointer data)
   dmap_db->count = simple_dmap_db_count;
 }
 
+static gboolean
+same_media (GrlMedia *a, GrlMedia *b)
+{
+  return ! strcmp (grl_media_get_id (a), grl_media_get_id (b));
+}
+
 void
-simple_dmap_db_filtered_foreach (SimpleDMAPDb *db,
-                                 guint skip,
-                                 guint count,
-                                 GHRFunc predicate,
-                                 gpointer pred_user_data,
-                                 GHFunc func,
-                                 gpointer user_data)
+simple_dmap_db_browse (SimpleDMAPDb *db,
+                       GrlMedia *container,
+                       GrlSource *source,
+                       guint op_id,
+                       guint skip,
+                       guint count,
+                       GrlSourceResultCb func,
+                       gpointer user_data)
 {
+  int i;
+  guint remaining;
+  GHashTable *hash_table;
   GHashTableIter iter;
   gpointer key, val;
-  guint i;
 
-  g_hash_table_iter_init (&iter, db->priv->db);
-  for (i = 0; g_hash_table_iter_next (&iter, &key, &val); i++) {
+  const gchar *box_id = grl_media_get_id (container);
+  if (NULL == box_id) {
+    hash_table = db->priv->root; 
+  } else if (same_media (container, GRL_MEDIA (db->priv->albums_box))) {
+    hash_table = db->priv->albums; 
+  } else if (same_media (container, GRL_MEDIA (db->priv->artists_box))) {
+    hash_table = db->priv->artists; 
+  } else {
+    hash_table = g_hash_table_lookup (db->priv->artists, container);
+    if (NULL == hash_table) {
+      hash_table = g_hash_table_lookup (db->priv->albums, container);
+    }
+  }
+
+  // Should not be NULL; this means the container requested
+  // does not exist in the database.
+  if (NULL == hash_table) {
+    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 (i == skip + count) {
-      break;
+    if (GRL_IS_MEDIA_BOX (key)) {
+      grl_media_box_set_childcount (GRL_MEDIA_BOX (key), g_hash_table_size (val)); 
     }
-    if (predicate (key, val, pred_user_data)) {
-      func (key, val, user_data);
+    func (source, op_id, GRL_MEDIA (g_object_ref (key)), --remaining, user_data, NULL);
+  }
+done:
+  return;
+}
+
+void
+simple_dmap_db_search (SimpleDMAPDb *db,
+                       GrlSource *source,
+                       guint op_id,
+                       GHRFunc predicate,
+                       gpointer pred_user_data,
+                       GrlSourceResultCb func,
+                       gpointer user_data)
+{
+  gint i, j, k;
+  guint remaining = 0;
+  gpointer key1, val1, key2, val2;
+  GHashTable *hash_table[] = { db->priv->albums, db->priv->artists };
+  GHashTable *results = NULL; // Use hash table to avoid duplicates.
+  GHashTableIter iter1, iter2;
+
+  results = g_hash_table_new (g_str_hash, g_str_equal);
+
+  // For albums and artists...
+  for (i = 0; i < 2; i++) {
+    g_hash_table_iter_init (&iter1, hash_table[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);
+  }
 }
 
 G_DEFINE_TYPE_WITH_CODE (SimpleDMAPDb, simple_dmap_db, G_TYPE_OBJECT,
@@ -132,13 +368,26 @@ simple_dmap_db_constructor (GType type, guint n_construct_params, GObjectConstru
   return object;
 }
 
-static void simple_dmap_db_init (SimpleDMAPDb *db)
+static void
+simple_dmap_db_init (SimpleDMAPDb *db)
 {
   db->priv = SIMPLE_DMAP_DB_GET_PRIVATE (db);
-  db->priv->db = g_hash_table_new_full (g_direct_hash,
-                                        g_direct_equal,
-                                        NULL,
-                                        g_object_unref);
+
+  db->priv->albums_box  = g_object_new (GRL_TYPE_MEDIA_BOX, NULL);
+  db->priv->artists_box = g_object_new (GRL_TYPE_MEDIA_BOX, NULL);
+
+  grl_media_set_id    (GRL_MEDIA (db->priv->albums_box), ALBUMS_ID);
+  grl_media_set_title (GRL_MEDIA (db->priv->albums_box), _(ALBUMS_NAME));
+
+  grl_media_set_id    (GRL_MEDIA (db->priv->artists_box), ARTISTS_ID);
+  grl_media_set_title (GRL_MEDIA (db->priv->artists_box), _(ARTISTS_NAME));
+
+  db->priv->root    = g_hash_table_new_full (box_hash, box_equal, g_object_unref, (GDestroyNotify) 
g_hash_table_destroy);
+  db->priv->albums  = g_hash_table_new_full (box_hash, box_equal, g_object_unref, (GDestroyNotify) 
g_hash_table_destroy);
+  db->priv->artists = 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->albums_box),  db->priv->albums);
+  g_hash_table_insert (db->priv->root, g_object_ref (db->priv->artists_box), db->priv->artists);
 }
 
 static void
@@ -146,10 +395,13 @@ simple_dmap_db_finalize (GObject *object)
 {
   SimpleDMAPDb *db = SIMPLE_DMAP_DB (object);
 
-  g_debug ("Finalizing SimpleDMAPDb (%d records)",
-           g_hash_table_size (db->priv->db));
+  g_debug ("Finalizing SimpleDMAPDb");
 
-  g_hash_table_destroy (db->priv->db);
+  g_object_unref (db->priv->albums_box);
+  g_object_unref (db->priv->artists_box);
+
+  g_hash_table_destroy (db->priv->albums);
+  g_hash_table_destroy (db->priv->artists);
 }
 
 static void
@@ -179,7 +431,8 @@ simple_dmap_db_get_property (GObject *object,
 }
 
 
-static void simple_dmap_db_class_init (SimpleDMAPDbClass *klass)
+static void
+simple_dmap_db_class_init (SimpleDMAPDbClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
diff --git a/src/dmap/simple-dmap-db.h b/src/dmap/simple-dmap-db.h
index f0e5ae5..fd890ae 100644
--- a/src/dmap/simple-dmap-db.h
+++ b/src/dmap/simple-dmap-db.h
@@ -22,6 +22,7 @@
 #define __SIMPLE_DMAP_DB
 
 #include <libdmapsharing/dmap.h>
+#include <grilo.h>
 
 G_BEGIN_DECLS
 
@@ -65,13 +66,22 @@ typedef struct {
   GObjectClass parent;
 } SimpleDMAPDbClass;
 
-void simple_dmap_db_filtered_foreach (SimpleDMAPDb *db,
-                                      guint skip,
-                                      guint count,
-                                      GHRFunc predicate,
-                                      gpointer pred_user_data,
-                                      GHFunc func,
-                                      gpointer user_data);
+void simple_dmap_db_browse (SimpleDMAPDb *db,
+                            GrlMedia *container,
+                            GrlSource *source,
+                            guint op_id,
+                            guint skip,
+                            guint count,
+                            GrlSourceResultCb func,
+                            gpointer user_data);
+
+void simple_dmap_db_search (SimpleDMAPDb *db,
+                            GrlSource *source,
+                            guint op_id,
+                            GHRFunc predicate,
+                            gpointer pred_user_data,
+                            GrlSourceResultCb func,
+                            gpointer user_data);
 
 SimpleDMAPDb *simple_dmap_db_new (void);
 
-- 
1.8.5.3


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