summaryrefslogtreecommitdiff
path: root/src/dmap/grl-daap.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/dmap/grl-daap.c')
-rw-r--r--src/dmap/grl-daap.c469
1 files changed, 469 insertions, 0 deletions
diff --git a/src/dmap/grl-daap.c b/src/dmap/grl-daap.c
new file mode 100644
index 0000000..0142bf7
--- /dev/null
+++ b/src/dmap/grl-daap.c
@@ -0,0 +1,469 @@
+/*
+ * 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-daap.h"
+#include "grl-daap-db.h"
+#include "grl-daap-record.h"
+#include "grl-daap-record-factory.h"
+
+/* --------- Logging -------- */
+
+#define GRL_LOG_DOMAIN_DEFAULT daap_log_domain
+GRL_LOG_DOMAIN_STATIC(daap_log_domain);
+
+/* --- Plugin information --- */
+
+#define PLUGIN_ID DAAP_PLUGIN_ID
+
+#define SOURCE_ID_TEMPLATE "grl-daap-%s"
+#define SOURCE_DESC_TEMPLATE _("A source for browsing the DAAP server '%s'")
+
+/* --- Grilo DAAP Private --- */
+
+#define GRL_DAAP_SOURCE_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((object), \
+ GRL_DAAP_SOURCE_TYPE, \
+ GrlDaapSourcePrivate))
+
+struct _GrlDaapSourcePrivate {
+ DMAPMdnsBrowserService *service;
+};
+
+/* --- Data types --- */
+
+typedef struct _ResultCbAndArgs {
+ GrlSourceResultCb callback;
+ GrlSource *source;
+ GrlMedia *container;
+ guint op_id;
+ GHRFunc predicate;
+ gchar *predicate_data;
+ guint skip;
+ guint count;
+ gpointer user_data;
+} ResultCbAndArgs;
+
+typedef struct _ResultCbAndArgsAndDb {
+ ResultCbAndArgs cb;
+ GrlDAAPDb *db;
+} ResultCbAndArgsAndDb;
+
+static GrlDaapSource *grl_daap_source_new (DMAPMdnsBrowserService *service);
+
+static void grl_daap_source_finalize (GObject *object);
+
+gboolean grl_daap_plugin_init (GrlRegistry *registry,
+ GrlPlugin *plugin,
+ GList *configs);
+
+static const GList *grl_daap_source_supported_keys (GrlSource *source);
+
+static void grl_daap_source_browse (GrlSource *source,
+ GrlSourceBrowseSpec *bs);
+
+static void grl_daap_source_search (GrlSource *source,
+ GrlSourceSearchSpec *ss);
+
+
+static void service_added_cb (DMAPMdnsBrowser *browser,
+ DMAPMdnsBrowserService *service,
+ GrlPlugin *plugin);
+
+static void service_removed_cb (DMAPMdnsBrowser *browser,
+ const gchar *service_name,
+ GrlPlugin *plugin);
+
+/* ===================== Globals ======================= */
+static DMAPMdnsBrowser *browser;
+/* Maps URIs to DBs */
+static GHashTable *connections;
+/* Map DAAP services to Grilo media sources */
+static GHashTable *sources;
+
+/* =================== DAAP Plugin ====================== */
+
+gboolean
+grl_daap_plugin_init (GrlRegistry *registry,
+ GrlPlugin *plugin,
+ GList *configs)
+{
+ GError *error = NULL;
+
+ GRL_LOG_DOMAIN_INIT (daap_log_domain, "daap");
+
+ GRL_DEBUG ("daap_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_DAAP);
+ 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 (service_added_cb),
+ (gpointer) plugin);
+
+ g_signal_connect (G_OBJECT (browser),
+ "service-removed",
+ G_CALLBACK (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_daap_plugin_init,
+ NULL,
+ PLUGIN_ID);
+
+/* ================== DAAP GObject ====================== */
+
+G_DEFINE_TYPE (GrlDaapSource,
+ grl_daap_source,
+ GRL_TYPE_SOURCE);
+
+static GrlDaapSource *
+grl_daap_source_new (DMAPMdnsBrowserService *service)
+{
+ gchar *source_desc;
+ gchar *source_id;
+
+ GrlDaapSource *source;
+
+ GRL_DEBUG ("grl_daap_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_DAAP_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_daap_source_class_init (GrlDaapSourceClass * klass)
+{
+ GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass);
+
+ source_class->browse = grl_daap_source_browse;
+ source_class->search = grl_daap_source_search;
+ source_class->supported_keys = grl_daap_source_supported_keys;
+
+ G_OBJECT_CLASS (source_class)->finalize = grl_daap_source_finalize;
+
+ g_type_class_add_private (klass, sizeof (GrlDaapSourcePrivate));
+}
+
+static void
+grl_daap_source_init (GrlDaapSource *source)
+{
+ source->priv = GRL_DAAP_SOURCE_GET_PRIVATE (source);
+}
+
+static void
+grl_daap_source_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (grl_daap_source_parent_class)->finalize (object);
+}
+
+/* ======================= Utilities ==================== */
+
+static gchar *
+build_url (DMAPMdnsBrowserService *service)
+{
+ return g_strdup_printf ("%s://%s:%u",
+ service->service_name,
+ service->host,
+ service->port);
+}
+
+static void
+do_browse (ResultCbAndArgsAndDb *cb_and_db)
+{
+ grl_daap_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);
+
+ g_free (cb_and_db);
+}
+
+static void
+do_search (ResultCbAndArgsAndDb *cb_and_db)
+{
+ grl_daap_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
+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 {
+ 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 {
+ do_search (cb_and_db);
+ }
+}
+
+static void
+service_added_cb (DMAPMdnsBrowser *browser,
+ DMAPMdnsBrowserService *service,
+ GrlPlugin *plugin)
+{
+ GrlRegistry *registry = grl_registry_get_default ();
+ GrlDaapSource *source = grl_daap_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
+service_removed_cb (DMAPMdnsBrowser *browser,
+ const gchar *service_name,
+ GrlPlugin *plugin)
+{
+ GrlRegistry *registry = grl_registry_get_default ();
+ GrlDaapSource *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_daap_connect (gchar *name, gchar *host, guint port, ResultCbAndArgsAndDb *cb_and_db, DMAPConnectionCallback callback)
+{
+ DMAPRecordFactory *factory;
+ DMAPConnection *connection;
+
+ factory = DMAP_RECORD_FACTORY (grl_daap_record_factory_new ());
+ connection = DMAP_CONNECTION (daap_connection_new (name, host, port, DMAP_DB (cb_and_db->db), factory));
+ dmap_connection_connect (connection, (DMAPConnectionCallback) callback, cb_and_db);
+}
+
+static gboolean
+match (GrlMedia *media, gpointer val, gpointer user_data)
+{
+ g_assert (GRL_IS_MEDIA_AUDIO (media) || GRL_IS_MEDIA_VIDEO (media));
+
+ if (NULL == user_data) {
+ return TRUE;
+ }
+
+ const char *title = grl_media_get_title (media);
+ return strstr (title, user_data) != NULL;
+}
+
+/* ================== API Implementation ================ */
+
+static const GList *
+grl_daap_source_supported_keys (GrlSource *source)
+{
+ static GList *keys = NULL;
+
+ GRL_DEBUG (__func__);
+
+ if (!keys) {
+ keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ALBUM,
+ GRL_METADATA_KEY_ARTIST,
+ GRL_METADATA_KEY_BITRATE,
+ GRL_METADATA_KEY_DURATION,
+ GRL_METADATA_KEY_GENRE,
+ GRL_METADATA_KEY_ID,
+ GRL_METADATA_KEY_TITLE,
+ GRL_METADATA_KEY_TRACK_NUMBER,
+ GRL_METADATA_KEY_URL,
+ NULL);
+ }
+ return keys;
+}
+
+static void
+grl_daap_source_browse (GrlSource *source,
+ GrlSourceBrowseSpec *bs)
+{
+ GrlDaapSource *daap_source = GRL_DAAP_SOURCE (source);
+ gchar *url = build_url (daap_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 = grl_daap_db_new ();
+
+ grl_daap_connect (daap_source->priv->service->name,
+ daap_source->priv->service->host,
+ daap_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_daap_source_search (GrlSource *source,
+ GrlSourceSearchSpec *ss)
+{
+ GrlDaapSource *daap_source = GRL_DAAP_SOURCE (source);
+
+ ResultCbAndArgsAndDb *cb_and_db;
+ DMAPMdnsBrowserService *service = daap_source->priv->service;
+ gchar *url = 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) 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 = grl_daap_db_new ();
+ grl_daap_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);
+}