[libdmapsharing] Make some functions static
- From: W. Michael Petullo <wmpetullo src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libdmapsharing] Make some functions static
- Date: Sun, 8 Dec 2019 01:51:43 +0000 (UTC)
commit 9d377f803d09cd3fb69f1d8db55e17f3575fa0b1
Author: W. Michael Petullo <mike flyn org>
Date: Fri Dec 6 22:33:58 2019 -0500
Make some functions static
Signed-off-by: W. Michael Petullo <mike flyn org>
libdmapsharing/dmap-av-share.c | 67 +-
libdmapsharing/dmap-image-share.c | 39 +-
libdmapsharing/dmap-share-private.h | 53 -
libdmapsharing/dmap-share.c | 3211 +++++++++++++++++------------------
4 files changed, 1654 insertions(+), 1716 deletions(-)
---
diff --git a/libdmapsharing/dmap-av-share.c b/libdmapsharing/dmap-av-share.c
index 0dedb6e..1bf3ed4 100644
--- a/libdmapsharing/dmap-av-share.c
+++ b/libdmapsharing/dmap-av-share.c
@@ -44,13 +44,13 @@
#include <libdmapsharing/dmap-gst-input-stream.h>
#endif /* HAVE_GSTREAMERAPP */
-guint dmap_av_share_get_desired_port (DmapShare * share);
-const char *dmap_av_share_get_type_of_service (DmapShare * share);
-void dmap_av_share_server_info (DmapShare * share,
- SoupMessage * message,
- const char *path);
-void dmap_av_share_message_add_standard_headers (DmapShare * share,
- SoupMessage * message);
+static guint _get_desired_port (DmapShare * share);
+static const char *_get_type_of_service (DmapShare * share);
+static void _server_info (DmapShare * share,
+ SoupMessage * message,
+ const char *path);
+static void _message_add_standard_headers (DmapShare * share,
+ SoupMessage * message);
static void _databases_browse_xxx (DmapShare * share,
SoupMessage * msg,
const char *path,
@@ -73,15 +73,14 @@ dmap_av_share_class_init (DmapAvShareClass * klass)
GObjectClass *object_class = G_OBJECT_CLASS (klass);
DmapShareClass *parent_class = DMAP_SHARE_CLASS (object_class);
- parent_class->get_desired_port = dmap_av_share_get_desired_port;
- parent_class->get_type_of_service = dmap_av_share_get_type_of_service;
- parent_class->message_add_standard_headers =
- dmap_av_share_message_add_standard_headers;
+ parent_class->get_desired_port = _get_desired_port;
+ parent_class->get_type_of_service = _get_type_of_service;
+ parent_class->message_add_standard_headers = _message_add_standard_headers;
parent_class->get_meta_data_map = _get_meta_data_map;
parent_class->add_entry_to_mlcl = _add_entry_to_mlcl;
parent_class->databases_browse_xxx = _databases_browse_xxx;
parent_class->databases_items_xxx = _databases_items_xxx;
- parent_class->server_info = dmap_av_share_server_info;
+ parent_class->server_info = _server_info;
}
static void
@@ -105,9 +104,9 @@ dmap_av_share_new (const char *name,
transcode_mimetype, NULL));
}
-void
-dmap_av_share_message_add_standard_headers (G_GNUC_UNUSED DmapShare * share,
- SoupMessage * message)
+static void
+_message_add_standard_headers (G_GNUC_UNUSED DmapShare * share,
+ SoupMessage * message)
{
soup_message_headers_append (message->response_headers, "DMAP-Server",
"libdmapsharing" VERSION);
@@ -117,22 +116,22 @@ dmap_av_share_message_add_standard_headers (G_GNUC_UNUSED DmapShare * share,
#define DAAP_VERSION 3.0
#define DAAP_TIMEOUT 1800
-guint
-dmap_av_share_get_desired_port (G_GNUC_UNUSED DmapShare * share)
+static guint
+_get_desired_port (G_GNUC_UNUSED DmapShare * share)
{
return DAAP_PORT;
}
-const char *
-dmap_av_share_get_type_of_service (G_GNUC_UNUSED DmapShare * share)
+static const char *
+_get_type_of_service (G_GNUC_UNUSED DmapShare * share)
{
return DAAP_TYPE_OF_SERVICE;
}
-void
-dmap_av_share_server_info (DmapShare * share,
- SoupMessage * message,
- const char *path)
+static void
+_server_info (DmapShare * share,
+ SoupMessage * message,
+ const char *path)
{
/* MSRV server info response
* MSTT status
@@ -1350,25 +1349,25 @@ START_TEST(_should_transcode_test_yes_trancode_mimetype_to_mp4)
}
END_TEST
-START_TEST(dmap_av_share_get_desired_port_test)
+START_TEST(_get_desired_port_test)
{
- DmapShare *share = _build_share_test("dmap_av_share_get_desired_port_test");
- ck_assert_int_eq(DAAP_PORT, dmap_av_share_get_desired_port(share));
+ DmapShare *share = _build_share_test("_get_desired_port_test");
+ ck_assert_int_eq(DAAP_PORT, _get_desired_port(share));
g_object_unref(share);
}
END_TEST
-START_TEST(dmap_av_share_get_type_of_service_test)
+START_TEST(_get_type_of_service_test)
{
- DmapShare *share = _build_share_test("dmap_av_share_get_type_of_service_test");
- ck_assert_str_eq(DAAP_TYPE_OF_SERVICE, dmap_av_share_get_type_of_service(share));
+ DmapShare *share = _build_share_test("_get_type_of_service_test");
+ ck_assert_str_eq(DAAP_TYPE_OF_SERVICE, _get_type_of_service(share));
g_object_unref(share);
}
END_TEST
-START_TEST(dmap_av_share_server_info_test)
+START_TEST(_server_info_test)
{
- char *nameprop = "dmap_av_share_server_info_test";
+ char *nameprop = "_server_info_test";
DmapShare *share;
SoupMessage *message;
SoupMessageBody *body;
@@ -1384,7 +1383,7 @@ START_TEST(dmap_av_share_server_info_test)
/* Causes auth. method to be set to DMAP_SHARE_AUTH_METHOD_PASSWORD. */
g_object_set(share, "password", "password", NULL);
- dmap_av_share_server_info(share, message, "/");
+ _server_info(share, message, "/");
g_object_get(message, "response-body", &body, NULL);
buffer = soup_message_body_flatten(body);
@@ -1444,14 +1443,14 @@ START_TEST(dmap_av_share_server_info_test)
}
END_TEST
-START_TEST(dmap_av_share_message_add_standard_headers_test)
+START_TEST(_message_add_standard_headers_test)
{
const char *header;
DmapShare *share;
SoupMessage *message;
SoupMessageHeaders *headers;
- share = _build_share_test("dmap_av_share_message_add_standard_headers_test");
+ share = _build_share_test("_message_add_standard_headers_test");
message = soup_message_new(SOUP_METHOD_GET, "http://test/");
soup_message_headers_append(message->response_headers,
diff --git a/libdmapsharing/dmap-image-share.c b/libdmapsharing/dmap-image-share.c
index a98bc9e..80671c5 100644
--- a/libdmapsharing/dmap-image-share.c
+++ b/libdmapsharing/dmap-image-share.c
@@ -45,13 +45,10 @@
#include <libdmapsharing/dmap-private-utils.h>
#include <libdmapsharing/dmap-structure.h>
-guint dmap_image_share_get_desired_port (DmapShare * share);
-const char *dmap_image_share_get_type_of_service (DmapShare * share);
-void dmap_image_share_server_info (DmapShare * share,
- SoupMessage * message,
- const char *path);
-void dmap_image_share_message_add_standard_headers (DmapShare * share,
- SoupMessage * message);
+static guint _get_desired_port (DmapShare * share);
+static const char *_get_type_of_service (DmapShare * share);
+static void _server_info (DmapShare * share, SoupMessage * message, const char *path);
+static void _message_add_standard_headers (DmapShare * share, SoupMessage * message);
#define DPAP_TYPE_OF_SERVICE "_dpap._tcp"
#define DPAP_PORT 8770
@@ -429,15 +426,14 @@ dmap_image_share_class_init (DmapImageShareClass * klass)
GObjectClass *object_class = G_OBJECT_CLASS (klass);
DmapShareClass *parent_class = DMAP_SHARE_CLASS (object_class);
- parent_class->get_desired_port = dmap_image_share_get_desired_port;
- parent_class->get_type_of_service = dmap_image_share_get_type_of_service;
- parent_class->message_add_standard_headers =
- dmap_image_share_message_add_standard_headers;
+ parent_class->get_desired_port = _get_desired_port;
+ parent_class->get_type_of_service = _get_type_of_service;
+ parent_class->message_add_standard_headers = _message_add_standard_headers;
parent_class->get_meta_data_map = _get_meta_data_map;
parent_class->add_entry_to_mlcl = _add_entry_to_mlcl;
parent_class->databases_browse_xxx = _databases_browse_xxx;
parent_class->databases_items_xxx = _databases_items_xxx;
- parent_class->server_info = dmap_image_share_server_info;
+ parent_class->server_info = _server_info;
}
static void
@@ -468,9 +464,8 @@ dmap_image_share_new (const char *name,
NULL));
}
-void
-dmap_image_share_message_add_standard_headers (G_GNUC_UNUSED DmapShare * share,
- SoupMessage * message)
+static void
+_message_add_standard_headers (G_GNUC_UNUSED DmapShare * share, SoupMessage * message)
{
soup_message_headers_append (message->response_headers, "DPAP-Server",
"libdmapsharing" VERSION);
@@ -480,22 +475,20 @@ dmap_image_share_message_add_standard_headers (G_GNUC_UNUSED DmapShare * share,
#define DPAP_VERSION 1.1
#define DPAP_TIMEOUT 1800
-guint
-dmap_image_share_get_desired_port (G_GNUC_UNUSED DmapShare * share)
+static guint
+_get_desired_port (G_GNUC_UNUSED DmapShare * share)
{
return DPAP_PORT;
}
-const char *
-dmap_image_share_get_type_of_service (G_GNUC_UNUSED DmapShare * share)
+static const char *
+_get_type_of_service (G_GNUC_UNUSED DmapShare * share)
{
return DPAP_TYPE_OF_SERVICE;
}
-void
-dmap_image_share_server_info (DmapShare * share,
- SoupMessage * message,
- const char *path)
+static void
+_server_info (DmapShare * share, SoupMessage * message, const char *path)
{
/* MSRV server info response
* MSTT status
diff --git a/libdmapsharing/dmap-share-private.h b/libdmapsharing/dmap-share-private.h
index 38f4a8a..cf17bc2 100644
--- a/libdmapsharing/dmap-share-private.h
+++ b/libdmapsharing/dmap-share-private.h
@@ -40,78 +40,25 @@ G_BEGIN_DECLS
/* Non-virtual methods */
guint dmap_share_get_auth_method (DmapShare * share);
-guint dmap_share_get_revision_number (DmapShare * share);
-
-gboolean dmap_share_get_revision_number_from_query (GHashTable * query,
- guint * number);
-
gboolean dmap_share_session_id_validate (DmapShare * share,
SoupClientContext * context,
GHashTable * query,
guint32 * id);
-guint32 dmap_share_session_id_create (DmapShare * share,
- SoupClientContext * ctx);
-
gboolean dmap_share_client_requested (DmapBits bits, gint field);
-gboolean dmap_share_uri_is_local (const char *text_uri);
-
-gboolean dmap_share_soup_auth_filter (SoupAuthDomain * auth_domain,
- SoupMessage * msg, gpointer user_data);
-
void dmap_share_message_set_from_dmap_structure (DmapShare * share,
SoupMessage * message,
GNode * structure);
-DmapBits dmap_share_parse_meta (GHashTable * query,
- struct DmapMetaDataMap *mdm);
-
-DmapBits dmap_share_parse_meta_str (const char *attrs,
- struct DmapMetaDataMap *mdm);
-
-void dmap_share_add_playlist_to_mlcl (guint id,
- DmapContainerRecord * record,
- gpointer mb);
-
GSList *dmap_share_build_filter (gchar * filterstr);
-/* Virtual methods (libsoup callbacks with default implementation): */
-void dmap_share_content_codes (DmapShare * share,
- SoupMessage * message,
- const char *path,
- SoupClientContext * context);
-
void dmap_share_login (DmapShare * share,
SoupMessage * message,
const char *path,
GHashTable * query,
SoupClientContext * context);
-void dmap_share_logout (DmapShare * share,
- SoupMessage * message,
- const char *path,
- GHashTable * query,
- SoupClientContext * context);
-
-void dmap_share_update (DmapShare * share,
- SoupServer * server,
- SoupMessage * message,
- const char *path,
- GHashTable * query);
-
-void dmap_share_databases (DmapShare * share,
- SoupServer * server,
- SoupMessage * message,
- const char *path,
- GHashTable * query, SoupClientContext * context);
-
-void dmap_share_ctrl_int (DmapShare * share,
- SoupServer * server,
- SoupMessage * message,
- const char *path,
- GHashTable * query, SoupClientContext * context);
-
/* Virtual methods: MDNS callbacks */
void dmap_share_name_collision (DmapShare * share,
DmapMdnsPublisher * publisher,
diff --git a/libdmapsharing/dmap-share.c b/libdmapsharing/dmap-share.c
index 310fe7c..780f706 100644
--- a/libdmapsharing/dmap-share.c
+++ b/libdmapsharing/dmap-share.c
@@ -62,6 +62,13 @@ enum
static guint _signals[LAST_SIGNAL] = { 0, };
+typedef struct {
+ gchar *name;
+ gint64 group_id;
+ gchar *artist;
+ int count;
+} GroupInfo;
+
struct DmapSharePrivate
{
gchar *name;
@@ -153,6 +160,48 @@ _server_info_adapter (G_GNUC_UNUSED SoupServer * server,
DMAP_SHARE_GET_CLASS (share)->server_info (share, message, path);
}
+static void
+_content_codes (DmapShare * share,
+ SoupMessage * message,
+ const char *path,
+ G_GNUC_UNUSED SoupClientContext * context)
+{
+/* MCCR content codes response
+ * MSTT status
+ * MDCL dictionary
+ * MCNM content codes number
+ * MCNA content codes name
+ * MCTY content codes type
+ * MDCL dictionary
+ * ...
+ */
+ const DmapContentCodeDefinition *defs;
+ guint num_defs = 0;
+ guint i;
+ GNode *mccr;
+
+ g_debug ("Path is %s.", path);
+
+ defs = dmap_structure_content_codes (&num_defs);
+
+ mccr = dmap_structure_add (NULL, DMAP_CC_MCCR);
+ dmap_structure_add (mccr, DMAP_CC_MSTT, (gint32) SOUP_STATUS_OK);
+
+ for (i = 0; i < num_defs; i++) {
+ GNode *mdcl;
+
+ mdcl = dmap_structure_add (mccr, DMAP_CC_MDCL);
+ dmap_structure_add (mdcl, DMAP_CC_MCNM,
+ dmap_structure_cc_string_as_int32 (defs[i].string));
+ dmap_structure_add (mdcl, DMAP_CC_MCNA, defs[i].name);
+ dmap_structure_add (mdcl, DMAP_CC_MCTY,
+ (gint32) defs[i].type);
+ }
+
+ dmap_share_message_set_from_dmap_structure (share, message, mccr);
+ dmap_structure_destroy (mccr);
+}
+
static void
_content_codes_adapter (G_GNUC_UNUSED SoupServer * server,
SoupMessage * message,
@@ -178,6 +227,36 @@ _login_adapter (G_GNUC_UNUSED SoupServer * server,
DMAP_SHARE_GET_CLASS (share)->login (share, message, path, query, context);
}
+static void
+_session_id_remove (DmapShare * share,
+ guint32 id)
+{
+ g_hash_table_remove (share->priv->session_ids, GUINT_TO_POINTER (id));
+}
+
+static void
+_logout (DmapShare * share,
+ SoupMessage * message,
+ const char *path,
+ GHashTable * query, SoupClientContext * context)
+{
+ int status;
+ guint32 id;
+
+ g_debug ("Path is %s.", path);
+
+ if (dmap_share_session_id_validate
+ (share, context, query, &id)) {
+ _session_id_remove (share, id);
+
+ status = SOUP_STATUS_NO_CONTENT;
+ } else {
+ status = SOUP_STATUS_FORBIDDEN;
+ }
+
+ soup_message_set_status (message, status);
+}
+
static void
_logout_adapter (G_GNUC_UNUSED SoupServer * server,
SoupMessage * message,
@@ -189,6 +268,78 @@ _logout_adapter (G_GNUC_UNUSED SoupServer * server,
DMAP_SHARE_GET_CLASS (share)->logout (share, message, path, query, context);
}
+static guint
+_get_revision_number (DmapShare * share)
+{
+ return share->priv->revision_number;
+}
+
+static gboolean
+_get_revision_number_from_query (GHashTable * query,
+ guint * number)
+{
+ gboolean ok = FALSE;
+ char *revision_number_str;
+ guint revision_number;
+
+ revision_number_str = g_hash_table_lookup (query, "revision-number");
+ if (revision_number_str == NULL) {
+ g_warning
+ ("Client asked for an update without a rev. number");
+ goto done;
+ }
+
+ revision_number = strtoul (revision_number_str, NULL, 10);
+ if (number != NULL) {
+ *number = revision_number;
+ }
+
+ ok = TRUE;
+
+done:
+ return ok;
+}
+
+static void
+_update (DmapShare * share,
+ SoupServer * server,
+ SoupMessage * message,
+ const char *path,
+ GHashTable * query)
+{
+ guint revision_number;
+ gboolean res;
+
+ g_debug ("Path is %s.", path);
+
+ res = _get_revision_number_from_query (query, &revision_number);
+
+ if (res && revision_number != _get_revision_number (share)) {
+ /* MUPD update response
+ * MSTT status
+ * MUSR server revision
+ */
+ GNode *mupd;
+
+ mupd = dmap_structure_add (NULL, DMAP_CC_MUPD);
+ dmap_structure_add (mupd, DMAP_CC_MSTT,
+ (gint32) SOUP_STATUS_OK);
+ dmap_structure_add (mupd, DMAP_CC_MUSR,
+ (gint32)
+ _get_revision_number (share));
+
+ dmap_share_message_set_from_dmap_structure (share, message,
+ mupd);
+ dmap_structure_destroy (mupd);
+ } else {
+ /* FIXME: This seems like a bug. It just leaks the
+ * message (and socket) without ever replying.
+ */
+ g_object_ref (message);
+ soup_server_pause_message (server, message);
+ }
+}
+
static void
_update_adapter (SoupServer * server,
SoupMessage * message,
@@ -203,778 +354,860 @@ _update_adapter (SoupServer * server,
}
static void
-_databases_adapter (SoupServer * server,
- SoupMessage * message,
- const char *path,
- GHashTable * query,
- SoupClientContext * context,
- DmapShare * share)
+_debug_param (gpointer key, gpointer val, G_GNUC_UNUSED gpointer user_data)
{
- DMAP_SHARE_GET_CLASS (share)->databases (share,
- server,
- message,
- path, query, context);
+ g_debug ("%s %s", (char *) key, (char *) val);
}
static void
-_ctrl_int_adapter (SoupServer * server,
- SoupMessage * message,
- const char *path,
- GHashTable * query,
- SoupClientContext * context,
- DmapShare * share)
+_add_playlist_to_mlcl (G_GNUC_UNUSED guint id,
+ DmapContainerRecord * record,
+ gpointer _mb)
{
- DMAP_SHARE_GET_CLASS (share)->ctrl_int (share,
- server,
- message,
- path, query, context);
-}
+ /* MLIT listing item
+ * MIID item id
+ * MPER persistent item id
+ * MINM item name
+ * MIMC item count
+ */
+ GNode *mlit;
+ guint num_songs;
+ gchar *name;
+ struct DmapMlclBits *mb = (struct DmapMlclBits *) _mb;
-static void
-_set_name (DmapShare * share, const char *name)
-{
- GError *error;
+ num_songs = dmap_container_record_get_entry_count (record);
+ g_object_get (record, "name", &name, NULL);
- g_return_if_fail (share != NULL);
+ /* FIXME: ITEM_ID, etc. is defined in DmapAvShare, so I can't use
+ * with dmap_share_client_requested() here (see add_entry_to_mlcl())
+ */
- g_free (share->priv->name);
- share->priv->name = g_strdup (name);
+ mlit = dmap_structure_add (mb->mlcl, DMAP_CC_MLIT);
+ dmap_structure_add (mlit, DMAP_CC_MIID,
+ dmap_container_record_get_id (record));
+ /* we don't have a persistant ID for playlists, unfortunately */
+ dmap_structure_add (mlit, DMAP_CC_MPER,
+ (gint64) dmap_container_record_get_id (record));
+ dmap_structure_add (mlit, DMAP_CC_MINM, name);
+ dmap_structure_add (mlit, DMAP_CC_MIMC, (gint32) num_songs);
- if (share->priv->published) {
- error = NULL;
- dmap_mdns_publisher_rename_at_port (share->priv->
- publisher,
- share->priv->port,
- name,
- &error);
- if (error != NULL) {
- g_warning ("Unable to change MDNS service name: %s",
- error->message);
- g_error_free (error);
- }
- }
-}
+ /* FIXME: Is this getting music-specific? */
+ dmap_structure_add (mlit, DMAP_CC_FQUESCH, 0);
+ dmap_structure_add (mlit, DMAP_CC_MPCO, 0);
+ dmap_structure_add (mlit, DMAP_CC_AESP, 0);
+ dmap_structure_add (mlit, DMAP_CC_AEPP, 0);
+ dmap_structure_add (mlit, DMAP_CC_AEPS, 0);
+ dmap_structure_add (mlit, DMAP_CC_AESG, 0);
-static void
-_published (DmapShare * share,
- G_GNUC_UNUSED DmapMdnsPublisher * publisher,
- const char *name)
-{
- if (share->priv->name == NULL || name == NULL) {
- return;
- }
+ g_free (name);
- if (strcmp (share->priv->name, name) == 0) {
- g_debug ("mDNS publish successful");
- share->priv->published = TRUE;
- }
+ return;
}
static void
-_published_adapter (DmapMdnsPublisher * publisher,
- const char *name,
- DmapShare * share)
+_write_dmap_preamble (SoupMessage * message, GNode * node)
{
- DMAP_SHARE_GET_CLASS (share)->published (share, publisher, name);
+ guint length;
+ gchar *data = dmap_structure_serialize (node, &length);
+
+ soup_message_body_append (message->response_body,
+ SOUP_MEMORY_TAKE, data, length);
+ dmap_structure_destroy (node);
}
static void
-_name_collision (DmapShare * share,
- G_GNUC_UNUSED DmapMdnsPublisher * publisher,
- const char *name)
+_write_next_mlit (SoupMessage * message, struct share_bitwise_t *share_bitwise)
{
- g_assert(NULL != name);
- g_assert(NULL != share->priv->name);
+ if (share_bitwise->id_list == NULL) {
+ g_debug ("No more ID's, sending message complete.");
+ soup_message_body_complete (message->response_body);
+ } else {
+ gchar *data = NULL;
+ guint length;
+ DmapRecord *record;
+ struct DmapMlclBits mb = { NULL, 0, NULL };
- g_warning ("Duplicate share name on mDNS; renaming share to %s", name);
+ record = share_bitwise->lookup_by_id (share_bitwise->db,
+ GPOINTER_TO_UINT
+ (share_bitwise->
+ id_list->data));
- _set_name (DMAP_SHARE (share), name);
+ mb.bits = share_bitwise->mb.bits;
+ mb.mlcl = dmap_structure_add (NULL, DMAP_CC_MLCL);
+ mb.share = share_bitwise->mb.share;
- return;
-}
+ DMAP_SHARE_GET_CLASS (share_bitwise->mb.share)->
+ add_entry_to_mlcl (GPOINTER_TO_UINT(share_bitwise->id_list->data), record, &mb);
+ data = dmap_structure_serialize (g_node_first_child (mb.mlcl),
+ &length);
-static void
-_name_collision_adapter (DmapMdnsPublisher * publisher,
- const char *name, DmapShare * share)
-{
- DMAP_SHARE_GET_CLASS (share)->name_collision (share, publisher, name);
-}
-
-gboolean
-dmap_share_serve (DmapShare *share, GError **error)
-{
- guint desired_port = DMAP_SHARE_GET_CLASS (share)->get_desired_port (share);
- gboolean password_required, ok = FALSE;
- GSList *listening_uri_list;
- SoupURI *listening_uri;
- gboolean ret;
- GError *error2 = NULL;
-
- password_required = (share->priv->auth_method != DMAP_SHARE_AUTH_METHOD_NONE);
+ soup_message_body_append (message->response_body,
+ SOUP_MEMORY_TAKE, data, length);
+ g_debug ("Sending ID %u.",
+ GPOINTER_TO_UINT (share_bitwise->id_list->data));
+ dmap_structure_destroy (mb.mlcl);
- if (password_required) {
- SoupAuthDomain *auth_domain;
+ share_bitwise->id_list =
+ g_slist_remove (share_bitwise->id_list,
+ share_bitwise->id_list->data);
- auth_domain =
- soup_auth_domain_basic_new (SOUP_AUTH_DOMAIN_REALM,
- "Music Sharing",
- SOUP_AUTH_DOMAIN_ADD_PATH,
- "/login",
- SOUP_AUTH_DOMAIN_ADD_PATH,
- "/update",
- SOUP_AUTH_DOMAIN_ADD_PATH,
- "/database",
- SOUP_AUTH_DOMAIN_FILTER,
- dmap_share_soup_auth_filter,
- NULL);
- soup_auth_domain_basic_set_auth_callback (auth_domain,
- (SoupAuthDomainBasicAuthCallback)
- _soup_auth_callback,
- g_object_ref
- (share),
- g_object_unref);
- soup_server_add_auth_domain (share->priv->server, auth_domain);
+ g_object_unref (record);
}
- soup_server_add_handler (share->priv->server, "/server-info",
- (SoupServerCallback) _server_info_adapter,
- share, NULL);
- soup_server_add_handler (share->priv->server, "/content-codes",
- (SoupServerCallback) _content_codes_adapter,
- share, NULL);
- soup_server_add_handler (share->priv->server, "/login",
- (SoupServerCallback) _login_adapter,
- share, NULL);
- soup_server_add_handler (share->priv->server, "/logout",
- (SoupServerCallback) _logout_adapter,
- share, NULL);
- soup_server_add_handler (share->priv->server, "/update",
- (SoupServerCallback) _update_adapter,
- share, NULL);
- soup_server_add_handler (share->priv->server, "/databases",
- (SoupServerCallback) _databases_adapter,
- share, NULL);
- soup_server_add_handler (share->priv->server, "/ctrl-int",
- (SoupServerCallback) _ctrl_int_adapter,
- share, NULL);
+ soup_server_unpause_message (share_bitwise->share->priv->server, message);
+}
- ret = soup_server_listen_all (share->priv->server, desired_port, 0, &error2);
- if (ret == FALSE) {
- g_debug ("Unable to start music sharing server on port %d: %s. "
- "Trying any open IPv6 port", desired_port, error2->message);
- g_error_free(error2);
+static void
+_group_items (G_GNUC_UNUSED gpointer key, DmapRecord * record, GHashTable * groups)
+{
+ gchar *album, *artist;
+ GroupInfo *group_info;
+ gint64 group_id;
- ret = soup_server_listen_all (share->priv->server, SOUP_ADDRESS_ANY_PORT,
- 0, error);
+ g_object_get (record, "songartist", &artist, "songalbum", &album,
+ "songalbumid", &group_id, NULL);
+ if (!album) {
+ g_free (artist);
+ return;
}
-
- listening_uri_list = soup_server_get_uris (share->priv->server);
- if (ret == FALSE || listening_uri_list == NULL) {
- goto done;
+ group_info = g_hash_table_lookup (groups, album);
+ if (!group_info) {
+ group_info = g_new0 (GroupInfo, 1);
+ g_hash_table_insert (groups, album, group_info);
+ // They will be freed when the hash table is freed.
+ group_info->name = album;
+ group_info->artist = artist;
+ group_info->group_id = group_id;
+ } else {
+ g_free (album);
+ g_free (artist);
}
+ (group_info->count)++;
+}
- /* We can only expose one port, so no point checking more than one URI
- * here. Maybe it somehow is listening on a different port for IPv4 vs.
- * IPv6, but there's not much we can do.
- */
- listening_uri = listening_uri_list->data;
- share->priv->port = soup_uri_get_port (listening_uri);
- g_slist_free_full (listening_uri_list, (GDestroyNotify) soup_uri_free);
+static gint
+_group_info_cmp (gconstpointer group1, gconstpointer group2)
+{
+ return g_ascii_strcasecmp (((GroupInfo *) group1)->name,
+ ((GroupInfo *) group2)->name);
+}
- g_debug ("Started DMAP server on port %u", share->priv->port);
+static DmapRecord *
+_lookup_adapter (GHashTable * ht, guint id)
+{
+ /* NOTE: each time this is called by _write_next_mlit(), the
+ * returned value will be unref'ed by _write_next_mlit(). We
+ * also need to destroy the GHashTable, so bump up the reference
+ * count so that both can happen. */
+ return g_object_ref (g_hash_table_lookup (ht, GUINT_TO_POINTER (id)));
+}
- share->priv->server_active = TRUE;
+static void
+_accumulate_mlcl_size_and_ids (guint id,
+ DmapRecord * record,
+ struct share_bitwise_t *share_bitwise)
+{
+ share_bitwise->id_list = g_slist_append (share_bitwise->id_list, GUINT_TO_POINTER(id));
- ok = TRUE;
+ /* Make copy and set mlcl to NULL so real MLCL does not get changed */
+ struct DmapMlclBits mb_copy = share_bitwise->mb;
-done:
- g_assert((!ok && (NULL == error || NULL != *error))
- || ( ok && (NULL == error || NULL == *error)));
+ mb_copy.mlcl = dmap_structure_add (NULL, DMAP_CC_MLCL);;
- return ok;
+ DMAP_SHARE_GET_CLASS (share_bitwise->mb.share)->add_entry_to_mlcl (id,
+ record,
+ &mb_copy);
+ share_bitwise->size += dmap_structure_get_size (mb_copy.mlcl);
+
+ /* Minus eight because we do not want to add size of MLCL CC field + size field n times,
+ * where n == number of records.
+ */
+ share_bitwise->size -= 8;
+
+ /* Destroy created structures as we go. */
+ dmap_structure_destroy (mb_copy.mlcl);
}
-static gboolean
-_server_stop (DmapShare * share)
+static void
+_accumulate_mlcl_size_and_ids_adapter (gpointer id,
+ DmapRecord * record,
+ struct share_bitwise_t *share_bitwise)
{
- g_debug ("Stopping music sharing server on port %d",
- share->priv->port);
-
- if (share->priv->server) {
- soup_server_disconnect (share->priv->server);
- }
+ _accumulate_mlcl_size_and_ids(GPOINTER_TO_UINT(id),
+ record,
+ share_bitwise);
+}
- if (share->priv->session_ids) {
- g_hash_table_remove_all (share->priv->session_ids);
+static void
+_chunked_message_finished (G_GNUC_UNUSED SoupMessage * message,
+ struct share_bitwise_t *share_bitwise)
+{
+ g_debug ("Finished sending chunked data.");
+ if (share_bitwise->destroy) {
+ share_bitwise->destroy (share_bitwise->db);
}
-
- share->priv->server_active = FALSE;
-
- return TRUE;
+ g_free (share_bitwise);
}
-gboolean
-dmap_share_publish (DmapShare *share, GError **error)
+static DmapBits
+_parse_meta_str (const char *attrs, struct DmapMetaDataMap *mdm)
{
- gboolean ok;
- gboolean password_required;
+ guint i;
+ DmapBits bits = 0;
- password_required =
- (share->priv->auth_method != DMAP_SHARE_AUTH_METHOD_NONE);
+ /* iTunes 8 uses meta=all for /databases/1/items query: */
+ if (strcmp (attrs, "all") == 0) {
+ bits = ~0;
+ } else {
+ gchar **attrsv;
- ok = dmap_mdns_publisher_publish (share->priv->publisher,
- share->priv->name,
- share->priv->port,
- DMAP_SHARE_GET_CLASS (share)->
- get_type_of_service (share),
- password_required,
- share->priv->txt_records, error);
+ attrsv = g_strsplit (attrs, ",", -1);
- if (ok == FALSE) {
- goto done;
- }
+ for (i = 0; attrsv[i]; i++) {
+ guint j;
+ gboolean found = FALSE;
- g_debug ("Published DMAP server information to mdns");
+ for (j = 0; mdm[j].tag; j++) {
+ if (strcmp (mdm[j].tag, attrsv[i]) == 0) {
+ bits |= (((DmapBits) 1) << mdm[j].md);
+ found = TRUE;
+ }
+ }
-done:
- return ok;
+ if (found == FALSE) {
+ g_debug ("Unknown meta request: %s",
+ attrsv[i]);
+ }
+ }
+ g_strfreev (attrsv);
+ }
+
+ return bits;
}
-static gboolean
-_publish_stop (DmapShare * share)
+static DmapBits
+_parse_meta (GHashTable * query, struct DmapMetaDataMap * mdm)
{
- GError *error;
- gboolean ok = FALSE;
+ DmapBits bits = 0;
+ const gchar *attrs;
- if (!share->priv->publisher) {
- share->priv->published = FALSE;
+ attrs = g_hash_table_lookup (query, "meta");
+ if (attrs == NULL) {
goto done;
}
- error = NULL;
- ok = dmap_mdns_publisher_withdraw (share->priv->publisher,
- share->priv->port,
- &error);
- if (error != NULL) {
- g_warning
- ("Unable to withdraw music sharing service: %s",
- error->message);
- g_error_free (error);
- }
+ bits = _parse_meta_str (attrs, mdm);
done:
- return ok;
+ return bits;
}
static void
-_restart (DmapShare * share)
+_databases (DmapShare * share,
+ SoupServer * server,
+ SoupMessage * message,
+ const char *path,
+ GHashTable * query, SoupClientContext * context)
{
- GError *error = NULL;
- gboolean res;
+ const char *rest_of_path;
- _server_stop (share);
- res = dmap_share_serve (share, &error);
- if (NULL != error) {
- g_signal_emit (share, _signals[ERROR], 0, error);
- _publish_stop (share);
- } else {
- g_assert(FALSE != res);
-
- /* To update information just publish again */
- dmap_share_publish (share, NULL);
- }
-}
-
-static void
-_maybe_restart (DmapShare * share)
-{
- if (share->priv->published) {
- _restart (share);
- }
-}
+ g_debug ("Path is %s.", path);
+ g_hash_table_foreach (query, _debug_param, NULL);
-static void
-_set_password (DmapShare * share, const char *password)
-{
- if (share->priv->password && password &&
- 0 == strcmp (password, share->priv->password)) {
+ if (!dmap_share_session_id_validate
+ (share, context, query, NULL)) {
+ soup_message_set_status (message, SOUP_STATUS_FORBIDDEN);
goto done;
}
- g_free (share->priv->password);
- share->priv->password = g_strdup (password);
- if (password != NULL) {
- share->priv->auth_method = DMAP_SHARE_AUTH_METHOD_PASSWORD;
- } else {
- share->priv->auth_method = DMAP_SHARE_AUTH_METHOD_NONE;
- }
+ rest_of_path = strchr (path + 1, '/');
- _maybe_restart (share);
+ if (rest_of_path == NULL) {
+ /* AVDB server databases
+ * MSTT status
+ * MUTY update type
+ * MTCO specified total count
+ * MRCO returned count
+ * MLCL listing
+ * MLIT listing item
+ * MIID item id
+ * MPER persistent id
+ * MINM item name
+ * MIMC item count
+ * MCTC container count
+ */
+ GNode *avdb;
+ GNode *mlcl;
+ GNode *mlit;
-done:
- return;
-}
+ avdb = dmap_structure_add (NULL, DMAP_CC_AVDB);
+ dmap_structure_add (avdb, DMAP_CC_MSTT,
+ (gint32) SOUP_STATUS_OK);
+ dmap_structure_add (avdb, DMAP_CC_MUTY, 0);
+ dmap_structure_add (avdb, DMAP_CC_MTCO, (gint32) 1);
+ dmap_structure_add (avdb, DMAP_CC_MRCO, (gint32) 1);
+ mlcl = dmap_structure_add (avdb, DMAP_CC_MLCL);
+ mlit = dmap_structure_add (mlcl, DMAP_CC_MLIT);
+ dmap_structure_add (mlit, DMAP_CC_MIID, (gint32) 1);
+ dmap_structure_add (mlit, DMAP_CC_MPER, (gint64) 1);
+ dmap_structure_add (mlit, DMAP_CC_MINM, share->priv->name);
+ dmap_structure_add (mlit, DMAP_CC_MIMC,
+ dmap_db_count (share->priv->db));
+ dmap_structure_add (mlit, DMAP_CC_MCTC, (gint32) 1);
-static void
-_set_property (GObject * object,
- guint prop_id,
- const GValue * value, GParamSpec * pspec)
-{
- DmapShare *share = DMAP_SHARE (object);
+ dmap_share_message_set_from_dmap_structure (share, message,
+ avdb);
+ dmap_structure_destroy (avdb);
+ } else if (g_ascii_strcasecmp ("/1/groups", rest_of_path) == 0) {
+ /* ADBS database songs
+ * MSTT status
+ * MUTY update type
+ * MTCO specified total count
+ * MRCO returned count
+ * MLCL listing
+ * MLIT
+ * attrs
+ * MLIT
+ * ...
+ */
- switch (prop_id) {
- case PROP_NAME:
- _set_name (share, g_value_get_string (value));
- break;
- case PROP_PASSWORD:
- _set_password (share, g_value_get_string (value));
- break;
- case PROP_DB:
- if (share->priv->db) {
- g_object_unref(share->priv->db);
- }
- share->priv->db = g_value_dup_object (value);
- break;
- case PROP_CONTAINER_DB:
- if (share->priv->container_db) {
- g_object_unref(share->priv->container_db);
+ GSList *filter_def;
+ gchar *record_query;
+ GHashTable *records = NULL;
+ GHashTable *groups;
+ GList *values;
+ GList *value;
+ gchar *sort_by;
+ GroupInfo *group_info;
+ GNode *agal;
+ GNode *mlcl;
+ GNode *mlit;
+ gint num;
+
+ if (g_strcmp0
+ (g_hash_table_lookup (query, "group-type"),
+ "albums") != 0) {
+ g_warning ("Unsupported grouping");
+ soup_message_set_status (message,
+ SOUP_STATUS_INTERNAL_SERVER_ERROR);
+ goto done;
}
- share->priv->container_db = g_value_dup_object (value);
- break;
- case PROP_TRANSCODE_MIMETYPE:
- g_free(share->priv->transcode_mimetype);
- share->priv->transcode_mimetype = g_value_dup_string (value);
- break;
- case PROP_TXT_RECORDS:
- g_strfreev (share->priv->txt_records);
- share->priv->txt_records = g_value_dup_boxed (value);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- break;
- }
-}
-static void
-_get_property (GObject * object,
- guint prop_id, GValue * value, GParamSpec * pspec)
-{
- DmapShare *share = DMAP_SHARE (object);
+ record_query = g_hash_table_lookup (query, "query");
+ filter_def = dmap_share_build_filter (record_query);
+ records =
+ dmap_db_apply_filter (DMAP_DB (share->priv->db),
+ filter_def);
- switch (prop_id) {
- case PROP_SERVER:
- g_value_set_object (value, share->priv->server);
- return;
- case PROP_NAME:
- g_value_set_string (value, share->priv->name);
- break;
- case PROP_PASSWORD:
- g_value_set_string (value, share->priv->password);
- break;
- case PROP_REVISION_NUMBER:
- g_value_set_uint (value,
- dmap_share_get_revision_number
- (DMAP_SHARE (object)));
- break;
- case PROP_AUTH_METHOD:
- g_value_set_uint (value,
- dmap_share_get_auth_method
- (DMAP_SHARE (object)));
- break;
- case PROP_DB:
- g_value_set_object (value, share->priv->db);
- break;
- case PROP_CONTAINER_DB:
- g_value_set_object (value, share->priv->container_db);
- break;
- case PROP_TRANSCODE_MIMETYPE:
- g_value_set_string (value, share->priv->transcode_mimetype);
- break;
- case PROP_TXT_RECORDS:
- g_value_set_boxed (value, share->priv->txt_records);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- break;
- }
-}
+ groups = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_free);
+ g_hash_table_foreach (records, (GHFunc) _group_items, groups);
-static void
-_dispose (GObject * object)
-{
- DmapShare *share = DMAP_SHARE (object);
+ agal = dmap_structure_add (NULL, DMAP_CC_AGAL);
+ dmap_structure_add (agal, DMAP_CC_MSTT,
+ (gint32) SOUP_STATUS_OK);
+ dmap_structure_add (agal, DMAP_CC_MUTY, 0);
- if (share->priv->published) {
- _publish_stop (share);
- }
+ num = g_hash_table_size (groups);
+ dmap_structure_add (agal, DMAP_CC_MTCO, (gint32) num);
+ dmap_structure_add (agal, DMAP_CC_MRCO, (gint32) num);
- if (share->priv->server_active) {
- _server_stop (share);
- }
+ mlcl = dmap_structure_add (agal, DMAP_CC_MLCL);
- g_clear_object (&share->priv->publisher);
- g_clear_object (&share->priv->server);
- g_clear_object (&share->priv->db);
- g_clear_object (&share->priv->container_db);
+ values = g_hash_table_get_values (groups);
+ if (g_hash_table_lookup (query, "include-sort-headers")) {
+ sort_by = g_hash_table_lookup (query, "sort");
+ if (g_strcmp0 (sort_by, "album") == 0) {
+ values = g_list_sort (values, _group_info_cmp);
+ } else {
+ g_warning ("Unknown sort column: %s",
+ sort_by);
+ }
+ }
- G_OBJECT_CLASS (dmap_share_parent_class)->dispose (object);
-}
+ for (value = values; value; value = g_list_next (value)) {
+ group_info = (GroupInfo *) value->data;
+ mlit = dmap_structure_add (mlcl, DMAP_CC_MLIT);
+ dmap_structure_add (mlit, DMAP_CC_MIID,
+ (gint) group_info->group_id);
+ dmap_structure_add (mlit, DMAP_CC_MPER,
+ group_info->group_id);
+ dmap_structure_add (mlit, DMAP_CC_MINM,
+ group_info->name);
+ dmap_structure_add (mlit, DMAP_CC_ASAA,
+ group_info->artist);
+ dmap_structure_add (mlit, DMAP_CC_MIMC,
+ (gint32) group_info->count);
-static void
-_finalize (GObject * object)
-{
- DmapShare *share = DMAP_SHARE (object);
+ // Free this now, since the hash free func won't.
+ // Name will be freed when the hash table keys are freed.
+ g_free (group_info->artist);
+ }
- g_debug ("Finalizing DmapShare");
+ g_list_free (values);
+ dmap_share_free_filter (filter_def);
- g_hash_table_destroy (share->priv->session_ids);
- share->priv->session_ids = NULL;
+ dmap_share_message_set_from_dmap_structure (share, message,
+ agal);
- g_free (share->priv->name);
- g_free (share->priv->password);
- g_free (share->priv->transcode_mimetype);
- g_strfreev (share->priv->txt_records);
+ g_hash_table_destroy (records);
+ g_hash_table_destroy (groups);
+ dmap_structure_destroy (agal);
+ } else if (g_ascii_strcasecmp ("/1/items", rest_of_path) == 0) {
+ /* ADBS database songs
+ * MSTT status
+ * MUTY update type
+ * MTCO specified total count
+ * MRCO returned count
+ * MLCL listing
+ * MLIT
+ * attrs
+ * MLIT
+ * ...
+ */
+ GNode *adbs;
+ gchar *record_query;
+ GHashTable *records = NULL;
+ struct DmapMetaDataMap *map;
+ gint32 num_songs;
+ struct DmapMlclBits mb = { NULL, 0, NULL };
+ struct share_bitwise_t *share_bitwise;
- G_OBJECT_CLASS (dmap_share_parent_class)->finalize (object);
-}
+ record_query = g_hash_table_lookup (query, "query");
+ if (record_query) {
+ GSList *filter_def;
-static void
-dmap_share_class_init (DmapShareClass * klass)
-{
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ filter_def = dmap_share_build_filter (record_query);
+ records =
+ dmap_db_apply_filter (DMAP_DB
+ (share->priv->db),
+ filter_def);
+ num_songs = g_hash_table_size (records);
+ g_debug ("Found %d records", num_songs);
+ dmap_share_free_filter (filter_def);
+ } else {
+ num_songs = dmap_db_count (share->priv->db);
+ }
- object_class->get_property = _get_property;
- object_class->set_property = _set_property;
- object_class->dispose = _dispose;
- object_class->finalize = _finalize;
+ map = DMAP_SHARE_GET_CLASS (share)->get_meta_data_map (share);
+ mb.bits = _parse_meta (query, map);
+ mb.share = share;
- /* Pure virtual methods: */
- klass->get_desired_port = NULL;
- klass->get_type_of_service = NULL;
- klass->message_add_standard_headers = NULL;
- klass->get_meta_data_map = NULL;
- klass->add_entry_to_mlcl = NULL;
- klass->databases_browse_xxx = NULL;
- klass->databases_items_xxx = NULL;
+ /* NOTE:
+ * We previously simply called foreach...add_entry_to_mlcl and later serialized the entire
+ * structure. This has the disadvantage that the entire response must be in memory before
+ * libsoup sends it to the client.
+ *
+ * Now, we go through the database in multiple passes (as an interim solution):
+ *
+ * 1. Accumulate the eventual size of the MLCL by creating and then free'ing each MLIT.
+ * 2. Generate the DAAP preamble ending with the MLCL (with size fudged for ADBS and MLCL).
+ * 3. Setup libsoup response headers, etc.
+ * 4. Setup callback to transmit DAAP preamble (_write_dmap_preamble)
+ * 5. Setup callback to transmit MLIT's (_write_next_mlit)
+ */
- /* Virtual methods: */
- klass->content_codes = dmap_share_content_codes;
- klass->login = dmap_share_login;
- klass->logout = dmap_share_logout;
- klass->update = dmap_share_update;
- klass->published = _published;
- klass->name_collision = _name_collision;
- klass->databases = dmap_share_databases;
- klass->ctrl_int = dmap_share_ctrl_int;
+ /* 1: */
+ share_bitwise = g_new0 (struct share_bitwise_t, 1);
- g_object_class_install_property (object_class,
- PROP_SERVER,
- g_param_spec_object ("server",
- "Soup Server",
- "Soup server",
- SOUP_TYPE_SERVER,
- G_PARAM_READABLE));
+ share_bitwise->share = share;
+ share_bitwise->mb = mb;
+ share_bitwise->id_list = NULL;
+ share_bitwise->size = 0;
+ if (record_query) {
+ share_bitwise->db = records;
+ share_bitwise->lookup_by_id = (ShareBitwiseLookupByIdFunc)
+ _lookup_adapter;
+ share_bitwise->destroy = (ShareBitwiseDestroyFunc) g_hash_table_destroy;
+ g_hash_table_foreach (records,
+ (GHFunc) _accumulate_mlcl_size_and_ids_adapter,
+ share_bitwise);
+ } else {
+ share_bitwise->db = share->priv->db;
+ share_bitwise->lookup_by_id = (ShareBitwiseLookupByIdFunc) dmap_db_lookup_by_id;
+ share_bitwise->destroy = NULL;
+ dmap_db_foreach (share->priv->db,
+ (DmapIdRecordFunc) _accumulate_mlcl_size_and_ids,
+ share_bitwise);
+ }
- g_object_class_install_property (object_class,
- PROP_NAME,
- g_param_spec_string ("name",
- "Name",
- "Share Name",
- NULL,
- G_PARAM_READWRITE));
- g_object_class_install_property (object_class,
- PROP_PASSWORD,
- g_param_spec_string ("password",
- "Authentication password",
- "Authentication password",
- NULL,
- G_PARAM_READWRITE));
+ /* 2: */
+ adbs = dmap_structure_add (NULL, DMAP_CC_ADBS);
+ dmap_structure_add (adbs, DMAP_CC_MSTT,
+ (gint32) SOUP_STATUS_OK);
+ dmap_structure_add (adbs, DMAP_CC_MUTY, 0);
+ dmap_structure_add (adbs, DMAP_CC_MTCO, (gint32) num_songs);
+ dmap_structure_add (adbs, DMAP_CC_MRCO, (gint32) num_songs);
+ mb.mlcl = dmap_structure_add (adbs, DMAP_CC_MLCL);
+ dmap_structure_increase_by_predicted_size (adbs,
+ share_bitwise->
+ size);
+ dmap_structure_increase_by_predicted_size (mb.mlcl,
+ share_bitwise->
+ size);
- g_object_class_install_property (object_class,
- PROP_REVISION_NUMBER,
- g_param_spec_uint ("revision-number",
- "Revision number",
- "Revision number",
- 0,
- G_MAXINT,
- 0,
- G_PARAM_READWRITE));
+ /* 3: */
+ /* Free memory after each chunk sent out over network. */
+ soup_message_body_set_accumulate (message->response_body,
+ FALSE);
+ soup_message_headers_append (message->response_headers,
+ "Content-Type",
+ "application/x-dmap-tagged");
+ DMAP_SHARE_GET_CLASS (share)->
+ message_add_standard_headers (share, message);
+ soup_message_headers_set_content_length (message->
+ response_headers,
+ dmap_structure_get_size
+ (adbs));
+ soup_message_set_status (message, SOUP_STATUS_OK);
- g_object_class_install_property (object_class,
- PROP_AUTH_METHOD,
- g_param_spec_uint ("auth-method",
- "Authentication method",
- "Authentication method",
- DMAP_SHARE_AUTH_METHOD_NONE,
- DMAP_SHARE_AUTH_METHOD_PASSWORD,
- 0,
- G_PARAM_READWRITE));
- g_object_class_install_property (object_class,
- PROP_DB,
- g_param_spec_object ("db",
- "DB",
- "DB object",
- DMAP_TYPE_DB,
- G_PARAM_READWRITE |
- G_PARAM_CONSTRUCT_ONLY));
+ /* 4: */
+ g_signal_connect (message, "wrote_headers",
+ G_CALLBACK (_write_dmap_preamble), adbs);
- g_object_class_install_property (object_class,
- PROP_CONTAINER_DB,
- g_param_spec_object ("container-db",
- "Container DB",
- "Container DB object",
- DMAP_TYPE_CONTAINER_DB,
- G_PARAM_READWRITE |
- G_PARAM_CONSTRUCT_ONLY));
+ /* 5: */
+ g_signal_connect (message, "wrote_chunk",
+ G_CALLBACK (_write_next_mlit),
+ share_bitwise);
+ g_signal_connect (message, "finished",
+ G_CALLBACK (_chunked_message_finished),
+ share_bitwise);
- g_object_class_install_property (object_class,
- PROP_TRANSCODE_MIMETYPE,
- g_param_spec_string
- ("transcode-mimetype",
- "Transcode mimetype",
- "Set mimetype of stream after transcoding",
- NULL,
- G_PARAM_READWRITE |
- G_PARAM_CONSTRUCT_ONLY));
+ } else if (g_ascii_strcasecmp ("/1/containers", rest_of_path) == 0) {
+ /* APLY database playlists
+ * MSTT status
+ * MUTY update type
+ * MTCO specified total count
+ * MRCO returned count
+ * MLCL listing
+ * MLIT listing item
+ * MIID item id
+ * MPER persistent item id
+ * MINM item name
+ * MIMC item count
+ * ABPL baseplaylist (only for base)
+ * MLIT
+ * ...
+ */
+ GNode *aply;
+ GNode *mlit;
+ struct DmapMetaDataMap *map;
+ struct DmapMlclBits mb = { NULL, 0, NULL };
- g_object_class_install_property (object_class,
- PROP_TXT_RECORDS,
- g_param_spec_boxed ("txt-records",
- "TXT-Records",
- "Set TXT-Records used for MDNS publishing",
- G_TYPE_STRV,
- G_PARAM_READWRITE));
+ map = DMAP_SHARE_GET_CLASS (share)->get_meta_data_map (share);
+ mb.bits = _parse_meta (query, map);
+ mb.share = share;
- _signals[ERROR] =
- g_signal_new ("error",
- G_TYPE_FROM_CLASS (object_class),
- G_SIGNAL_RUN_FIRST,
- 0, NULL, NULL,
- g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1,
- G_TYPE_POINTER);
-}
+ aply = dmap_structure_add (NULL, DMAP_CC_APLY);
+ dmap_structure_add (aply, DMAP_CC_MSTT,
+ (gint32) SOUP_STATUS_OK);
+ dmap_structure_add (aply, DMAP_CC_MUTY, 0);
+ dmap_structure_add (aply, DMAP_CC_MTCO,
+ (gint32) dmap_container_db_count (share->
+ priv->
+ container_db)
+ + 1);
+ dmap_structure_add (aply, DMAP_CC_MRCO,
+ (gint32) dmap_container_db_count (share->
+ priv->
+ container_db)
+ + 1);
+ mb.mlcl = dmap_structure_add (aply, DMAP_CC_MLCL);
-static void
-dmap_share_init (DmapShare * share)
-{
- share->priv = DMAP_SHARE_GET_PRIVATE (share);
+ /* Base playlist (playlist 1 contains all songs): */
+ mlit = dmap_structure_add (mb.mlcl, DMAP_CC_MLIT);
+ dmap_structure_add (mlit, DMAP_CC_MIID, (gint32) 1);
+ dmap_structure_add (mlit, DMAP_CC_MPER, (gint64) 1);
+ dmap_structure_add (mlit, DMAP_CC_MINM, share->priv->name);
+ dmap_structure_add (mlit, DMAP_CC_MIMC,
+ dmap_db_count (share->priv->db));
+ dmap_structure_add (mlit, DMAP_CC_FQUESCH, 0);
+ dmap_structure_add (mlit, DMAP_CC_MPCO, 0);
+ dmap_structure_add (mlit, DMAP_CC_AESP, 0);
+ dmap_structure_add (mlit, DMAP_CC_AEPP, 0);
+ dmap_structure_add (mlit, DMAP_CC_AEPS, 0);
+ dmap_structure_add (mlit, DMAP_CC_AESG, 0);
- share->priv->revision_number = 5;
- share->priv->auth_method = DMAP_SHARE_AUTH_METHOD_NONE;
- share->priv->publisher = dmap_mdns_publisher_new ();
- share->priv->server = soup_server_new (NULL, NULL);
+ dmap_structure_add (mlit, DMAP_CC_ABPL, (gchar) 1);
- /* using direct since there is no g_uint_hash or g_uint_equal */
- share->priv->session_ids =
- g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
- g_free);
+ dmap_container_db_foreach (share->priv->container_db,
+ (DmapIdContainerRecordFunc)
+ _add_playlist_to_mlcl,
+ &mb);
- g_signal_connect_object (share->priv->publisher,
- "published",
- G_CALLBACK (_published_adapter), share, 0);
- g_signal_connect_object (share->priv->publisher,
- "name-collision",
- G_CALLBACK (_name_collision_adapter),
- share, 0);
-}
+ dmap_share_message_set_from_dmap_structure (share, message,
+ aply);
+ dmap_structure_destroy (aply);
+ } else if (g_ascii_strncasecmp ("/1/containers/", rest_of_path, 14) ==
+ 0) {
+ /* APSO playlist songs
+ * MSTT status
+ * MUTY update type
+ * MTCO specified total count
+ * MRCO returned count
+ * MLCL listing
+ * MLIT listing item
+ * MIKD item kind
+ * MIID item id
+ * MCTI container item id
+ * MLIT
+ * ...
+ */
+ GNode *apso;
+ struct DmapMetaDataMap *map;
+ struct DmapMlclBits mb = { NULL, 0, NULL };
+ guint pl_id;
+ gchar *record_query;
+ GSList *filter_def;
+ GHashTable *records;
-guint
-dmap_share_get_auth_method (DmapShare * share)
-{
- return share->priv->auth_method;
-}
+ map = DMAP_SHARE_GET_CLASS (share)->get_meta_data_map (share);
+ mb.bits = _parse_meta (query, map);
+ mb.share = share;
-guint
-dmap_share_get_revision_number (DmapShare * share)
-{
- return share->priv->revision_number;
-}
+ apso = dmap_structure_add (NULL, DMAP_CC_APSO);
+ dmap_structure_add (apso, DMAP_CC_MSTT,
+ (gint32) SOUP_STATUS_OK);
+ dmap_structure_add (apso, DMAP_CC_MUTY, 0);
-static gboolean
-_get_session_id (GHashTable * query, guint32 * id)
-{
- gboolean ok = FALSE;
- char *session_id_str;
- guint32 session_id;
+ if (g_ascii_strcasecmp ("/1/items", rest_of_path + 13) == 0) {
+ GList *id;
+ gchar *sort_by;
+ GList *keys;
- session_id_str = g_hash_table_lookup (query, "session-id");
- if (session_id_str == NULL) {
- g_warning ("Session id not found.");
- goto done;
- }
+ record_query = g_hash_table_lookup (query, "query");
+ filter_def = dmap_share_build_filter (record_query);
+ records =
+ dmap_db_apply_filter (DMAP_DB
+ (share->priv->db),
+ filter_def);
+ gint32 num_songs = g_hash_table_size (records);
- session_id = (guint32) strtoul (session_id_str, NULL, 10);
- if (id != NULL) {
- *id = session_id;
- }
+ g_debug ("Found %d records", num_songs);
+ dmap_share_free_filter (filter_def);
- ok = TRUE;
+ dmap_structure_add (apso, DMAP_CC_MTCO,
+ (gint32) num_songs);
+ dmap_structure_add (apso, DMAP_CC_MRCO,
+ (gint32) num_songs);
+ mb.mlcl = dmap_structure_add (apso, DMAP_CC_MLCL);
-done:
- return ok;
-}
+ sort_by = g_hash_table_lookup (query, "sort");
+ keys = g_hash_table_get_keys (records);
+ if (g_strcmp0 (sort_by, "album") == 0) {
+ keys = g_list_sort_with_data (keys,
+ (GCompareDataFunc)
+ dmap_av_record_cmp_by_album,
+ share->priv->
+ db);
+ } else if (sort_by != NULL) {
+ g_warning ("Unknown sort column: %s",
+ sort_by);
+ }
-gboolean
-dmap_share_get_revision_number_from_query (GHashTable * query,
- guint * number)
-{
- gboolean ok = FALSE;
- char *revision_number_str;
- guint revision_number;
+ for (id = keys; id; id = id->next) {
+ (*
+ (DMAP_SHARE_GET_CLASS (share)->
+ add_entry_to_mlcl)) (GPOINTER_TO_UINT(id->data),
+ g_hash_table_lookup
+ (records, id->data),
+ &mb);
+ }
- revision_number_str = g_hash_table_lookup (query, "revision-number");
- if (revision_number_str == NULL) {
- g_warning
- ("Client asked for an update without a rev. number");
- goto done;
- }
+ g_list_free (keys);
+ g_hash_table_destroy (records);
+ } else {
+ pl_id = strtoul (rest_of_path + 14, NULL, 10);
+ if (pl_id == 1) {
+ gint32 num_songs =
+ dmap_db_count (share->priv->db);
+ dmap_structure_add (apso, DMAP_CC_MTCO,
+ (gint32) num_songs);
+ dmap_structure_add (apso, DMAP_CC_MRCO,
+ (gint32) num_songs);
+ mb.mlcl =
+ dmap_structure_add (apso,
+ DMAP_CC_MLCL);
- revision_number = strtoul (revision_number_str, NULL, 10);
- if (number != NULL) {
- *number = revision_number;
- }
+ dmap_db_foreach (share->priv->db,
+ DMAP_SHARE_GET_CLASS
+ (share)->add_entry_to_mlcl,
+ &mb);
+ } else {
+ DmapContainerRecord *record;
+ DmapDb *entries;
+ guint num_songs;
- ok = TRUE;
+ record = dmap_container_db_lookup_by_id
+ (share->priv->container_db, pl_id);
+ entries =
+ dmap_container_record_get_entries
+ (record);
+ /* FIXME: what if entries is NULL (handled in dmapd but should be [also]
handled here)? */
+ num_songs = dmap_db_count (entries);
+
+ dmap_structure_add (apso, DMAP_CC_MTCO,
+ (gint32) num_songs);
+ dmap_structure_add (apso, DMAP_CC_MRCO,
+ (gint32) num_songs);
+ mb.mlcl =
+ dmap_structure_add (apso,
+ DMAP_CC_MLCL);
+
+ dmap_db_foreach (entries,
+ DMAP_SHARE_GET_CLASS
+ (share)->add_entry_to_mlcl,
+ &mb);
+
+ g_object_unref (entries);
+ g_object_unref (record);
+ }
+ }
+
+ dmap_share_message_set_from_dmap_structure (share, message,
+ apso);
+ dmap_structure_destroy (apso);
+ } else if (g_ascii_strncasecmp ("/1/browse/", rest_of_path, 9) == 0) {
+ DMAP_SHARE_GET_CLASS (share)->databases_browse_xxx (share,
+ message,
+ path,
+ query);
+ } else if (g_ascii_strncasecmp ("/1/items/", rest_of_path, 9) == 0) {
+ /* just the file :) */
+ DMAP_SHARE_GET_CLASS (share)->databases_items_xxx (share,
+ server,
+ message,
+ path);
+ } else if (g_str_has_prefix (rest_of_path, "/1/groups/") &&
+ g_str_has_suffix (rest_of_path, "/extra_data/artwork")) {
+ /* We don't yet implement cover requests here, say no cover */
+ g_debug ("Assuming no artwork for requested group/album");
+ soup_message_set_status (message, SOUP_STATUS_NOT_FOUND);
+ } else {
+ g_warning ("Unhandled: %s", path);
+ }
done:
- return ok;
+ return;
}
-gboolean
-dmap_share_session_id_validate (DmapShare * share,
- SoupClientContext * context,
- GHashTable * query, guint32 * id)
+static void
+_databases_adapter (SoupServer * server,
+ SoupMessage * message,
+ const char *path,
+ GHashTable * query,
+ SoupClientContext * context,
+ DmapShare * share)
{
- gboolean ok = FALSE;
- guint32 session_id;
- gboolean res;
- const char *addr;
- const char *remote_address;
+ DMAP_SHARE_GET_CLASS (share)->databases (share,
+ server,
+ message,
+ path, query, context);
+}
- if (id) {
- *id = 0;
+static void
+_ctrl_int (G_GNUC_UNUSED DmapShare * share,
+ G_GNUC_UNUSED SoupServer * server,
+ G_GNUC_UNUSED SoupMessage * message,
+ const char *path,
+ GHashTable * query,
+ G_GNUC_UNUSED SoupClientContext * context)
+{
+ g_debug ("Path is %s.", path);
+ if (query) {
+ g_hash_table_foreach (query, _debug_param, NULL);
}
- res = _get_session_id (query, &session_id);
- if (!res) {
- g_warning ("Validation failed: Unable to parse session id");
- goto done;
- }
+ g_debug ("ctrl-int not implemented");
+}
- /* check hash for remote address */
- addr = g_hash_table_lookup (share->priv->session_ids,
- GUINT_TO_POINTER (session_id));
- if (addr == NULL) {
- g_warning
- ("Validation failed: Unable to lookup session id %u",
- session_id);
- goto done;
- }
-
- remote_address = soup_client_context_get_host (context);
- g_debug ("Validating session id %u from %s matches %s",
- session_id, remote_address, addr);
- if (remote_address == NULL || strcmp (addr, remote_address) != 0) {
- g_warning
- ("Validation failed: Remote address does not match stored address");
- goto done;
- }
-
- if (id) {
- *id = session_id;
- }
-
- ok = TRUE;
-
-done:
- return ok;
+static void
+_ctrl_int_adapter (SoupServer * server,
+ SoupMessage * message,
+ const char *path,
+ GHashTable * query,
+ SoupClientContext * context,
+ DmapShare * share)
+{
+ DMAP_SHARE_GET_CLASS (share)->ctrl_int (share,
+ server,
+ message,
+ path, query, context);
}
-static guint32
-_session_id_generate (void)
+static void
+_set_name (DmapShare * share, const char *name)
{
- guint32 id;
+ GError *error;
- id = g_random_int ();
+ g_return_if_fail (share != NULL);
- return id;
+ g_free (share->priv->name);
+ share->priv->name = g_strdup (name);
+
+ if (share->priv->published) {
+ error = NULL;
+ dmap_mdns_publisher_rename_at_port (share->priv->
+ publisher,
+ share->priv->port,
+ name,
+ &error);
+ if (error != NULL) {
+ g_warning ("Unable to change MDNS service name: %s",
+ error->message);
+ g_error_free (error);
+ }
+ }
}
-guint32
-dmap_share_session_id_create (DmapShare * share, SoupClientContext * context)
+static void
+_published (DmapShare * share,
+ G_GNUC_UNUSED DmapMdnsPublisher * publisher,
+ const char *name)
{
- guint32 id;
- const char *addr;
- char *remote_address;
-
- do {
- /* create a unique session id */
- id = _session_id_generate ();
- g_debug ("Generated session id %u", id);
-
- /* if already used, try again */
- addr = g_hash_table_lookup (share->priv->session_ids,
- GUINT_TO_POINTER (id));
- } while (addr != NULL);
-
- /* store session id and remote address */
- /* FIXME, warning, this fails against libsoup-2.33.90-1.fc15.x86_64 with:
- * (dmapd:12917): libsoup-CRITICAL **: soup_address_get_physical: assertion `SOUP_IS_ADDRESS (addr)'
failed
- * Is this a bug in libsoup or libdmapsharing?
- */
- remote_address = g_strdup (soup_client_context_get_host (context));
- g_hash_table_insert (share->priv->session_ids, GUINT_TO_POINTER (id),
- remote_address);
+ if (share->priv->name == NULL || name == NULL) {
+ return;
+ }
- return id;
+ if (strcmp (share->priv->name, name) == 0) {
+ g_debug ("mDNS publish successful");
+ share->priv->published = TRUE;
+ }
}
-void
-dmap_share_session_id_remove (DmapShare * share,
- guint32 id)
+static void
+_published_adapter (DmapMdnsPublisher * publisher,
+ const char *name,
+ DmapShare * share)
{
- g_hash_table_remove (share->priv->session_ids, GUINT_TO_POINTER (id));
+ DMAP_SHARE_GET_CLASS (share)->published (share, publisher, name);
}
-void
-dmap_share_message_set_from_dmap_structure (DmapShare * share,
- SoupMessage * message,
- GNode * structure)
+static void
+_name_collision (DmapShare * share,
+ G_GNUC_UNUSED DmapMdnsPublisher * publisher,
+ const char *name)
{
- gchar *resp;
- guint length;
-
- resp = dmap_structure_serialize (structure, &length);
-
- if (resp == NULL) {
- g_warning ("Serialize gave us null?");
- return;
- }
-
- soup_message_set_response (message, "application/x-dmap-tagged",
- SOUP_MEMORY_TAKE, resp, length);
+ g_assert(NULL != name);
+ g_assert(NULL != share->priv->name);
- DMAP_SHARE_GET_CLASS (share)->message_add_standard_headers (share,
- message);
+ g_warning ("Duplicate share name on mDNS; renaming share to %s", name);
- soup_message_set_status (message, SOUP_STATUS_OK);
-}
+ _set_name (DMAP_SHARE (share), name);
-gboolean
-dmap_share_client_requested (DmapBits bits, gint field)
-{
- return 0 != (bits & (((DmapBits) 1) << field));
+ return;
}
-gboolean
-dmap_share_uri_is_local (const char *text_uri)
+static void
+_name_collision_adapter (DmapMdnsPublisher * publisher,
+ const char *name, DmapShare * share)
{
- return g_str_has_prefix (text_uri, "file://");
+ DMAP_SHARE_GET_CLASS (share)->name_collision (share, publisher, name);
}
-gboolean
-dmap_share_soup_auth_filter (G_GNUC_UNUSED SoupAuthDomain * auth_domain,
- SoupMessage * msg, G_GNUC_UNUSED gpointer user_data)
+static gboolean
+_soup_auth_filter (G_GNUC_UNUSED SoupAuthDomain * auth_domain,
+ SoupMessage * msg, G_GNUC_UNUSED gpointer user_data)
{
gboolean ok = FALSE;
const char *path;
@@ -996,1139 +1229,905 @@ done:
return ok;
}
-void
-dmap_share_content_codes (DmapShare * share,
- SoupMessage * message,
- const char *path,
- G_GNUC_UNUSED SoupClientContext * context)
+gboolean
+dmap_share_serve (DmapShare *share, GError **error)
{
-/* MCCR content codes response
- * MSTT status
- * MDCL dictionary
- * MCNM content codes number
- * MCNA content codes name
- * MCTY content codes type
- * MDCL dictionary
- * ...
- */
- const DmapContentCodeDefinition *defs;
- guint num_defs = 0;
- guint i;
- GNode *mccr;
-
- g_debug ("Path is %s.", path);
-
- defs = dmap_structure_content_codes (&num_defs);
+ guint desired_port = DMAP_SHARE_GET_CLASS (share)->get_desired_port (share);
+ gboolean password_required, ok = FALSE;
+ GSList *listening_uri_list;
+ SoupURI *listening_uri;
+ gboolean ret;
+ GError *error2 = NULL;
- mccr = dmap_structure_add (NULL, DMAP_CC_MCCR);
- dmap_structure_add (mccr, DMAP_CC_MSTT, (gint32) SOUP_STATUS_OK);
+ password_required = (share->priv->auth_method != DMAP_SHARE_AUTH_METHOD_NONE);
- for (i = 0; i < num_defs; i++) {
- GNode *mdcl;
+ if (password_required) {
+ SoupAuthDomain *auth_domain;
- mdcl = dmap_structure_add (mccr, DMAP_CC_MDCL);
- dmap_structure_add (mdcl, DMAP_CC_MCNM,
- dmap_structure_cc_string_as_int32 (defs[i].string));
- dmap_structure_add (mdcl, DMAP_CC_MCNA, defs[i].name);
- dmap_structure_add (mdcl, DMAP_CC_MCTY,
- (gint32) defs[i].type);
+ auth_domain =
+ soup_auth_domain_basic_new (SOUP_AUTH_DOMAIN_REALM,
+ "Music Sharing",
+ SOUP_AUTH_DOMAIN_ADD_PATH,
+ "/login",
+ SOUP_AUTH_DOMAIN_ADD_PATH,
+ "/update",
+ SOUP_AUTH_DOMAIN_ADD_PATH,
+ "/database",
+ SOUP_AUTH_DOMAIN_FILTER,
+ _soup_auth_filter,
+ NULL);
+ soup_auth_domain_basic_set_auth_callback (auth_domain,
+ (SoupAuthDomainBasicAuthCallback)
+ _soup_auth_callback,
+ g_object_ref
+ (share),
+ g_object_unref);
+ soup_server_add_auth_domain (share->priv->server, auth_domain);
}
- dmap_share_message_set_from_dmap_structure (share, message, mccr);
- dmap_structure_destroy (mccr);
-}
-
-void
-dmap_share_login (DmapShare * share,
- SoupMessage * message,
- const char *path,
- G_GNUC_UNUSED GHashTable * query,
- SoupClientContext * context)
-{
-/* MLOG login response
- * MSTT status
- * MLID session id
- */
- GNode *mlog;
- guint32 session_id;
+ soup_server_add_handler (share->priv->server, "/server-info",
+ (SoupServerCallback) _server_info_adapter,
+ share, NULL);
+ soup_server_add_handler (share->priv->server, "/content-codes",
+ (SoupServerCallback) _content_codes_adapter,
+ share, NULL);
+ soup_server_add_handler (share->priv->server, "/login",
+ (SoupServerCallback) _login_adapter,
+ share, NULL);
+ soup_server_add_handler (share->priv->server, "/logout",
+ (SoupServerCallback) _logout_adapter,
+ share, NULL);
+ soup_server_add_handler (share->priv->server, "/update",
+ (SoupServerCallback) _update_adapter,
+ share, NULL);
+ soup_server_add_handler (share->priv->server, "/databases",
+ (SoupServerCallback) _databases_adapter,
+ share, NULL);
+ soup_server_add_handler (share->priv->server, "/ctrl-int",
+ (SoupServerCallback) _ctrl_int_adapter,
+ share, NULL);
- g_debug ("Path is %s.", path);
+ ret = soup_server_listen_all (share->priv->server, desired_port, 0, &error2);
+ if (ret == FALSE) {
+ g_debug ("Unable to start music sharing server on port %d: %s. "
+ "Trying any open IPv6 port", desired_port, error2->message);
+ g_error_free(error2);
- session_id = dmap_share_session_id_create (share, context);
+ ret = soup_server_listen_all (share->priv->server, SOUP_ADDRESS_ANY_PORT,
+ 0, error);
+ }
- mlog = dmap_structure_add (NULL, DMAP_CC_MLOG);
- dmap_structure_add (mlog, DMAP_CC_MSTT, (gint32) SOUP_STATUS_OK);
- dmap_structure_add (mlog, DMAP_CC_MLID, session_id);
+ listening_uri_list = soup_server_get_uris (share->priv->server);
+ if (ret == FALSE || listening_uri_list == NULL) {
+ goto done;
+ }
- dmap_share_message_set_from_dmap_structure (share, message, mlog);
- dmap_structure_destroy (mlog);
-}
+ /* We can only expose one port, so no point checking more than one URI
+ * here. Maybe it somehow is listening on a different port for IPv4 vs.
+ * IPv6, but there's not much we can do.
+ */
+ listening_uri = listening_uri_list->data;
+ share->priv->port = soup_uri_get_port (listening_uri);
+ g_slist_free_full (listening_uri_list, (GDestroyNotify) soup_uri_free);
-void
-dmap_share_logout (DmapShare * share,
- SoupMessage * message,
- const char *path,
- GHashTable * query, SoupClientContext * context)
-{
- int status;
- guint32 id;
+ g_debug ("Started DMAP server on port %u", share->priv->port);
- g_debug ("Path is %s.", path);
+ share->priv->server_active = TRUE;
- if (dmap_share_session_id_validate
- (share, context, query, &id)) {
- dmap_share_session_id_remove (share, id);
+ ok = TRUE;
- status = SOUP_STATUS_NO_CONTENT;
- } else {
- status = SOUP_STATUS_FORBIDDEN;
- }
+done:
+ g_assert((!ok && (NULL == error || NULL != *error))
+ || ( ok && (NULL == error || NULL == *error)));
- soup_message_set_status (message, status);
+ return ok;
}
-void
-dmap_share_update (DmapShare * share,
- SoupServer * server,
- SoupMessage * message,
- const char *path,
- GHashTable * query)
+static gboolean
+_server_stop (DmapShare * share)
{
- guint revision_number;
- gboolean res;
-
- g_debug ("Path is %s.", path);
+ g_debug ("Stopping music sharing server on port %d",
+ share->priv->port);
- res = dmap_share_get_revision_number_from_query (query,
- &revision_number);
+ if (share->priv->server) {
+ soup_server_disconnect (share->priv->server);
+ }
- if (res && revision_number != dmap_share_get_revision_number (share)) {
- /* MUPD update response
- * MSTT status
- * MUSR server revision
- */
- GNode *mupd;
+ if (share->priv->session_ids) {
+ g_hash_table_remove_all (share->priv->session_ids);
+ }
- mupd = dmap_structure_add (NULL, DMAP_CC_MUPD);
- dmap_structure_add (mupd, DMAP_CC_MSTT,
- (gint32) SOUP_STATUS_OK);
- dmap_structure_add (mupd, DMAP_CC_MUSR,
- (gint32)
- dmap_share_get_revision_number (share));
+ share->priv->server_active = FALSE;
- dmap_share_message_set_from_dmap_structure (share, message,
- mupd);
- dmap_structure_destroy (mupd);
- } else {
- /* FIXME: This seems like a bug. It just leaks the
- * message (and socket) without ever replying.
- */
- g_object_ref (message);
- soup_server_pause_message (server, message);
- }
+ return TRUE;
}
-DmapBits
-dmap_share_parse_meta_str (const char *attrs, struct DmapMetaDataMap *mdm)
+gboolean
+dmap_share_publish (DmapShare *share, GError **error)
{
- guint i;
- DmapBits bits = 0;
-
- /* iTunes 8 uses meta=all for /databases/1/items query: */
- if (strcmp (attrs, "all") == 0) {
- bits = ~0;
- } else {
- gchar **attrsv;
-
- attrsv = g_strsplit (attrs, ",", -1);
+ gboolean ok;
+ gboolean password_required;
- for (i = 0; attrsv[i]; i++) {
- guint j;
- gboolean found = FALSE;
+ password_required =
+ (share->priv->auth_method != DMAP_SHARE_AUTH_METHOD_NONE);
- for (j = 0; mdm[j].tag; j++) {
- if (strcmp (mdm[j].tag, attrsv[i]) == 0) {
- bits |= (((DmapBits) 1) << mdm[j].md);
- found = TRUE;
- }
- }
+ ok = dmap_mdns_publisher_publish (share->priv->publisher,
+ share->priv->name,
+ share->priv->port,
+ DMAP_SHARE_GET_CLASS (share)->
+ get_type_of_service (share),
+ password_required,
+ share->priv->txt_records, error);
- if (found == FALSE) {
- g_debug ("Unknown meta request: %s",
- attrsv[i]);
- }
- }
- g_strfreev (attrsv);
+ if (ok == FALSE) {
+ goto done;
}
- return bits;
+ g_debug ("Published DMAP server information to mdns");
+
+done:
+ return ok;
}
-DmapBits
-dmap_share_parse_meta (GHashTable * query, struct DmapMetaDataMap * mdm)
+static gboolean
+_publish_stop (DmapShare * share)
{
- DmapBits bits = 0;
- const gchar *attrs;
+ GError *error;
+ gboolean ok = FALSE;
- attrs = g_hash_table_lookup (query, "meta");
- if (attrs == NULL) {
+ if (!share->priv->publisher) {
+ share->priv->published = FALSE;
goto done;
}
- bits = dmap_share_parse_meta_str (attrs, mdm);
+ error = NULL;
+ ok = dmap_mdns_publisher_withdraw (share->priv->publisher,
+ share->priv->port,
+ &error);
+ if (error != NULL) {
+ g_warning
+ ("Unable to withdraw music sharing service: %s",
+ error->message);
+ g_error_free (error);
+ }
done:
- return bits;
+ return ok;
}
-void
-dmap_share_add_playlist_to_mlcl (G_GNUC_UNUSED guint id,
- DmapContainerRecord * record,
- gpointer _mb)
+static void
+_restart (DmapShare * share)
{
- /* MLIT listing item
- * MIID item id
- * MPER persistent item id
- * MINM item name
- * MIMC item count
- */
- GNode *mlit;
- guint num_songs;
- gchar *name;
- struct DmapMlclBits *mb = (struct DmapMlclBits *) _mb;
-
- num_songs = dmap_container_record_get_entry_count (record);
- g_object_get (record, "name", &name, NULL);
-
- /* FIXME: ITEM_ID, etc. is defined in DmapAvShare, so I can't use
- * with dmap_share_client_requested() here (see add_entry_to_mlcl())
- */
-
- mlit = dmap_structure_add (mb->mlcl, DMAP_CC_MLIT);
- dmap_structure_add (mlit, DMAP_CC_MIID,
- dmap_container_record_get_id (record));
- /* we don't have a persistant ID for playlists, unfortunately */
- dmap_structure_add (mlit, DMAP_CC_MPER,
- (gint64) dmap_container_record_get_id (record));
- dmap_structure_add (mlit, DMAP_CC_MINM, name);
- dmap_structure_add (mlit, DMAP_CC_MIMC, (gint32) num_songs);
-
- /* FIXME: Is this getting music-specific? */
- dmap_structure_add (mlit, DMAP_CC_FQUESCH, 0);
- dmap_structure_add (mlit, DMAP_CC_MPCO, 0);
- dmap_structure_add (mlit, DMAP_CC_AESP, 0);
- dmap_structure_add (mlit, DMAP_CC_AEPP, 0);
- dmap_structure_add (mlit, DMAP_CC_AEPS, 0);
- dmap_structure_add (mlit, DMAP_CC_AESG, 0);
+ GError *error = NULL;
+ gboolean res;
- g_free (name);
+ _server_stop (share);
+ res = dmap_share_serve (share, &error);
+ if (NULL != error) {
+ g_signal_emit (share, _signals[ERROR], 0, error);
+ _publish_stop (share);
+ } else {
+ g_assert(FALSE != res);
- return;
+ /* To update information just publish again */
+ dmap_share_publish (share, NULL);
+ }
}
-GSList *
-dmap_share_build_filter (gchar * filterstr)
+static void
+_maybe_restart (DmapShare * share)
{
- /* Produces a list of lists, each being a filter definition that may
- * be one or more filter criteria.
- */
-
- /* A filter string looks like (iTunes):
- * 'daap.songgenre:Other'+'daap.songartist:Band'.
- * or (Roku):
- * 'daap.songgenre:Other' 'daap.songartist:Band'.
- * or
- * 'dmap.itemid:1000'
- * or
- * 'dmap.itemid:1000','dmap:itemid:1001'
- * or
- * 'daap.songgenre:Foo'+'daap.songartist:Bar'+'daap.songalbum:Baz'
- * or (iPhoto '09)
- * ('daap.idemid:1000','dmap:itemid:1001')
- * or (DACP):
- * ('com.apple.itunes.mediakind:1','com.apple.itunes.mediakind:32') 'daap.songartist!:'
- */
-
- gchar *next_char;
- GString *value;
-
- //gboolean save_value;
- gboolean is_key;
- gboolean is_value;
- gboolean new_group;
- gboolean accept;
- gboolean negate;
- gint parentheses_count;
- gint quotes_count;
- DmapDbFilterDefinition *def;
-
- GSList *list = NULL;
- GSList *filter = NULL;
-
- g_debug ("Filter string is %s.", filterstr);
+ if (share->priv->published) {
+ _restart (share);
+ }
+}
- if (filterstr == NULL) {
+static void
+_set_password (DmapShare * share, const char *password)
+{
+ if (share->priv->password && password &&
+ 0 == strcmp (password, share->priv->password)) {
goto done;
}
- next_char = filterstr;
+ g_free (share->priv->password);
+ share->priv->password = g_strdup (password);
+ if (password != NULL) {
+ share->priv->auth_method = DMAP_SHARE_AUTH_METHOD_PASSWORD;
+ } else {
+ share->priv->auth_method = DMAP_SHARE_AUTH_METHOD_NONE;
+ }
- parentheses_count = 0;
- quotes_count = 0;
- is_key = TRUE;
- is_value = FALSE;
- new_group = FALSE;
- negate = FALSE;
- value = NULL;
- def = NULL;
-
- // The query string is divided in groups of AND separated by a space,
- // each group containing queries of OR separated by comma enclosed in
- // parentheses. Queries are key, value pairs separated by ':' enclosed
- // in quotes or not.
- // The result from this is a list of lists. Here is an example:
- // String is ('com.apple.itunes.mediakind:1','com.apple.itunes.mediakind:32') 'daap.songartist!:'
- // list:
- // |-> filter1: -> com.apple.itunes.mediakind = 1
- // | | -> com.apple.itunes.mediakind = 32
- // |-> filter1: -> daap.songartist! = (null)
- // This basically means that the query is (filter1 AND filter2), and
- // filter1 is (com.apple.itunes.mediakind = 1) OR (com.apple.itunes.mediakind = 32)
- while (TRUE) {
- // We check each character to see if it should be included in
- // the current value (the current value will become the query key
- // or the query value). Anything that is not a special character
- // handled below will be accepted (this means weird characters
- // might appear in names).
- // Handling of unicode characters is unknown, but as long it
- // doesn't appear as a character handled below, it will be
- // included.
- // This parser will result in unknown behaviour on bad-formatted
- // queries (it does not check for syntax errors), but should not
- // crash. In this way it may accept much more syntaxes then
- // other parsers.
- accept = FALSE;
- // A slash can escape characters such as ', so add the character
- // after the slash.
- if (*next_char == '\\') {
- accept = TRUE;
- next_char++;
- } else {
- switch (*next_char) {
- case '(':
- if (is_value) {
- accept = TRUE;
- } else {
- parentheses_count++;
- }
- break;
- case ')':
- if (is_value) {
- accept = TRUE;
- } else {
- parentheses_count--;
- }
- break;
- case '\'':
- if (quotes_count > 0) {
- quotes_count = 0;
- } else {
- quotes_count = 1;
- }
- break;
- case ' ':
- if (is_value) {
- accept = TRUE;
- } else {
- new_group = TRUE;
- }
- break;
- case ':':
- // Inside values, they will be included in the
- // query string, otherwise it indicates there
- // will be a value next.
- if (is_value) {
- accept = TRUE;
- } else if (is_key) {
- is_value = TRUE;
- }
- break;
- case '!':
- if (is_value) {
- accept = TRUE;
- } else if (is_key && value) {
- negate = TRUE;
- }
- break;
- case ',':
- case '+':
- // Accept these characters only if inside quotes
- if (is_value) {
- accept = TRUE;
- }
- break;
- case '\0':
- // Never accept
- break;
- default:
- accept = TRUE;
- break;
- }
- }
- //g_debug ("Char: %c, Accept: %s", *next_char, accept?"TRUE":"FALSE");
- //Is the next character to be accepted?
- if (accept) {
- if (!value) {
- value = g_string_new ("");
- }
- g_string_append_c (value, *next_char);
- } else if (value != NULL && *next_char != '!') {
- // If we won't accept this character, we are ending a
- // query key or value, so we should save them in a new
- // DmapDbFilterDefinition. If is_value is TRUE, we will still
- // parse the query value, so we must keep our def around.
- // Otherwise, save it in the list of filters.
- if (!def) {
- def = g_new0 (DmapDbFilterDefinition, 1);
- }
- if (is_key) {
- def->key = value->str;
- g_string_free (value, FALSE);
- def->negate = negate;
- negate = FALSE;
- is_key = FALSE;
- } else {
- def->value = value->str;
- g_string_free (value, FALSE);
- is_value = FALSE;
- is_key = TRUE;
- }
- value = NULL;
- if (!is_value) {
- filter = g_slist_append (filter, def);
- def = NULL;
- }
- }
- if (new_group && filter) {
- list = g_slist_append (list, filter);
- filter = NULL;
- new_group = FALSE;
- }
- // Only handle \0 here so we can handle remaining values above.
- if (*next_char == '\0') {
- break;
- }
- next_char++;
- };
-
- // Any remaining def or filter must still be handled here.
- if (def) {
- filter = g_slist_append (filter, def);
- }
-
- if (filter) {
- list = g_slist_append (list, filter);
- }
-
- GSList *ptr1, *ptr2;
-
- for (ptr1 = list; ptr1 != NULL; ptr1 = ptr1->next) {
- for (ptr2 = ptr1->data; ptr2 != NULL; ptr2 = ptr2->next) {
- g_debug ("%s = %s",
- ((DmapDbFilterDefinition *) ptr2->data)->key,
- ((DmapDbFilterDefinition *) ptr2->data)->value);
- }
- }
+ _maybe_restart (share);
done:
- return list;
+ return;
}
-void
-dmap_share_free_filter (GSList * filter)
+static void
+_set_property (GObject * object,
+ guint prop_id,
+ const GValue * value, GParamSpec * pspec)
{
- GSList *ptr1, *ptr2;
+ DmapShare *share = DMAP_SHARE (object);
- for (ptr1 = filter; ptr1 != NULL; ptr1 = ptr1->next) {
- for (ptr2 = ptr1->data; ptr2 != NULL; ptr2 = ptr2->next) {
- g_free (((DmapDbFilterDefinition *) ptr2->data)->value);
- g_free (ptr2->data);
+ switch (prop_id) {
+ case PROP_NAME:
+ _set_name (share, g_value_get_string (value));
+ break;
+ case PROP_PASSWORD:
+ _set_password (share, g_value_get_string (value));
+ break;
+ case PROP_DB:
+ if (share->priv->db) {
+ g_object_unref(share->priv->db);
+ }
+ share->priv->db = g_value_dup_object (value);
+ break;
+ case PROP_CONTAINER_DB:
+ if (share->priv->container_db) {
+ g_object_unref(share->priv->container_db);
}
+ share->priv->container_db = g_value_dup_object (value);
+ break;
+ case PROP_TRANSCODE_MIMETYPE:
+ g_free(share->priv->transcode_mimetype);
+ share->priv->transcode_mimetype = g_value_dup_string (value);
+ break;
+ case PROP_TXT_RECORDS:
+ g_strfreev (share->priv->txt_records);
+ share->priv->txt_records = g_value_dup_boxed (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
}
}
-void
-dmap_share_emit_error(DmapShare *share, gint code, const gchar *format, ...)
+static void
+_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec)
{
- va_list ap;
- GError *error;
-
- va_start(ap, format);
- error = g_error_new_valist(DMAP_ERROR, code, format, ap);
- g_signal_emit_by_name(share, "error", error);
+ DmapShare *share = DMAP_SHARE (object);
- va_end(ap);
+ switch (prop_id) {
+ case PROP_SERVER:
+ g_value_set_object (value, share->priv->server);
+ return;
+ case PROP_NAME:
+ g_value_set_string (value, share->priv->name);
+ break;
+ case PROP_PASSWORD:
+ g_value_set_string (value, share->priv->password);
+ break;
+ case PROP_REVISION_NUMBER:
+ g_value_set_uint (value,
+ _get_revision_number
+ (DMAP_SHARE (object)));
+ break;
+ case PROP_AUTH_METHOD:
+ g_value_set_uint (value,
+ dmap_share_get_auth_method
+ (DMAP_SHARE (object)));
+ break;
+ case PROP_DB:
+ g_value_set_object (value, share->priv->db);
+ break;
+ case PROP_CONTAINER_DB:
+ g_value_set_object (value, share->priv->container_db);
+ break;
+ case PROP_TRANSCODE_MIMETYPE:
+ g_value_set_string (value, share->priv->transcode_mimetype);
+ break;
+ case PROP_TXT_RECORDS:
+ g_value_set_boxed (value, share->priv->txt_records);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
}
-typedef struct {
- gchar *name;
- gint64 group_id;
- gchar *artist;
- int count;
-} GroupInfo;
-
static void
-_group_items (G_GNUC_UNUSED gpointer key, DmapRecord * record, GHashTable * groups)
+_dispose (GObject * object)
{
- gchar *album, *artist;
- GroupInfo *group_info;
- gint64 group_id;
+ DmapShare *share = DMAP_SHARE (object);
- g_object_get (record, "songartist", &artist, "songalbum", &album,
- "songalbumid", &group_id, NULL);
- if (!album) {
- g_free (artist);
- return;
- }
- group_info = g_hash_table_lookup (groups, album);
- if (!group_info) {
- group_info = g_new0 (GroupInfo, 1);
- g_hash_table_insert (groups, album, group_info);
- // They will be freed when the hash table is freed.
- group_info->name = album;
- group_info->artist = artist;
- group_info->group_id = group_id;
- } else {
- g_free (album);
- g_free (artist);
+ if (share->priv->published) {
+ _publish_stop (share);
}
- (group_info->count)++;
-}
-
-static gint
-_group_info_cmp (gconstpointer group1, gconstpointer group2)
-{
- return g_ascii_strcasecmp (((GroupInfo *) group1)->name,
- ((GroupInfo *) group2)->name);
-}
-
-static void
-_debug_param (gpointer key, gpointer val, G_GNUC_UNUSED gpointer user_data)
-{
- g_debug ("%s %s", (char *) key, (char *) val);
-}
-void
-dmap_share_ctrl_int (G_GNUC_UNUSED DmapShare * share,
- G_GNUC_UNUSED SoupServer * server,
- G_GNUC_UNUSED SoupMessage * message,
- const char *path,
- GHashTable * query,
- G_GNUC_UNUSED SoupClientContext * context)
-{
- g_debug ("Path is %s.", path);
- if (query) {
- g_hash_table_foreach (query, _debug_param, NULL);
+ if (share->priv->server_active) {
+ _server_stop (share);
}
- g_debug ("ctrl-int not implemented");
+ g_clear_object (&share->priv->publisher);
+ g_clear_object (&share->priv->server);
+ g_clear_object (&share->priv->db);
+ g_clear_object (&share->priv->container_db);
+
+ G_OBJECT_CLASS (dmap_share_parent_class)->dispose (object);
}
static void
-_accumulate_mlcl_size_and_ids (guint id,
- DmapRecord * record,
- struct share_bitwise_t *share_bitwise)
+_finalize (GObject * object)
{
- share_bitwise->id_list = g_slist_append (share_bitwise->id_list, GUINT_TO_POINTER(id));
-
- /* Make copy and set mlcl to NULL so real MLCL does not get changed */
- struct DmapMlclBits mb_copy = share_bitwise->mb;
+ DmapShare *share = DMAP_SHARE (object);
- mb_copy.mlcl = dmap_structure_add (NULL, DMAP_CC_MLCL);;
+ g_debug ("Finalizing DmapShare");
- DMAP_SHARE_GET_CLASS (share_bitwise->mb.share)->add_entry_to_mlcl (id,
- record,
- &mb_copy);
- share_bitwise->size += dmap_structure_get_size (mb_copy.mlcl);
+ g_hash_table_destroy (share->priv->session_ids);
+ share->priv->session_ids = NULL;
- /* Minus eight because we do not want to add size of MLCL CC field + size field n times,
- * where n == number of records.
- */
- share_bitwise->size -= 8;
+ g_free (share->priv->name);
+ g_free (share->priv->password);
+ g_free (share->priv->transcode_mimetype);
+ g_strfreev (share->priv->txt_records);
- /* Destroy created structures as we go. */
- dmap_structure_destroy (mb_copy.mlcl);
+ G_OBJECT_CLASS (dmap_share_parent_class)->finalize (object);
}
static void
-_accumulate_mlcl_size_and_ids_adapter (gpointer id,
- DmapRecord * record,
- struct share_bitwise_t *share_bitwise)
+dmap_share_class_init (DmapShareClass * klass)
{
- _accumulate_mlcl_size_and_ids(GPOINTER_TO_UINT(id),
- record,
- share_bitwise);
-}
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
-static void
-_write_dmap_preamble (SoupMessage * message, GNode * node)
-{
- guint length;
- gchar *data = dmap_structure_serialize (node, &length);
+ object_class->get_property = _get_property;
+ object_class->set_property = _set_property;
+ object_class->dispose = _dispose;
+ object_class->finalize = _finalize;
- soup_message_body_append (message->response_body,
- SOUP_MEMORY_TAKE, data, length);
- dmap_structure_destroy (node);
-}
+ /* Pure virtual methods: */
+ klass->get_desired_port = NULL;
+ klass->get_type_of_service = NULL;
+ klass->message_add_standard_headers = NULL;
+ klass->get_meta_data_map = NULL;
+ klass->add_entry_to_mlcl = NULL;
+ klass->databases_browse_xxx = NULL;
+ klass->databases_items_xxx = NULL;
-static void
-_write_next_mlit (SoupMessage * message, struct share_bitwise_t *share_bitwise)
-{
- if (share_bitwise->id_list == NULL) {
- g_debug ("No more ID's, sending message complete.");
- soup_message_body_complete (message->response_body);
- } else {
- gchar *data = NULL;
- guint length;
- DmapRecord *record;
- struct DmapMlclBits mb = { NULL, 0, NULL };
+ /* Virtual methods: */
+ klass->content_codes = _content_codes;
+ klass->login = dmap_share_login;
+ klass->logout = _logout;
+ klass->update = _update;
+ klass->published = _published;
+ klass->name_collision = _name_collision;
+ klass->databases = _databases;
+ klass->ctrl_int = _ctrl_int;
- record = share_bitwise->lookup_by_id (share_bitwise->db,
- GPOINTER_TO_UINT
- (share_bitwise->
- id_list->data));
+ g_object_class_install_property (object_class,
+ PROP_SERVER,
+ g_param_spec_object ("server",
+ "Soup Server",
+ "Soup server",
+ SOUP_TYPE_SERVER,
+ G_PARAM_READABLE));
- mb.bits = share_bitwise->mb.bits;
- mb.mlcl = dmap_structure_add (NULL, DMAP_CC_MLCL);
- mb.share = share_bitwise->mb.share;
+ g_object_class_install_property (object_class,
+ PROP_NAME,
+ g_param_spec_string ("name",
+ "Name",
+ "Share Name",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_PASSWORD,
+ g_param_spec_string ("password",
+ "Authentication password",
+ "Authentication password",
+ NULL,
+ G_PARAM_READWRITE));
- DMAP_SHARE_GET_CLASS (share_bitwise->mb.share)->
- add_entry_to_mlcl (GPOINTER_TO_UINT(share_bitwise->id_list->data), record, &mb);
- data = dmap_structure_serialize (g_node_first_child (mb.mlcl),
- &length);
+ g_object_class_install_property (object_class,
+ PROP_REVISION_NUMBER,
+ g_param_spec_uint ("revision-number",
+ "Revision number",
+ "Revision number",
+ 0,
+ G_MAXINT,
+ 0,
+ G_PARAM_READWRITE));
- soup_message_body_append (message->response_body,
- SOUP_MEMORY_TAKE, data, length);
- g_debug ("Sending ID %u.",
- GPOINTER_TO_UINT (share_bitwise->id_list->data));
- dmap_structure_destroy (mb.mlcl);
+ g_object_class_install_property (object_class,
+ PROP_AUTH_METHOD,
+ g_param_spec_uint ("auth-method",
+ "Authentication method",
+ "Authentication method",
+ DMAP_SHARE_AUTH_METHOD_NONE,
+ DMAP_SHARE_AUTH_METHOD_PASSWORD,
+ 0,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_DB,
+ g_param_spec_object ("db",
+ "DB",
+ "DB object",
+ DMAP_TYPE_DB,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
- share_bitwise->id_list =
- g_slist_remove (share_bitwise->id_list,
- share_bitwise->id_list->data);
+ g_object_class_install_property (object_class,
+ PROP_CONTAINER_DB,
+ g_param_spec_object ("container-db",
+ "Container DB",
+ "Container DB object",
+ DMAP_TYPE_CONTAINER_DB,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
- g_object_unref (record);
- }
+ g_object_class_install_property (object_class,
+ PROP_TRANSCODE_MIMETYPE,
+ g_param_spec_string
+ ("transcode-mimetype",
+ "Transcode mimetype",
+ "Set mimetype of stream after transcoding",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
- soup_server_unpause_message (share_bitwise->share->priv->server, message);
+ g_object_class_install_property (object_class,
+ PROP_TXT_RECORDS,
+ g_param_spec_boxed ("txt-records",
+ "TXT-Records",
+ "Set TXT-Records used for MDNS publishing",
+ G_TYPE_STRV,
+ G_PARAM_READWRITE));
+
+ _signals[ERROR] =
+ g_signal_new ("error",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
}
static void
-_chunked_message_finished (G_GNUC_UNUSED SoupMessage * message,
- struct share_bitwise_t *share_bitwise)
+dmap_share_init (DmapShare * share)
{
- g_debug ("Finished sending chunked data.");
- if (share_bitwise->destroy) {
- share_bitwise->destroy (share_bitwise->db);
- }
- g_free (share_bitwise);
-}
+ share->priv = DMAP_SHARE_GET_PRIVATE (share);
-static DmapRecord *
-_lookup_adapter (GHashTable * ht, guint id)
-{
- /* NOTE: each time this is called by _write_next_mlit(), the
- * returned value will be unref'ed by _write_next_mlit(). We
- * also need to destroy the GHashTable, so bump up the reference
- * count so that both can happen. */
- return g_object_ref (g_hash_table_lookup (ht, GUINT_TO_POINTER (id)));
-}
+ share->priv->revision_number = 5;
+ share->priv->auth_method = DMAP_SHARE_AUTH_METHOD_NONE;
+ share->priv->publisher = dmap_mdns_publisher_new ();
+ share->priv->server = soup_server_new (NULL, NULL);
-void
-dmap_share_databases (DmapShare * share,
- SoupServer * server,
- SoupMessage * message,
- const char *path,
- GHashTable * query, SoupClientContext * context)
-{
- const char *rest_of_path;
+ /* using direct since there is no g_uint_hash or g_uint_equal */
+ share->priv->session_ids =
+ g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
+ g_free);
- g_debug ("Path is %s.", path);
- g_hash_table_foreach (query, _debug_param, NULL);
+ g_signal_connect_object (share->priv->publisher,
+ "published",
+ G_CALLBACK (_published_adapter), share, 0);
+ g_signal_connect_object (share->priv->publisher,
+ "name-collision",
+ G_CALLBACK (_name_collision_adapter),
+ share, 0);
+}
- if (!dmap_share_session_id_validate
- (share, context, query, NULL)) {
- soup_message_set_status (message, SOUP_STATUS_FORBIDDEN);
+guint
+dmap_share_get_auth_method (DmapShare * share)
+{
+ return share->priv->auth_method;
+}
+
+static gboolean
+_get_session_id (GHashTable * query, guint32 * id)
+{
+ gboolean ok = FALSE;
+ char *session_id_str;
+ guint32 session_id;
+
+ session_id_str = g_hash_table_lookup (query, "session-id");
+ if (session_id_str == NULL) {
+ g_warning ("Session id not found.");
goto done;
}
- rest_of_path = strchr (path + 1, '/');
+ session_id = (guint32) strtoul (session_id_str, NULL, 10);
+ if (id != NULL) {
+ *id = session_id;
+ }
- if (rest_of_path == NULL) {
- /* AVDB server databases
- * MSTT status
- * MUTY update type
- * MTCO specified total count
- * MRCO returned count
- * MLCL listing
- * MLIT listing item
- * MIID item id
- * MPER persistent id
- * MINM item name
- * MIMC item count
- * MCTC container count
- */
- GNode *avdb;
- GNode *mlcl;
- GNode *mlit;
+ ok = TRUE;
- avdb = dmap_structure_add (NULL, DMAP_CC_AVDB);
- dmap_structure_add (avdb, DMAP_CC_MSTT,
- (gint32) SOUP_STATUS_OK);
- dmap_structure_add (avdb, DMAP_CC_MUTY, 0);
- dmap_structure_add (avdb, DMAP_CC_MTCO, (gint32) 1);
- dmap_structure_add (avdb, DMAP_CC_MRCO, (gint32) 1);
- mlcl = dmap_structure_add (avdb, DMAP_CC_MLCL);
- mlit = dmap_structure_add (mlcl, DMAP_CC_MLIT);
- dmap_structure_add (mlit, DMAP_CC_MIID, (gint32) 1);
- dmap_structure_add (mlit, DMAP_CC_MPER, (gint64) 1);
- dmap_structure_add (mlit, DMAP_CC_MINM, share->priv->name);
- dmap_structure_add (mlit, DMAP_CC_MIMC,
- dmap_db_count (share->priv->db));
- dmap_structure_add (mlit, DMAP_CC_MCTC, (gint32) 1);
+done:
+ return ok;
+}
- dmap_share_message_set_from_dmap_structure (share, message,
- avdb);
- dmap_structure_destroy (avdb);
- } else if (g_ascii_strcasecmp ("/1/groups", rest_of_path) == 0) {
- /* ADBS database songs
- * MSTT status
- * MUTY update type
- * MTCO specified total count
- * MRCO returned count
- * MLCL listing
- * MLIT
- * attrs
- * MLIT
- * ...
- */
+gboolean
+dmap_share_session_id_validate (DmapShare * share,
+ SoupClientContext * context,
+ GHashTable * query, guint32 * id)
+{
+ gboolean ok = FALSE;
+ guint32 session_id;
+ gboolean res;
+ const char *addr;
+ const char *remote_address;
- GSList *filter_def;
- gchar *record_query;
- GHashTable *records = NULL;
- GHashTable *groups;
- GList *values;
- GList *value;
- gchar *sort_by;
- GroupInfo *group_info;
- GNode *agal;
- GNode *mlcl;
- GNode *mlit;
- gint num;
+ if (id) {
+ *id = 0;
+ }
- if (g_strcmp0
- (g_hash_table_lookup (query, "group-type"),
- "albums") != 0) {
- g_warning ("Unsupported grouping");
- soup_message_set_status (message,
- SOUP_STATUS_INTERNAL_SERVER_ERROR);
- goto done;
- }
+ res = _get_session_id (query, &session_id);
+ if (!res) {
+ g_warning ("Validation failed: Unable to parse session id");
+ goto done;
+ }
- record_query = g_hash_table_lookup (query, "query");
- filter_def = dmap_share_build_filter (record_query);
- records =
- dmap_db_apply_filter (DMAP_DB (share->priv->db),
- filter_def);
+ /* check hash for remote address */
+ addr = g_hash_table_lookup (share->priv->session_ids,
+ GUINT_TO_POINTER (session_id));
+ if (addr == NULL) {
+ g_warning
+ ("Validation failed: Unable to lookup session id %u",
+ session_id);
+ goto done;
+ }
- groups = g_hash_table_new_full (g_str_hash, g_str_equal,
- g_free, g_free);
- g_hash_table_foreach (records, (GHFunc) _group_items, groups);
+ remote_address = soup_client_context_get_host (context);
+ g_debug ("Validating session id %u from %s matches %s",
+ session_id, remote_address, addr);
+ if (remote_address == NULL || strcmp (addr, remote_address) != 0) {
+ g_warning
+ ("Validation failed: Remote address does not match stored address");
+ goto done;
+ }
- agal = dmap_structure_add (NULL, DMAP_CC_AGAL);
- dmap_structure_add (agal, DMAP_CC_MSTT,
- (gint32) SOUP_STATUS_OK);
- dmap_structure_add (agal, DMAP_CC_MUTY, 0);
+ if (id) {
+ *id = session_id;
+ }
- num = g_hash_table_size (groups);
- dmap_structure_add (agal, DMAP_CC_MTCO, (gint32) num);
- dmap_structure_add (agal, DMAP_CC_MRCO, (gint32) num);
+ ok = TRUE;
- mlcl = dmap_structure_add (agal, DMAP_CC_MLCL);
+done:
+ return ok;
+}
- values = g_hash_table_get_values (groups);
- if (g_hash_table_lookup (query, "include-sort-headers")) {
- sort_by = g_hash_table_lookup (query, "sort");
- if (g_strcmp0 (sort_by, "album") == 0) {
- values = g_list_sort (values, _group_info_cmp);
- } else {
- g_warning ("Unknown sort column: %s",
- sort_by);
- }
- }
+static guint32
+_session_id_generate (void)
+{
+ guint32 id;
- for (value = values; value; value = g_list_next (value)) {
- group_info = (GroupInfo *) value->data;
- mlit = dmap_structure_add (mlcl, DMAP_CC_MLIT);
- dmap_structure_add (mlit, DMAP_CC_MIID,
- (gint) group_info->group_id);
- dmap_structure_add (mlit, DMAP_CC_MPER,
- group_info->group_id);
- dmap_structure_add (mlit, DMAP_CC_MINM,
- group_info->name);
- dmap_structure_add (mlit, DMAP_CC_ASAA,
- group_info->artist);
- dmap_structure_add (mlit, DMAP_CC_MIMC,
- (gint32) group_info->count);
+ id = g_random_int ();
- // Free this now, since the hash free func won't.
- // Name will be freed when the hash table keys are freed.
- g_free (group_info->artist);
- }
+ return id;
+}
- g_list_free (values);
- dmap_share_free_filter (filter_def);
+static guint32
+_session_id_create (DmapShare * share, SoupClientContext * context)
+{
+ guint32 id;
+ const char *addr;
+ char *remote_address;
- dmap_share_message_set_from_dmap_structure (share, message,
- agal);
+ do {
+ /* create a unique session id */
+ id = _session_id_generate ();
+ g_debug ("Generated session id %u", id);
- g_hash_table_destroy (records);
- g_hash_table_destroy (groups);
- dmap_structure_destroy (agal);
- } else if (g_ascii_strcasecmp ("/1/items", rest_of_path) == 0) {
- /* ADBS database songs
- * MSTT status
- * MUTY update type
- * MTCO specified total count
- * MRCO returned count
- * MLCL listing
- * MLIT
- * attrs
- * MLIT
- * ...
- */
- GNode *adbs;
- gchar *record_query;
- GHashTable *records = NULL;
- struct DmapMetaDataMap *map;
- gint32 num_songs;
- struct DmapMlclBits mb = { NULL, 0, NULL };
- struct share_bitwise_t *share_bitwise;
+ /* if already used, try again */
+ addr = g_hash_table_lookup (share->priv->session_ids,
+ GUINT_TO_POINTER (id));
+ } while (addr != NULL);
- record_query = g_hash_table_lookup (query, "query");
- if (record_query) {
- GSList *filter_def;
+ /* store session id and remote address */
+ /* FIXME, warning, this fails against libsoup-2.33.90-1.fc15.x86_64 with:
+ * (dmapd:12917): libsoup-CRITICAL **: soup_address_get_physical: assertion `SOUP_IS_ADDRESS (addr)'
failed
+ * Is this a bug in libsoup or libdmapsharing?
+ */
+ remote_address = g_strdup (soup_client_context_get_host (context));
+ g_hash_table_insert (share->priv->session_ids, GUINT_TO_POINTER (id),
+ remote_address);
- filter_def = dmap_share_build_filter (record_query);
- records =
- dmap_db_apply_filter (DMAP_DB
- (share->priv->db),
- filter_def);
- num_songs = g_hash_table_size (records);
- g_debug ("Found %d records", num_songs);
- dmap_share_free_filter (filter_def);
- } else {
- num_songs = dmap_db_count (share->priv->db);
- }
+ return id;
+}
- map = DMAP_SHARE_GET_CLASS (share)->get_meta_data_map (share);
- mb.bits = dmap_share_parse_meta (query, map);
- mb.share = share;
+void
+dmap_share_message_set_from_dmap_structure (DmapShare * share,
+ SoupMessage * message,
+ GNode * structure)
+{
+ gchar *resp;
+ guint length;
- /* NOTE:
- * We previously simply called foreach...add_entry_to_mlcl and later serialized the entire
- * structure. This has the disadvantage that the entire response must be in memory before
- * libsoup sends it to the client.
- *
- * Now, we go through the database in multiple passes (as an interim solution):
- *
- * 1. Accumulate the eventual size of the MLCL by creating and then free'ing each MLIT.
- * 2. Generate the DAAP preamble ending with the MLCL (with size fudged for ADBS and MLCL).
- * 3. Setup libsoup response headers, etc.
- * 4. Setup callback to transmit DAAP preamble (_write_dmap_preamble)
- * 5. Setup callback to transmit MLIT's (_write_next_mlit)
- */
+ resp = dmap_structure_serialize (structure, &length);
- /* 1: */
- share_bitwise = g_new0 (struct share_bitwise_t, 1);
+ if (resp == NULL) {
+ g_warning ("Serialize gave us null?");
+ return;
+ }
- share_bitwise->share = share;
- share_bitwise->mb = mb;
- share_bitwise->id_list = NULL;
- share_bitwise->size = 0;
- if (record_query) {
- share_bitwise->db = records;
- share_bitwise->lookup_by_id = (ShareBitwiseLookupByIdFunc)
- _lookup_adapter;
- share_bitwise->destroy = (ShareBitwiseDestroyFunc) g_hash_table_destroy;
- g_hash_table_foreach (records,
- (GHFunc) _accumulate_mlcl_size_and_ids_adapter,
- share_bitwise);
- } else {
- share_bitwise->db = share->priv->db;
- share_bitwise->lookup_by_id = (ShareBitwiseLookupByIdFunc) dmap_db_lookup_by_id;
- share_bitwise->destroy = NULL;
- dmap_db_foreach (share->priv->db,
- (DmapIdRecordFunc) _accumulate_mlcl_size_and_ids,
- share_bitwise);
- }
+ soup_message_set_response (message, "application/x-dmap-tagged",
+ SOUP_MEMORY_TAKE, resp, length);
- /* 2: */
- adbs = dmap_structure_add (NULL, DMAP_CC_ADBS);
- dmap_structure_add (adbs, DMAP_CC_MSTT,
- (gint32) SOUP_STATUS_OK);
- dmap_structure_add (adbs, DMAP_CC_MUTY, 0);
- dmap_structure_add (adbs, DMAP_CC_MTCO, (gint32) num_songs);
- dmap_structure_add (adbs, DMAP_CC_MRCO, (gint32) num_songs);
- mb.mlcl = dmap_structure_add (adbs, DMAP_CC_MLCL);
- dmap_structure_increase_by_predicted_size (adbs,
- share_bitwise->
- size);
- dmap_structure_increase_by_predicted_size (mb.mlcl,
- share_bitwise->
- size);
+ DMAP_SHARE_GET_CLASS (share)->message_add_standard_headers (share,
+ message);
- /* 3: */
- /* Free memory after each chunk sent out over network. */
- soup_message_body_set_accumulate (message->response_body,
- FALSE);
- soup_message_headers_append (message->response_headers,
- "Content-Type",
- "application/x-dmap-tagged");
- DMAP_SHARE_GET_CLASS (share)->
- message_add_standard_headers (share, message);
- soup_message_headers_set_content_length (message->
- response_headers,
- dmap_structure_get_size
- (adbs));
- soup_message_set_status (message, SOUP_STATUS_OK);
+ soup_message_set_status (message, SOUP_STATUS_OK);
+}
- /* 4: */
- g_signal_connect (message, "wrote_headers",
- G_CALLBACK (_write_dmap_preamble), adbs);
+gboolean
+dmap_share_client_requested (DmapBits bits, gint field)
+{
+ return 0 != (bits & (((DmapBits) 1) << field));
+}
- /* 5: */
- g_signal_connect (message, "wrote_chunk",
- G_CALLBACK (_write_next_mlit),
- share_bitwise);
- g_signal_connect (message, "finished",
- G_CALLBACK (_chunked_message_finished),
- share_bitwise);
+static gboolean
+_uri_is_local (const char *text_uri)
+{
+ return g_str_has_prefix (text_uri, "file://");
+}
- } else if (g_ascii_strcasecmp ("/1/containers", rest_of_path) == 0) {
- /* APLY database playlists
- * MSTT status
- * MUTY update type
- * MTCO specified total count
- * MRCO returned count
- * MLCL listing
- * MLIT listing item
- * MIID item id
- * MPER persistent item id
- * MINM item name
- * MIMC item count
- * ABPL baseplaylist (only for base)
- * MLIT
- * ...
- */
- GNode *aply;
- GNode *mlit;
- struct DmapMetaDataMap *map;
- struct DmapMlclBits mb = { NULL, 0, NULL };
+void
+dmap_share_login (DmapShare * share,
+ SoupMessage * message,
+ const char *path,
+ G_GNUC_UNUSED GHashTable * query,
+ SoupClientContext * context)
+{
+/* MLOG login response
+ * MSTT status
+ * MLID session id
+ */
+ GNode *mlog;
+ guint32 session_id;
- map = DMAP_SHARE_GET_CLASS (share)->get_meta_data_map (share);
- mb.bits = dmap_share_parse_meta (query, map);
- mb.share = share;
+ g_debug ("Path is %s.", path);
- aply = dmap_structure_add (NULL, DMAP_CC_APLY);
- dmap_structure_add (aply, DMAP_CC_MSTT,
- (gint32) SOUP_STATUS_OK);
- dmap_structure_add (aply, DMAP_CC_MUTY, 0);
- dmap_structure_add (aply, DMAP_CC_MTCO,
- (gint32) dmap_container_db_count (share->
- priv->
- container_db)
- + 1);
- dmap_structure_add (aply, DMAP_CC_MRCO,
- (gint32) dmap_container_db_count (share->
- priv->
- container_db)
- + 1);
- mb.mlcl = dmap_structure_add (aply, DMAP_CC_MLCL);
+ session_id = _session_id_create (share, context);
- /* Base playlist (playlist 1 contains all songs): */
- mlit = dmap_structure_add (mb.mlcl, DMAP_CC_MLIT);
- dmap_structure_add (mlit, DMAP_CC_MIID, (gint32) 1);
- dmap_structure_add (mlit, DMAP_CC_MPER, (gint64) 1);
- dmap_structure_add (mlit, DMAP_CC_MINM, share->priv->name);
- dmap_structure_add (mlit, DMAP_CC_MIMC,
- dmap_db_count (share->priv->db));
- dmap_structure_add (mlit, DMAP_CC_FQUESCH, 0);
- dmap_structure_add (mlit, DMAP_CC_MPCO, 0);
- dmap_structure_add (mlit, DMAP_CC_AESP, 0);
- dmap_structure_add (mlit, DMAP_CC_AEPP, 0);
- dmap_structure_add (mlit, DMAP_CC_AEPS, 0);
- dmap_structure_add (mlit, DMAP_CC_AESG, 0);
+ mlog = dmap_structure_add (NULL, DMAP_CC_MLOG);
+ dmap_structure_add (mlog, DMAP_CC_MSTT, (gint32) SOUP_STATUS_OK);
+ dmap_structure_add (mlog, DMAP_CC_MLID, session_id);
- dmap_structure_add (mlit, DMAP_CC_ABPL, (gchar) 1);
+ dmap_share_message_set_from_dmap_structure (share, message, mlog);
+ dmap_structure_destroy (mlog);
+}
- dmap_container_db_foreach (share->priv->container_db,
- (DmapIdContainerRecordFunc)
- dmap_share_add_playlist_to_mlcl,
- &mb);
+GSList *
+dmap_share_build_filter (gchar * filterstr)
+{
+ /* Produces a list of lists, each being a filter definition that may
+ * be one or more filter criteria.
+ */
- dmap_share_message_set_from_dmap_structure (share, message,
- aply);
- dmap_structure_destroy (aply);
- } else if (g_ascii_strncasecmp ("/1/containers/", rest_of_path, 14) ==
- 0) {
- /* APSO playlist songs
- * MSTT status
- * MUTY update type
- * MTCO specified total count
- * MRCO returned count
- * MLCL listing
- * MLIT listing item
- * MIKD item kind
- * MIID item id
- * MCTI container item id
- * MLIT
- * ...
- */
- GNode *apso;
- struct DmapMetaDataMap *map;
- struct DmapMlclBits mb = { NULL, 0, NULL };
- guint pl_id;
- gchar *record_query;
- GSList *filter_def;
- GHashTable *records;
+ /* A filter string looks like (iTunes):
+ * 'daap.songgenre:Other'+'daap.songartist:Band'.
+ * or (Roku):
+ * 'daap.songgenre:Other' 'daap.songartist:Band'.
+ * or
+ * 'dmap.itemid:1000'
+ * or
+ * 'dmap.itemid:1000','dmap:itemid:1001'
+ * or
+ * 'daap.songgenre:Foo'+'daap.songartist:Bar'+'daap.songalbum:Baz'
+ * or (iPhoto '09)
+ * ('daap.idemid:1000','dmap:itemid:1001')
+ * or (DACP):
+ * ('com.apple.itunes.mediakind:1','com.apple.itunes.mediakind:32') 'daap.songartist!:'
+ */
- map = DMAP_SHARE_GET_CLASS (share)->get_meta_data_map (share);
- mb.bits = dmap_share_parse_meta (query, map);
- mb.share = share;
+ gchar *next_char;
+ GString *value;
- apso = dmap_structure_add (NULL, DMAP_CC_APSO);
- dmap_structure_add (apso, DMAP_CC_MSTT,
- (gint32) SOUP_STATUS_OK);
- dmap_structure_add (apso, DMAP_CC_MUTY, 0);
+ //gboolean save_value;
+ gboolean is_key;
+ gboolean is_value;
+ gboolean new_group;
+ gboolean accept;
+ gboolean negate;
+ gint parentheses_count;
+ gint quotes_count;
+ DmapDbFilterDefinition *def;
- if (g_ascii_strcasecmp ("/1/items", rest_of_path + 13) == 0) {
- GList *id;
- gchar *sort_by;
- GList *keys;
+ GSList *list = NULL;
+ GSList *filter = NULL;
- record_query = g_hash_table_lookup (query, "query");
- filter_def = dmap_share_build_filter (record_query);
- records =
- dmap_db_apply_filter (DMAP_DB
- (share->priv->db),
- filter_def);
- gint32 num_songs = g_hash_table_size (records);
+ g_debug ("Filter string is %s.", filterstr);
- g_debug ("Found %d records", num_songs);
- dmap_share_free_filter (filter_def);
-
- dmap_structure_add (apso, DMAP_CC_MTCO,
- (gint32) num_songs);
- dmap_structure_add (apso, DMAP_CC_MRCO,
- (gint32) num_songs);
- mb.mlcl = dmap_structure_add (apso, DMAP_CC_MLCL);
+ if (filterstr == NULL) {
+ goto done;
+ }
- sort_by = g_hash_table_lookup (query, "sort");
- keys = g_hash_table_get_keys (records);
- if (g_strcmp0 (sort_by, "album") == 0) {
- keys = g_list_sort_with_data (keys,
- (GCompareDataFunc)
- dmap_av_record_cmp_by_album,
- share->priv->
- db);
- } else if (sort_by != NULL) {
- g_warning ("Unknown sort column: %s",
- sort_by);
- }
+ next_char = filterstr;
- for (id = keys; id; id = id->next) {
- (*
- (DMAP_SHARE_GET_CLASS (share)->
- add_entry_to_mlcl)) (GPOINTER_TO_UINT(id->data),
- g_hash_table_lookup
- (records, id->data),
- &mb);
- }
+ parentheses_count = 0;
+ quotes_count = 0;
+ is_key = TRUE;
+ is_value = FALSE;
+ new_group = FALSE;
+ negate = FALSE;
+ value = NULL;
+ def = NULL;
- g_list_free (keys);
- g_hash_table_destroy (records);
+ // The query string is divided in groups of AND separated by a space,
+ // each group containing queries of OR separated by comma enclosed in
+ // parentheses. Queries are key, value pairs separated by ':' enclosed
+ // in quotes or not.
+ // The result from this is a list of lists. Here is an example:
+ // String is ('com.apple.itunes.mediakind:1','com.apple.itunes.mediakind:32') 'daap.songartist!:'
+ // list:
+ // |-> filter1: -> com.apple.itunes.mediakind = 1
+ // | | -> com.apple.itunes.mediakind = 32
+ // |-> filter1: -> daap.songartist! = (null)
+ // This basically means that the query is (filter1 AND filter2), and
+ // filter1 is (com.apple.itunes.mediakind = 1) OR (com.apple.itunes.mediakind = 32)
+ while (TRUE) {
+ // We check each character to see if it should be included in
+ // the current value (the current value will become the query key
+ // or the query value). Anything that is not a special character
+ // handled below will be accepted (this means weird characters
+ // might appear in names).
+ // Handling of unicode characters is unknown, but as long it
+ // doesn't appear as a character handled below, it will be
+ // included.
+ // This parser will result in unknown behaviour on bad-formatted
+ // queries (it does not check for syntax errors), but should not
+ // crash. In this way it may accept much more syntaxes then
+ // other parsers.
+ accept = FALSE;
+ // A slash can escape characters such as ', so add the character
+ // after the slash.
+ if (*next_char == '\\') {
+ accept = TRUE;
+ next_char++;
} else {
- pl_id = strtoul (rest_of_path + 14, NULL, 10);
- if (pl_id == 1) {
- gint32 num_songs =
- dmap_db_count (share->priv->db);
- dmap_structure_add (apso, DMAP_CC_MTCO,
- (gint32) num_songs);
- dmap_structure_add (apso, DMAP_CC_MRCO,
- (gint32) num_songs);
- mb.mlcl =
- dmap_structure_add (apso,
- DMAP_CC_MLCL);
-
- dmap_db_foreach (share->priv->db,
- DMAP_SHARE_GET_CLASS
- (share)->add_entry_to_mlcl,
- &mb);
+ switch (*next_char) {
+ case '(':
+ if (is_value) {
+ accept = TRUE;
+ } else {
+ parentheses_count++;
+ }
+ break;
+ case ')':
+ if (is_value) {
+ accept = TRUE;
+ } else {
+ parentheses_count--;
+ }
+ break;
+ case '\'':
+ if (quotes_count > 0) {
+ quotes_count = 0;
+ } else {
+ quotes_count = 1;
+ }
+ break;
+ case ' ':
+ if (is_value) {
+ accept = TRUE;
+ } else {
+ new_group = TRUE;
+ }
+ break;
+ case ':':
+ // Inside values, they will be included in the
+ // query string, otherwise it indicates there
+ // will be a value next.
+ if (is_value) {
+ accept = TRUE;
+ } else if (is_key) {
+ is_value = TRUE;
+ }
+ break;
+ case '!':
+ if (is_value) {
+ accept = TRUE;
+ } else if (is_key && value) {
+ negate = TRUE;
+ }
+ break;
+ case ',':
+ case '+':
+ // Accept these characters only if inside quotes
+ if (is_value) {
+ accept = TRUE;
+ }
+ break;
+ case '\0':
+ // Never accept
+ break;
+ default:
+ accept = TRUE;
+ break;
+ }
+ }
+ //g_debug ("Char: %c, Accept: %s", *next_char, accept?"TRUE":"FALSE");
+ //Is the next character to be accepted?
+ if (accept) {
+ if (!value) {
+ value = g_string_new ("");
+ }
+ g_string_append_c (value, *next_char);
+ } else if (value != NULL && *next_char != '!') {
+ // If we won't accept this character, we are ending a
+ // query key or value, so we should save them in a new
+ // DmapDbFilterDefinition. If is_value is TRUE, we will still
+ // parse the query value, so we must keep our def around.
+ // Otherwise, save it in the list of filters.
+ if (!def) {
+ def = g_new0 (DmapDbFilterDefinition, 1);
+ }
+ if (is_key) {
+ def->key = value->str;
+ g_string_free (value, FALSE);
+ def->negate = negate;
+ negate = FALSE;
+ is_key = FALSE;
} else {
- DmapContainerRecord *record;
- DmapDb *entries;
- guint num_songs;
+ def->value = value->str;
+ g_string_free (value, FALSE);
+ is_value = FALSE;
+ is_key = TRUE;
+ }
+ value = NULL;
+ if (!is_value) {
+ filter = g_slist_append (filter, def);
+ def = NULL;
+ }
+ }
+ if (new_group && filter) {
+ list = g_slist_append (list, filter);
+ filter = NULL;
+ new_group = FALSE;
+ }
+ // Only handle \0 here so we can handle remaining values above.
+ if (*next_char == '\0') {
+ break;
+ }
+ next_char++;
+ };
- record = dmap_container_db_lookup_by_id
- (share->priv->container_db, pl_id);
- entries =
- dmap_container_record_get_entries
- (record);
- /* FIXME: what if entries is NULL (handled in dmapd but should be [also]
handled here)? */
- num_songs = dmap_db_count (entries);
+ // Any remaining def or filter must still be handled here.
+ if (def) {
+ filter = g_slist_append (filter, def);
+ }
- dmap_structure_add (apso, DMAP_CC_MTCO,
- (gint32) num_songs);
- dmap_structure_add (apso, DMAP_CC_MRCO,
- (gint32) num_songs);
- mb.mlcl =
- dmap_structure_add (apso,
- DMAP_CC_MLCL);
+ if (filter) {
+ list = g_slist_append (list, filter);
+ }
- dmap_db_foreach (entries,
- DMAP_SHARE_GET_CLASS
- (share)->add_entry_to_mlcl,
- &mb);
+ GSList *ptr1, *ptr2;
- g_object_unref (entries);
- g_object_unref (record);
- }
+ for (ptr1 = list; ptr1 != NULL; ptr1 = ptr1->next) {
+ for (ptr2 = ptr1->data; ptr2 != NULL; ptr2 = ptr2->next) {
+ g_debug ("%s = %s",
+ ((DmapDbFilterDefinition *) ptr2->data)->key,
+ ((DmapDbFilterDefinition *) ptr2->data)->value);
}
-
- dmap_share_message_set_from_dmap_structure (share, message,
- apso);
- dmap_structure_destroy (apso);
- } else if (g_ascii_strncasecmp ("/1/browse/", rest_of_path, 9) == 0) {
- DMAP_SHARE_GET_CLASS (share)->databases_browse_xxx (share,
- message,
- path,
- query);
- } else if (g_ascii_strncasecmp ("/1/items/", rest_of_path, 9) == 0) {
- /* just the file :) */
- DMAP_SHARE_GET_CLASS (share)->databases_items_xxx (share,
- server,
- message,
- path);
- } else if (g_str_has_prefix (rest_of_path, "/1/groups/") &&
- g_str_has_suffix (rest_of_path, "/extra_data/artwork")) {
- /* We don't yet implement cover requests here, say no cover */
- g_debug ("Assuming no artwork for requested group/album");
- soup_message_set_status (message, SOUP_STATUS_NOT_FOUND);
- } else {
- g_warning ("Unhandled: %s", path);
}
done:
- return;
+ return list;
+}
+
+void
+dmap_share_free_filter (GSList * filter)
+{
+ GSList *ptr1, *ptr2;
+
+ for (ptr1 = filter; ptr1 != NULL; ptr1 = ptr1->next) {
+ for (ptr2 = ptr1->data; ptr2 != NULL; ptr2 = ptr2->next) {
+ g_free (((DmapDbFilterDefinition *) ptr2->data)->value);
+ g_free (ptr2->data);
+ }
+ }
+}
+
+void
+dmap_share_emit_error(DmapShare *share, gint code, const gchar *format, ...)
+{
+ va_list ap;
+ GError *error;
+
+ va_start(ap, format);
+ error = g_error_new_valist(DMAP_ERROR, code, format, ap);
+ g_signal_emit_by_name(share, "error", error);
+
+ va_end(ap);
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]