diff options
Diffstat (limited to 'src/dmap/grl-daap-db.c')
-rw-r--r-- | src/dmap/grl-daap-db.c | 450 |
1 files changed, 450 insertions, 0 deletions
diff --git a/src/dmap/grl-daap-db.c b/src/dmap/grl-daap-db.c new file mode 100644 index 0000000..5ddd40c --- /dev/null +++ b/src/dmap/grl-daap-db.c @@ -0,0 +1,450 @@ +/* + * 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 + */ + +/* This DAAP 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 DAAP + * 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 grl_daap_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. + */ + +#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-daap-db.h" + +#define ALBUMS_ID "albums" +#define ALBUMS_NAME _("Albums") +#define ARTISTS_ID "artists" +#define ARTISTS_NAME _("Artists") + +/* 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. */ + +struct GrlDAAPDbPrivate { + /* Contains each album box (tracked with albums hash table) */ + GrlMediaBox *albums_box; + + /* Contains each artist box (tracked with artist hash table) */ + GrlMediaBox *artists_box; + + GHashTable *root; + GHashTable *albums; + GHashTable *artists; +}; + +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))); +} + +GrlDAAPDb * +grl_daap_db_new (void) +{ + GrlDAAPDb *db = g_object_new (TYPE_GRL_DAAP_DB, NULL); + + return db; +} + +static DMAPRecord * +grl_daap_db_lookup_by_id (const DMAPDb *db, guint id) +{ + g_error ("Not implemented"); + return NULL; +} + +static void +grl_daap_db_foreach (const DMAPDb *db, + GHFunc func, + gpointer data) +{ + g_error ("Not implemented"); +} + +static gint64 +grl_daap_db_count (const DMAPDb *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 +grl_daap_db_add (DMAPDb *_db, DMAPRecord *record) +{ + GrlDAAPDb *db = GRL_DAAP_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); + + 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 +grl_daap_db_interface_init (gpointer iface, gpointer data) +{ + DMAPDbIface *daap_db = iface; + + g_assert (G_TYPE_FROM_INTERFACE (daap_db) == DMAP_TYPE_DB); + + daap_db->add = grl_daap_db_add; + daap_db->lookup_by_id = grl_daap_db_lookup_by_id; + daap_db->foreach = grl_daap_db_foreach; + daap_db->count = grl_daap_db_count; +} + +static gboolean +same_media (GrlMedia *a, GrlMedia *b) +{ + return ! strcmp (grl_media_get_id (a), grl_media_get_id (b)); +} + +void +grl_daap_db_browse (GrlDAAPDb *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; + + 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 (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_daap_db_search (GrlDAAPDb *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 }; + + /* Use hash table to avoid duplicates */ + GHashTable *results = NULL; + 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 (GrlDAAPDb, grl_daap_db, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (DMAP_TYPE_DB, grl_daap_db_interface_init)) + +static GObject* +grl_daap_db_constructor (GType type, guint n_construct_params, GObjectConstructParam *construct_params) +{ + GObject *object; + + object = G_OBJECT_CLASS (grl_daap_db_parent_class)->constructor (type, n_construct_params, construct_params); + + return object; +} + +static void +grl_daap_db_init (GrlDAAPDb *db) +{ + db->priv = GRL_DAAP_DB_GET_PRIVATE (db); + + 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 +grl_daap_db_finalize (GObject *object) +{ + GrlDAAPDb *db = GRL_DAAP_DB (object); + + GRL_DEBUG ("Finalizing GrlDAAPDb"); + + 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 +grl_daap_db_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +grl_daap_db_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +static void +grl_daap_db_class_init (GrlDAAPDbClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (GrlDAAPDbPrivate)); + + object_class->finalize = grl_daap_db_finalize; + object_class->constructor = grl_daap_db_constructor; + object_class->set_property = grl_daap_db_set_property; + object_class->get_property = grl_daap_db_get_property; +} |