From 92ba754fc67b084202c1a1c7a750fa4f2133418c Mon Sep 17 00:00:00 2001
From: Hagen Schink <troja84@gmail.com>
Date: Mon, 13 Apr 2009 19:35:02 +0200
Subject: Implemented basic icy support for the httpd output

[mk: folded with patch "Put icy related functions in extra source
files"; moved icy_server.c from HAVE_CURL to ENABLE_HTTPD_OUTPUT;
removed an unused variable]
---
 src/output/httpd_client.c        | 187 ++++++++++++++++++++++++++++++++++++---
 src/output/httpd_client.h        |   6 ++
 src/output/httpd_internal.h      |   5 ++
 src/output/httpd_output_plugin.c |  37 +++++++-
 4 files changed, 223 insertions(+), 12 deletions(-)

(limited to 'src/output')

diff --git a/src/output/httpd_client.c b/src/output/httpd_client.c
index 266a207cc..efdb9b190 100644
--- a/src/output/httpd_client.c
+++ b/src/output/httpd_client.c
@@ -21,6 +21,7 @@
 #include "httpd_internal.h"
 #include "fifo_buffer.h"
 #include "page.h"
+#include "icy_server.h"
 
 #include <stdbool.h>
 #include <assert.h>
@@ -85,6 +86,39 @@ struct httpd_client {
 	 * #current_page.
 	 */
 	size_t current_position;
+
+	/* ICY */
+
+	/**
+	 * If we should sent icy metadata.
+	 */
+	gboolean metadata_requested;
+
+	/**
+	 * If the current metadata was already sent to the client.
+	 */
+	gboolean metadata_sent;
+
+	/**
+	 * The amount of streaming data between each metadata block
+	 */
+	guint metaint;
+
+	/**
+	 * The metadata as #page which is currently being sent to the client.
+	 */
+	struct page *metadata;
+
+	/*
+	 * The amount of bytes which were already sent from the metadata.
+	 */
+	size_t metadata_current_position;
+
+	/**
+	 * The amount of streaming data sent to the client
+	 * since the last icy information was sent.
+	 */
+	guint metadata_fill;
 };
 
 static void
@@ -110,6 +144,9 @@ httpd_client_free(struct httpd_client *client)
 	} else
 		fifo_buffer_free(client->input);
 
+	if (client->metadata)
+		page_unref (client->metadata);
+
 	g_source_remove(client->read_source_id);
 	g_io_channel_unref(client->channel);
 	g_free(client);
@@ -171,6 +208,12 @@ httpd_client_handle_line(struct httpd_client *client, const char *line)
 			return true;
 		}
 
+		if (strncmp(line, "Icy-MetaData: 1", 15) == 0) {
+			/* Send icy metadata */
+			client->metadata_requested = TRUE;
+			return true;
+		}
+
 		/* expect more request headers */
 		return true;
 	}
@@ -218,18 +261,33 @@ httpd_client_send_response(struct httpd_client *client)
 
 	assert(client->state == RESPONSE);
 
-	g_snprintf(buffer, sizeof(buffer),
-		   "HTTP/1.1 200 OK\r\n"
-		   "Content-Type: %s\r\n"
-		   "Connection: close\r\n"
-		   "Pragma: no-cache\r\n"
-		   "Cache-Control: no-cache, no-store\r\n"
-		   "\r\n",
-		   client->httpd->content_type);
+	if (!client->metadata_requested) {
+		g_snprintf(buffer, sizeof(buffer),
+			   "HTTP/1.1 200 OK\r\n"
+			   "Content-Type: %s\r\n"
+			   "Connection: close\r\n"
+			   "Pragma: no-cache\r\n"
+			   "Cache-Control: no-cache, no-store\r\n"
+			   "\r\n",
+			   client->httpd->content_type);
+	} else {
+		gchar *metadata_header;
+
+		metadata_header = icy_server_metadata_header("Add config information here!", /* TODO */
+							     "Add config information here!", /* TODO */
+							     "Add config information here!", /* TODO */
+							     client->httpd->content_type,
+							     client->metaint);
+
+		g_strlcpy(buffer, metadata_header, sizeof(buffer));
+
+		g_free(metadata_header);
+	}
 
 	status = g_io_channel_write_chars(client->channel,
 					  buffer, strlen(buffer),
 					  &bytes_written, &error);
+
 	switch (status) {
 	case G_IO_STATUS_NORMAL:
 	case G_IO_STATUS_AGAIN:
@@ -385,6 +443,13 @@ httpd_client_new(struct httpd_output *httpd, int fd)
 	client->input = fifo_buffer_new(4096);
 	client->state = REQUEST;
 
+	client->metadata_requested = FALSE;
+	client->metadata_sent = TRUE;
+	client->metaint = 8192; /*TODO: just a std value */
+	client->metadata = NULL;
+	client->metadata_current_position = 0;
+	client->metadata_fill = 0;
+
 	return client;
 }
 
@@ -444,6 +509,42 @@ write_page_to_channel(GIOChannel *channel,
 					bytes_written_r, error);
 }
 
+static GIOStatus
+write_n_bytes_to_channel(GIOChannel *channel, const struct page *page,
+			 size_t position, gint n,
+			 gsize *bytes_written_r, GError **error)
+{
+	GIOStatus status;
+
+	assert(channel != NULL);
+	assert(page != NULL);
+	assert(position < page->size);
+
+	if (n == -1) {
+		status =  write_page_to_channel (channel, page, position,
+						 bytes_written_r, error);
+	} else {
+		status = g_io_channel_write_chars(channel,
+						  (const gchar*)page->data + position,
+						  n, bytes_written_r, error);
+	}
+
+	return status;
+}
+
+static gint
+bytes_left_till_metadata (struct httpd_client *client)
+{
+	assert(client != NULL);
+
+	if (client->metadata_requested &&
+	    client->current_page->size - client->current_position
+	    > client->metaint - client->metadata_fill)
+		return client->metaint - client->metadata_fill;
+
+	return -1;
+}
+
 static gboolean
 httpd_client_out_event(GIOChannel *source,
 		       G_GNUC_UNUSED GIOCondition condition, gpointer data)
@@ -453,6 +554,7 @@ httpd_client_out_event(GIOChannel *source,
 	GError *error = NULL;
 	GIOStatus status;
 	gsize bytes_written;
+	gint bytes_to_write;
 
 	g_mutex_lock(httpd->mutex);
 
@@ -471,14 +573,62 @@ httpd_client_out_event(GIOChannel *source,
 		client->current_position = 0;
 	}
 
-	status = write_page_to_channel(source, client->current_page,
-				       client->current_position,
-				       &bytes_written, &error);
+	bytes_to_write = bytes_left_till_metadata(client);
+
+	if (bytes_to_write == 0) {
+		gint metadata_to_write;
+
+		metadata_to_write = client->metadata_current_position;
+
+		if (!client->metadata_sent) {
+			status = write_page_to_channel(source,
+						       client->metadata,
+						       metadata_to_write,
+						       &bytes_written, &error);
+
+			client->metadata_current_position += bytes_written;
+
+			if (client->metadata->size
+			    - client->metadata_current_position == 0) {
+				client->metadata_fill = 0;
+				client->metadata_current_position = 0;
+				client->metadata_sent = TRUE;
+			}
+		} else {
+			struct page *empty_meta;
+			guchar empty_data = 0;
+
+			empty_meta = page_new_copy(&empty_data, 1);
+
+			status = write_page_to_channel(source,
+						       empty_meta,
+						       metadata_to_write,
+						       &bytes_written, &error);
+
+			client->metadata_current_position += bytes_written;
+
+			if (empty_meta->size
+			    - client->metadata_current_position == 0) {
+				client->metadata_fill = 0;
+				client->metadata_current_position = 0;
+			}
+		}
+
+		bytes_written = 0;
+	} else {
+		status = write_n_bytes_to_channel(source, client->current_page,
+						  client->current_position, bytes_to_write,
+						  &bytes_written, &error);
+	}
+
 	switch (status) {
 	case G_IO_STATUS_NORMAL:
 		client->current_position += bytes_written;
 		assert(client->current_position <= client->current_page->size);
 
+		if (client->metadata_requested)
+			client->metadata_fill += bytes_written;
+
 		if (client->current_position >= client->current_page->size) {
 			page_unref(client->current_page);
 			client->current_page = NULL;
@@ -539,3 +689,18 @@ httpd_client_send(struct httpd_client *client, struct page *page)
 			g_io_add_watch(client->channel, G_IO_OUT,
 				       httpd_client_out_event, client);
 }
+
+void
+httpd_client_send_metadata(struct httpd_client *client, struct page *page)
+{
+	if (client->metadata) {
+		page_unref(client->metadata);
+		client->metadata = NULL;
+	}
+
+	g_return_if_fail (page);
+
+	page_ref(page);
+	client->metadata = page;
+	client->metadata_sent = FALSE;
+}
diff --git a/src/output/httpd_client.h b/src/output/httpd_client.h
index c81cdb12f..80ab6bc2e 100644
--- a/src/output/httpd_client.h
+++ b/src/output/httpd_client.h
@@ -62,4 +62,10 @@ httpd_client_cancel(struct httpd_client *client);
 void
 httpd_client_send(struct httpd_client *client, struct page *page);
 
+/**
+ * Sends the passed metadata.
+ */
+void
+httpd_client_send_metadata(struct httpd_client *client, struct page *page);
+
 #endif
diff --git a/src/output/httpd_internal.h b/src/output/httpd_internal.h
index d19909c83..2257e27a2 100644
--- a/src/output/httpd_internal.h
+++ b/src/output/httpd_internal.h
@@ -81,6 +81,11 @@ struct httpd_output {
 	 */
 	struct page *header;
 
+	/**
+	 * The metadata, which is sent to every client.
+	 */
+	struct page *metadata;
+
 	/**
 	 * A linked list containing all clients which are currently
 	 * connected.
diff --git a/src/output/httpd_output_plugin.c b/src/output/httpd_output_plugin.c
index 60d17c520..c1f609489 100644
--- a/src/output/httpd_output_plugin.c
+++ b/src/output/httpd_output_plugin.c
@@ -24,6 +24,7 @@
 #include "encoder_list.h"
 #include "socket_util.h"
 #include "page.h"
+#include "icy_server.h"
 
 #include <assert.h>
 
@@ -83,6 +84,9 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
 	sin->sin_addr.s_addr = INADDR_ANY;
 	httpd->address_size = sizeof(*sin);
 
+	/* initialize metadata */
+	httpd->metadata = NULL;
+
 	/* initialize encoder */
 
 	httpd->encoder = encoder_init(encoder_plugin, param, error);
@@ -99,6 +103,9 @@ httpd_output_finish(void *data)
 {
 	struct httpd_output *httpd = data;
 
+	if (httpd->metadata)
+		page_unref(httpd->metadata);
+
 	encoder_finish(httpd->encoder);
 	g_mutex_free(httpd->mutex);
 	g_free(httpd);
@@ -114,6 +121,10 @@ httpd_client_add(struct httpd_output *httpd, int fd)
 	struct httpd_client *client = httpd_client_new(httpd, fd);
 
 	httpd->clients = g_list_prepend(httpd->clients, client);
+
+	/* pass metadata to client */
+	if (httpd->metadata)
+		httpd_client_send_metadata(client, httpd->metadata);
 }
 
 static gboolean
@@ -353,12 +364,36 @@ httpd_output_play(void *data, const void *chunk, size_t size, GError **error)
 	return size;
 }
 
+static void
+httpd_send_metadata(gpointer data, gpointer user_data)
+{
+	struct httpd_client *client = data;
+	struct page *icy_metadata = user_data;
+
+	httpd_client_send_metadata(client, icy_metadata);
+}
+
 static void
 httpd_output_tag(void *data, const struct tag *tag)
 {
 	struct httpd_output *httpd = data;
 
-	/* XXX add suport for icy-metadata */
+	if (httpd->metadata) {
+		page_unref (httpd->metadata);
+		httpd->metadata = NULL;
+	}
+
+	if (tag)
+		httpd->metadata = icy_server_metadata_page(tag, TAG_ITEM_ALBUM,
+							   TAG_ITEM_ARTIST,
+							   TAG_ITEM_TITLE,
+							   TAG_NUM_OF_ITEM_TYPES);
+
+	if (httpd->metadata) {
+		g_mutex_lock(httpd->mutex);
+		g_list_foreach(httpd->clients, httpd_send_metadata, httpd->metadata);
+		g_mutex_unlock(httpd->mutex);
+	}
 
 	encoder_tag(httpd->encoder, tag, NULL);
 }
-- 
cgit v1.2.3