aboutsummaryrefslogtreecommitdiffstats
path: root/src/output
diff options
context:
space:
mode:
authorMax Kellermann <max@duempel.org>2013-01-15 18:22:17 +0100
committerMax Kellermann <max@duempel.org>2013-01-15 20:20:51 +0100
commit70879356560d9b7f0343cc0f0cbd8331984cf96a (patch)
tree73b369f887a6f9d2773c3206eaeb5f4e3f7566fa /src/output
parent5822daa63de641419dcecd19e81f1c4190bd1cce (diff)
downloadmpd-70879356560d9b7f0343cc0f0cbd8331984cf96a.tar.gz
mpd-70879356560d9b7f0343cc0f0cbd8331984cf96a.tar.xz
mpd-70879356560d9b7f0343cc0f0cbd8331984cf96a.zip
output/httpd: convert to C++
Diffstat (limited to 'src/output')
-rw-r--r--src/output/HttpdClient.cxx592
-rw-r--r--src/output/HttpdClient.hxx215
-rw-r--r--src/output/HttpdInternal.hxx (renamed from src/output/httpd_internal.h)14
-rw-r--r--src/output/HttpdOutputPlugin.cxx (renamed from src/output/httpd_output_plugin.c)235
-rw-r--r--src/output/HttpdOutputPlugin.hxx (renamed from src/output/httpd_output_plugin.h)6
-rw-r--r--src/output/httpd_client.c764
-rw-r--r--src/output/httpd_client.h70
7 files changed, 910 insertions, 986 deletions
diff --git a/src/output/HttpdClient.cxx b/src/output/HttpdClient.cxx
new file mode 100644
index 000000000..33aad1919
--- /dev/null
+++ b/src/output/HttpdClient.cxx
@@ -0,0 +1,592 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "HttpdClient.hxx"
+#include "HttpdInternal.hxx"
+#include "util/fifo_buffer.h"
+#include "page.h"
+#include "icy_server.h"
+#include "glib_socket.h"
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "httpd_output"
+
+HttpdClient::~HttpdClient()
+{
+ if (state == RESPONSE) {
+ if (write_source_id != 0)
+ g_source_remove(write_source_id);
+
+ if (current_page != nullptr)
+ page_unref(current_page);
+
+ for (auto page : pages)
+ page_unref(page);
+ } else
+ fifo_buffer_free(input);
+
+ if (metadata)
+ page_unref(metadata);
+
+ g_source_remove(read_source_id);
+ g_io_channel_unref(channel);
+}
+
+void
+HttpdClient::Close()
+{
+ httpd_output_remove_client(httpd, this);
+}
+
+void
+HttpdClient::LockClose()
+{
+ const ScopeLock protect(httpd->mutex);
+ Close();
+}
+
+void
+HttpdClient::BeginResponse()
+{
+ assert(state != RESPONSE);
+
+ state = RESPONSE;
+ write_source_id = 0;
+ current_page = nullptr;
+
+ httpd_output_send_header(httpd, this);
+}
+
+/**
+ * Handle a line of the HTTP request.
+ */
+bool
+HttpdClient::HandleLine(const char *line)
+{
+ assert(state != RESPONSE);
+
+ if (state == REQUEST) {
+ if (strncmp(line, "GET /", 5) != 0) {
+ /* only GET is supported */
+ g_warning("malformed request line from client");
+ return false;
+ }
+
+ line = strchr(line + 5, ' ');
+ if (line == nullptr || strncmp(line + 1, "HTTP/", 5) != 0) {
+ /* HTTP/0.9 without request headers */
+ BeginResponse();
+ return true;
+ }
+
+ /* after the request line, request headers follow */
+ state = HEADERS;
+ return true;
+ } else {
+ if (*line == 0) {
+ /* empty line: request is finished */
+ BeginResponse();
+ return true;
+ }
+
+ if (g_ascii_strncasecmp(line, "Icy-MetaData: 1", 15) == 0) {
+ /* Send icy metadata */
+ metadata_requested = metadata_supported;
+ return true;
+ }
+
+ if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) {
+ /* Send as dlna */
+ dlna_streaming_requested = true;
+ /* metadata is not supported by dlna streaming, so disable it */
+ metadata_supported = false;
+ metadata_requested = false;
+ return true;
+ }
+
+ /* expect more request headers */
+ return true;
+ }
+}
+
+char *
+HttpdClient::ReadLine()
+{
+ assert(state != RESPONSE);
+
+ const ScopeLock protect(httpd->mutex);
+
+ size_t length;
+ const char *p = (const char *)fifo_buffer_read(input, &length);
+ if (p == nullptr)
+ /* empty input buffer */
+ return nullptr;
+
+ const char *newline = (const char *)memchr(p, '\n', length);
+ if (newline == nullptr)
+ /* incomplete line */
+ return nullptr;
+
+ char *line = g_strndup(p, newline - p);
+ fifo_buffer_consume(input, newline - p + 1);
+
+ /* remove trailing whitespace (e.g. '\r') */
+ return g_strchomp(line);
+}
+
+/**
+ * Sends the status line and response headers to the client.
+ */
+bool
+HttpdClient::SendResponse()
+{
+ char buffer[1024];
+ GError *error = nullptr;
+ GIOStatus status;
+ gsize bytes_written;
+
+ assert(state == RESPONSE);
+
+ if (dlna_streaming_requested) {
+ g_snprintf(buffer, sizeof(buffer),
+ "HTTP/1.1 206 OK\r\n"
+ "Content-Type: %s\r\n"
+ "Content-Length: 10000\r\n"
+ "Content-RangeX: 0-1000000/1000000\r\n"
+ "transferMode.dlna.org: Streaming\r\n"
+ "Accept-Ranges: bytes\r\n"
+ "Connection: close\r\n"
+ "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
+ "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n"
+ "\r\n",
+ httpd->content_type);
+
+ } else if (metadata_requested) {
+ gchar *metadata_header;
+
+ metadata_header =
+ icy_server_metadata_header(httpd->name, httpd->genre,
+ httpd->website,
+ httpd->content_type,
+ metaint);
+
+ g_strlcpy(buffer, metadata_header, sizeof(buffer));
+
+ g_free(metadata_header);
+
+ } else { /* revert to a normal HTTP request */
+ 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",
+ httpd->content_type);
+ }
+
+ status = g_io_channel_write_chars(channel,
+ buffer, strlen(buffer),
+ &bytes_written, &error);
+
+ switch (status) {
+ case G_IO_STATUS_NORMAL:
+ case G_IO_STATUS_AGAIN:
+ return true;
+
+ case G_IO_STATUS_EOF:
+ /* client has disconnected */
+
+ Close();
+ return false;
+
+ case G_IO_STATUS_ERROR:
+ /* I/O error */
+
+ g_warning("failed to write to client: %s", error->message);
+ g_error_free(error);
+
+ Close();
+ return false;
+ }
+
+ /* unreachable */
+ Close();
+ return false;
+}
+
+bool
+HttpdClient::Received()
+{
+ assert(state != RESPONSE);
+
+ char *line;
+ bool success;
+
+ while ((line = ReadLine()) != nullptr) {
+ success = HandleLine(line);
+ g_free(line);
+ if (!success) {
+ assert(state != RESPONSE);
+ return false;
+ }
+
+ if (state == RESPONSE) {
+ if (!fifo_buffer_is_empty(input)) {
+ g_warning("unexpected input from client");
+ return false;
+ }
+
+ fifo_buffer_free(input);
+
+ return SendResponse();
+ }
+ }
+
+ return true;
+}
+
+bool
+HttpdClient::Read()
+{
+ size_t max_length;
+ GError *error = nullptr;
+ GIOStatus status;
+ gsize bytes_read;
+
+ if (state == RESPONSE) {
+ /* the client has already sent the request, and he
+ must not send more */
+ char buffer[1];
+
+ status = g_io_channel_read_chars(channel, buffer,
+ sizeof(buffer), &bytes_read,
+ nullptr);
+ if (status == G_IO_STATUS_NORMAL)
+ g_warning("unexpected input from client");
+
+ return false;
+ }
+
+ char *p = (char *)fifo_buffer_write(input, &max_length);
+ if (p == nullptr) {
+ g_warning("buffer overflow");
+ return false;
+ }
+
+ status = g_io_channel_read_chars(channel, p, max_length,
+ &bytes_read, &error);
+ switch (status) {
+ case G_IO_STATUS_NORMAL:
+ fifo_buffer_append(input, bytes_read);
+ return Received();
+
+ case G_IO_STATUS_AGAIN:
+ /* try again later, after select() */
+ return true;
+
+ case G_IO_STATUS_EOF:
+ /* peer disconnected */
+ return false;
+
+ case G_IO_STATUS_ERROR:
+ /* I/O error */
+ g_warning("failed to read from client: %s",
+ error->message);
+ g_error_free(error);
+ return false;
+ }
+
+ /* unreachable */
+ return false;
+}
+
+static gboolean
+httpd_client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
+ gpointer data)
+{
+ HttpdClient *client = (HttpdClient *)data;
+
+ if (condition == G_IO_IN && client->Read()) {
+ return true;
+ } else {
+ client->LockClose();
+ return false;
+ }
+}
+
+HttpdClient::HttpdClient(httpd_output *_httpd, int _fd,
+ bool _metadata_supported)
+ :httpd(_httpd),
+ channel(g_io_channel_new_socket(_fd)),
+ input(fifo_buffer_new(4096)),
+ state(REQUEST),
+ dlna_streaming_requested(false),
+ metadata_supported(_metadata_supported),
+ metadata_requested(false), metadata_sent(true),
+ metaint(8192), /*TODO: just a std value */
+ metadata(nullptr),
+ metadata_current_position(0), metadata_fill(0)
+{
+ /* GLib is responsible for closing the file descriptor */
+ g_io_channel_set_close_on_unref(channel, true);
+ /* NULL encoding means the stream is binary safe */
+ g_io_channel_set_encoding(channel, nullptr, nullptr);
+ /* we prefer to do buffering */
+ g_io_channel_set_buffered(channel, false);
+
+ read_source_id = g_io_add_watch(channel,
+ GIOCondition(G_IO_IN|G_IO_ERR|G_IO_HUP),
+ httpd_client_in_event, this);
+}
+
+size_t
+HttpdClient::GetQueueSize() const
+{
+ if (state != RESPONSE)
+ return 0;
+
+ size_t size = 0;
+ for (auto page : pages)
+ size += page->size;
+ return size;
+}
+
+void
+HttpdClient::CancelQueue()
+{
+ if (state != RESPONSE)
+ return;
+
+ for (auto page : pages)
+ page_unref(page);
+ pages.clear();
+
+ if (write_source_id != 0 && current_page == nullptr) {
+ g_source_remove(write_source_id);
+ write_source_id = 0;
+ }
+}
+
+static GIOStatus
+write_page_to_channel(GIOChannel *channel,
+ const struct page *page, size_t position,
+ gsize *bytes_written_r, GError **error)
+{
+ assert(channel != nullptr);
+ assert(page != nullptr);
+ assert(position < page->size);
+
+ return g_io_channel_write_chars(channel,
+ (const gchar*)page->data + position,
+ page->size - position,
+ 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 != nullptr);
+ assert(page != nullptr);
+ 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;
+}
+
+int
+HttpdClient::GetBytesTillMetaData() const
+{
+ if (metadata_requested &&
+ current_page->size - current_position > metaint - metadata_fill)
+ return metaint - metadata_fill;
+
+ return -1;
+}
+
+inline bool
+HttpdClient::Write()
+{
+ GError *error = nullptr;
+ GIOStatus status;
+ gsize bytes_written;
+
+ const ScopeLock protect(httpd->mutex);
+
+ assert(state == RESPONSE);
+
+ if (write_source_id == 0)
+ /* another thread has removed the event source while
+ this thread was waiting for httpd->mutex */
+ return false;
+
+ if (current_page == nullptr) {
+ current_page = pages.front();
+ pages.pop_front();
+ current_position = 0;
+ }
+
+ const gint bytes_to_write = GetBytesTillMetaData();
+ if (bytes_to_write == 0) {
+ gint metadata_to_write;
+
+ metadata_to_write = metadata_current_position;
+
+ if (!metadata_sent) {
+ status = write_page_to_channel(channel,
+ metadata,
+ metadata_to_write,
+ &bytes_written, &error);
+
+ metadata_current_position += bytes_written;
+
+ if (metadata->size - metadata_current_position == 0) {
+ metadata_fill = 0;
+ metadata_current_position = 0;
+ 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(channel,
+ empty_meta,
+ metadata_to_write,
+ &bytes_written, &error);
+
+ metadata_current_position += bytes_written;
+
+ if (empty_meta->size - metadata_current_position == 0) {
+ metadata_fill = 0;
+ metadata_current_position = 0;
+ }
+ }
+
+ bytes_written = 0;
+ } else {
+ status = write_n_bytes_to_channel(channel, current_page,
+ current_position, bytes_to_write,
+ &bytes_written, &error);
+ }
+
+ switch (status) {
+ case G_IO_STATUS_NORMAL:
+ current_position += bytes_written;
+ assert(current_position <= current_page->size);
+
+ if (metadata_requested)
+ metadata_fill += bytes_written;
+
+ if (current_position >= current_page->size) {
+ page_unref(current_page);
+ current_page = nullptr;
+
+ if (pages.empty()) {
+ /* all pages are sent: remove the
+ event source */
+ write_source_id = 0;
+
+ return false;
+ }
+ }
+
+ return true;
+
+ case G_IO_STATUS_AGAIN:
+ return true;
+
+ case G_IO_STATUS_EOF:
+ /* client has disconnected */
+
+ Close();
+ return false;
+
+ case G_IO_STATUS_ERROR:
+ /* I/O error */
+
+ g_warning("failed to write to client: %s", error->message);
+ g_error_free(error);
+
+ Close();
+ return false;
+ }
+
+ /* unreachable */
+ Close();
+ return false;
+}
+
+static gboolean
+httpd_client_out_event(gcc_unused GIOChannel *source,
+ gcc_unused GIOCondition condition, gpointer data)
+{
+ assert(condition == G_IO_OUT);
+
+ HttpdClient *client = (HttpdClient *)data;
+ return client->Write();
+}
+
+void
+HttpdClient::PushPage(struct page *page)
+{
+ if (state != RESPONSE)
+ /* the client is still writing the HTTP request */
+ return;
+
+ page_ref(page);
+ pages.push_back(page);
+
+ if (write_source_id == 0)
+ write_source_id = g_io_add_watch(channel, G_IO_OUT,
+ httpd_client_out_event,
+ this);
+}
+
+void
+HttpdClient::PushMetaData(struct page *page)
+{
+ if (metadata) {
+ page_unref(metadata);
+ metadata = nullptr;
+ }
+
+ g_return_if_fail (page);
+
+ page_ref(page);
+ metadata = page;
+ metadata_sent = false;
+}
diff --git a/src/output/HttpdClient.hxx b/src/output/HttpdClient.hxx
new file mode 100644
index 000000000..d3ed2746d
--- /dev/null
+++ b/src/output/HttpdClient.hxx
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OUTPUT_HTTPD_CLIENT_HXX
+#define MPD_OUTPUT_HTTPD_CLIENT_HXX
+
+#include "gcc.h"
+
+#include <glib.h>
+
+#include <list>
+
+#include <stddef.h>
+
+struct httpd_output;
+struct page;
+
+class HttpdClient final {
+ /**
+ * The httpd output object this client is connected to.
+ */
+ httpd_output *const httpd;
+
+ /**
+ * The TCP socket.
+ */
+ GIOChannel *channel;
+
+ /**
+ * The GLib main loop source id for reading from the socket,
+ * and to detect errors.
+ */
+ guint read_source_id;
+
+ /**
+ * The GLib main loop source id for writing to the socket. If
+ * 0, then there is no event source currently (because there
+ * are no queued pages).
+ */
+ guint write_source_id;
+
+ /**
+ * For buffered reading. This pointer is only valid while the
+ * HTTP request is read.
+ */
+ struct fifo_buffer *input;
+
+ /**
+ * The current state of the client.
+ */
+ enum {
+ /** reading the request line */
+ REQUEST,
+
+ /** reading the request headers */
+ HEADERS,
+
+ /** sending the HTTP response */
+ RESPONSE,
+ } state;
+
+ /**
+ * A queue of #page objects to be sent to the client.
+ */
+ std::list<page *> pages;
+
+ /**
+ * The #page which is currently being sent to the client.
+ */
+ page *current_page;
+
+ /**
+ * The amount of bytes which were already sent from
+ * #current_page.
+ */
+ size_t current_position;
+
+ /**
+ * If DLNA streaming was an option.
+ */
+ bool dlna_streaming_requested;
+
+ /* ICY */
+
+ /**
+ * Do we support sending Icy-Metadata to the client? This is
+ * disabled if the httpd audio output uses encoder tags.
+ */
+ bool metadata_supported;
+
+ /**
+ * If we should sent icy metadata.
+ */
+ bool metadata_requested;
+
+ /**
+ * If the current metadata was already sent to the client.
+ */
+ bool 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.
+ */
+ 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;
+
+public:
+ /**
+ * @param httpd the HTTP output device
+ * @param fd the socket file descriptor
+ */
+ HttpdClient(httpd_output *httpd, int _fd, bool _metadata_supported);
+
+ /**
+ * Note: this does not remove the client from the
+ * #httpd_output object.
+ */
+ ~HttpdClient();
+
+ /**
+ * Frees the client and removes it from the server's client list.
+ */
+ void Close();
+
+ void LockClose();
+
+ /**
+ * Returns the total size of this client's page queue.
+ */
+ gcc_pure
+ size_t GetQueueSize() const;
+
+ /**
+ * Clears the page queue.
+ */
+ void CancelQueue();
+
+ bool Read();
+
+ /**
+ * Data has been received from the client and it is appended
+ * to the input buffer.
+ */
+ bool Received();
+
+ /**
+ * Check if a complete line of input is present in the input
+ * buffer, and duplicates it. It is removed from the input
+ * buffer. The return value has to be freed with g_free().
+ */
+ char *ReadLine();
+
+ /**
+ * Handle a line of the HTTP request.
+ */
+ bool HandleLine(const char *line);
+
+ /**
+ * Switch the client to the "RESPONSE" state.
+ */
+ void BeginResponse();
+
+ /**
+ * Sends the status line and response headers to the client.
+ */
+ bool SendResponse();
+
+ gcc_pure
+ int GetBytesTillMetaData() const;
+
+ bool Write();
+
+ /**
+ * Appends a page to the client's queue.
+ */
+ void PushPage(page *page);
+
+ /**
+ * Sends the passed metadata.
+ */
+ void PushMetaData(page *page);
+};
+
+#endif
diff --git a/src/output/httpd_internal.h b/src/output/HttpdInternal.hxx
index 5dcb8ab9b..601c43162 100644
--- a/src/output/httpd_internal.h
+++ b/src/output/HttpdInternal.hxx
@@ -25,14 +25,18 @@
#ifndef MPD_OUTPUT_HTTPD_INTERNAL_H
#define MPD_OUTPUT_HTTPD_INTERNAL_H
+#include "HttpdClient.hxx"
#include "output_internal.h"
#include "timer.h"
+#include "thread/Mutex.hxx"
#include <glib.h>
+#include <forward_list>
+
#include <stdbool.h>
-struct httpd_client;
+class HttpdClient;
struct httpd_output {
struct audio_output base;
@@ -65,7 +69,7 @@ struct httpd_output {
* This mutex protects the listener socket and the client
* list.
*/
- GMutex *mutex;
+ mutable Mutex mutex;
/**
* A #timer object to synchronize this output with the
@@ -105,7 +109,7 @@ struct httpd_output {
* A linked list containing all clients which are currently
* connected.
*/
- GList *clients;
+ std::forward_list<HttpdClient> clients;
/**
* A temporary buffer for the httpd_output_read_page()
@@ -125,7 +129,7 @@ struct httpd_output {
*/
void
httpd_output_remove_client(struct httpd_output *httpd,
- struct httpd_client *client);
+ HttpdClient *client);
/**
* Sends the encoder header to the client. This is called right after
@@ -133,6 +137,6 @@ httpd_output_remove_client(struct httpd_output *httpd,
*/
void
httpd_output_send_header(struct httpd_output *httpd,
- struct httpd_client *client);
+ HttpdClient *client);
#endif
diff --git a/src/output/httpd_output_plugin.c b/src/output/HttpdOutputPlugin.cxx
index 1d730df7f..c686104c1 100644
--- a/src/output/httpd_output_plugin.c
+++ b/src/output/HttpdOutputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * Copyright (C) 2003-2013 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,9 +18,9 @@
*/
#include "config.h"
-#include "httpd_output_plugin.h"
-#include "httpd_internal.h"
-#include "httpd_client.h"
+#include "HttpdOutputPlugin.hxx"
+#include "HttpdInternal.hxx"
+#include "HttpdClient.hxx"
#include "output_api.h"
#include "encoder_plugin.h"
#include "encoder_list.h"
@@ -60,9 +60,9 @@ httpd_output_quark(void)
*/
G_GNUC_PURE
static bool
-httpd_output_has_clients(const struct httpd_output *httpd)
+httpd_output_has_clients(const httpd_output *httpd)
{
- return httpd->clients != NULL;
+ return !httpd->clients.empty();
}
/**
@@ -70,12 +70,10 @@ httpd_output_has_clients(const struct httpd_output *httpd)
*/
G_GNUC_PURE
static bool
-httpd_output_lock_has_clients(const struct httpd_output *httpd)
+httpd_output_lock_has_clients(const httpd_output *httpd)
{
- g_mutex_lock(httpd->mutex);
- bool result = httpd_output_has_clients(httpd);
- g_mutex_unlock(httpd->mutex);
- return result;
+ const ScopeLock protect(httpd->mutex);
+ return httpd_output_has_clients(httpd);
}
static void
@@ -83,32 +81,28 @@ httpd_listen_in_event(int fd, const struct sockaddr *address,
size_t address_length, int uid, void *ctx);
static bool
-httpd_output_bind(struct httpd_output *httpd, GError **error_r)
+httpd_output_bind(httpd_output *httpd, GError **error_r)
{
httpd->open = false;
- g_mutex_lock(httpd->mutex);
- bool success = server_socket_open(httpd->server_socket, error_r);
- g_mutex_unlock(httpd->mutex);
-
- return success;
+ const ScopeLock protect(httpd->mutex);
+ return server_socket_open(httpd->server_socket, error_r);
}
static void
-httpd_output_unbind(struct httpd_output *httpd)
+httpd_output_unbind(httpd_output *httpd)
{
assert(!httpd->open);
- g_mutex_lock(httpd->mutex);
+ const ScopeLock protect(httpd->mutex);
server_socket_close(httpd->server_socket);
- g_mutex_unlock(httpd->mutex);
}
static struct audio_output *
httpd_output_init(const struct config_param *param,
GError **error)
{
- struct httpd_output *httpd = g_new(struct httpd_output, 1);
+ httpd_output *httpd = new httpd_output();
if (!ao_base_init(&httpd->base, &httpd_output_plugin, param, error)) {
g_free(httpd);
return NULL;
@@ -174,50 +168,44 @@ httpd_output_init(const struct config_param *param,
httpd->content_type = "application/octet-stream";
}
- httpd->mutex = g_mutex_new();
-
return &httpd->base;
}
static void
httpd_output_finish(struct audio_output *ao)
{
- struct httpd_output *httpd = (struct httpd_output *)ao;
+ httpd_output *httpd = (httpd_output *)ao;
if (httpd->metadata)
page_unref(httpd->metadata);
encoder_finish(httpd->encoder);
server_socket_free(httpd->server_socket);
- g_mutex_free(httpd->mutex);
ao_base_finish(&httpd->base);
- g_free(httpd);
+ delete httpd;
}
/**
- * Creates a new #httpd_client object and adds it into the
+ * Creates a new #HttpdClient object and adds it into the
* httpd_output.clients linked list.
*/
static void
-httpd_client_add(struct httpd_output *httpd, int fd)
+httpd_client_add(httpd_output *httpd, int fd)
{
- struct httpd_client *client =
- httpd_client_new(httpd, fd,
- httpd->encoder->plugin->tag == NULL);
-
- httpd->clients = g_list_prepend(httpd->clients, client);
+ httpd->clients.emplace_front(httpd, fd,
+ httpd->encoder->plugin->tag == NULL);
httpd->clients_cnt++;
/* pass metadata to client */
if (httpd->metadata)
- httpd_client_send_metadata(client, httpd->metadata);
+ httpd->clients.front().PushMetaData(httpd->metadata);
}
static void
httpd_listen_in_event(int fd, const struct sockaddr *address,
size_t address_length, G_GNUC_UNUSED int uid, void *ctx)
{
- struct httpd_output *httpd = ctx;
+ httpd_output *httpd = (httpd_output *)ctx;
/* the listener socket has become readable - a client has
connected */
@@ -238,7 +226,6 @@ httpd_listen_in_event(int fd, const struct sockaddr *address,
progname, hostaddr);
g_free(hostaddr);
close_socket(fd);
- g_mutex_unlock(httpd->mutex);
return;
}
@@ -249,7 +236,7 @@ httpd_listen_in_event(int fd, const struct sockaddr *address,
(void)address_length;
#endif /* HAVE_WRAP */
- g_mutex_lock(httpd->mutex);
+ const ScopeLock protect(httpd->mutex);
if (fd >= 0) {
/* can we allow additional client */
@@ -262,8 +249,6 @@ httpd_listen_in_event(int fd, const struct sockaddr *address,
} else if (fd < 0 && errno != EINTR) {
g_warning("accept() failed: %s", g_strerror(errno));
}
-
- g_mutex_unlock(httpd->mutex);
}
/**
@@ -271,7 +256,7 @@ httpd_listen_in_event(int fd, const struct sockaddr *address,
* as a new #page object.
*/
static struct page *
-httpd_output_read_page(struct httpd_output *httpd)
+httpd_output_read_page(httpd_output *httpd)
{
if (httpd->unflushed_input >= 65536) {
/* we have fed a lot of input into the encoder, but it
@@ -301,7 +286,7 @@ httpd_output_read_page(struct httpd_output *httpd)
}
static bool
-httpd_output_encoder_open(struct httpd_output *httpd,
+httpd_output_encoder_open(httpd_output *httpd,
struct audio_format *audio_format,
GError **error)
{
@@ -321,7 +306,7 @@ httpd_output_encoder_open(struct httpd_output *httpd,
static bool
httpd_output_enable(struct audio_output *ao, GError **error_r)
{
- struct httpd_output *httpd = (struct httpd_output *)ao;
+ httpd_output *httpd = (httpd_output *)ao;
return httpd_output_bind(httpd, error_r);
}
@@ -329,7 +314,7 @@ httpd_output_enable(struct audio_output *ao, GError **error_r)
static void
httpd_output_disable(struct audio_output *ao)
{
- struct httpd_output *httpd = (struct httpd_output *)ao;
+ httpd_output *httpd = (httpd_output *)ao;
httpd_output_unbind(httpd);
}
@@ -338,82 +323,75 @@ static bool
httpd_output_open(struct audio_output *ao, struct audio_format *audio_format,
GError **error)
{
- struct httpd_output *httpd = (struct httpd_output *)ao;
+ httpd_output *httpd = (httpd_output *)ao;
+
+ assert(httpd->clients.empty());
- g_mutex_lock(httpd->mutex);
+ const ScopeLock protect(httpd->mutex);
/* open the encoder */
- if (!httpd_output_encoder_open(httpd, audio_format, error)) {
- g_mutex_unlock(httpd->mutex);
+ if (!httpd_output_encoder_open(httpd, audio_format, error))
return false;
- }
/* initialize other attributes */
- httpd->clients = NULL;
httpd->clients_cnt = 0;
httpd->timer = timer_new(audio_format);
httpd->open = true;
- g_mutex_unlock(httpd->mutex);
return true;
}
static void
-httpd_client_delete(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct httpd_client *client = data;
-
- httpd_client_free(client);
-}
-
-static void
httpd_output_close(struct audio_output *ao)
{
- struct httpd_output *httpd = (struct httpd_output *)ao;
+ httpd_output *httpd = (httpd_output *)ao;
- g_mutex_lock(httpd->mutex);
+ const ScopeLock protect(httpd->mutex);
httpd->open = false;
timer_free(httpd->timer);
- g_list_foreach(httpd->clients, httpd_client_delete, NULL);
- g_list_free(httpd->clients);
+ httpd->clients.clear();
if (httpd->header != NULL)
page_unref(httpd->header);
encoder_close(httpd->encoder);
-
- g_mutex_unlock(httpd->mutex);
}
void
-httpd_output_remove_client(struct httpd_output *httpd,
- struct httpd_client *client)
+httpd_output_remove_client(httpd_output *httpd, HttpdClient *client)
{
assert(httpd != NULL);
+ assert(httpd->clients_cnt > 0);
assert(client != NULL);
- httpd->clients = g_list_remove(httpd->clients, client);
- httpd->clients_cnt--;
+ for (auto prev = httpd->clients.before_begin(), i = std::next(prev);;
+ prev = i, i = std::next(prev)) {
+ assert(i != httpd->clients.end());
+ if (&*i == client) {
+ httpd->clients.erase_after(prev);
+ httpd->clients_cnt--;
+ break;
+ }
+ }
}
void
-httpd_output_send_header(struct httpd_output *httpd,
- struct httpd_client *client)
+httpd_output_send_header(httpd_output *httpd, HttpdClient *client)
{
if (httpd->header != NULL)
- httpd_client_send(client, httpd->header);
+ client->PushPage(httpd->header);
}
static unsigned
httpd_output_delay(struct audio_output *ao)
{
- struct httpd_output *httpd = (struct httpd_output *)ao;
+ httpd_output *httpd = (httpd_output *)ao;
if (!httpd_output_lock_has_clients(httpd) && httpd->base.pause) {
/* if there's no client and this output is paused,
@@ -433,51 +411,35 @@ httpd_output_delay(struct audio_output *ao)
: 0;
}
-static void
-httpd_client_check_queue(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct httpd_client *client = data;
-
- if (httpd_client_queue_size(client) > 256 * 1024) {
- g_debug("client is too slow, flushing its queue");
- httpd_client_cancel(client);
- }
-}
-
-static void
-httpd_client_send_page(gpointer data, gpointer user_data)
-{
- struct httpd_client *client = data;
- struct page *page = user_data;
-
- httpd_client_send(client, page);
-}
-
/**
* Broadcasts a page struct to all clients.
*/
static void
-httpd_output_broadcast_page(struct httpd_output *httpd, struct page *page)
+httpd_output_broadcast_page(httpd_output *httpd, struct page *page)
{
assert(page != NULL);
- g_mutex_lock(httpd->mutex);
- g_list_foreach(httpd->clients, httpd_client_send_page, page);
- g_mutex_unlock(httpd->mutex);
+ const ScopeLock protect(httpd->mutex);
+ for (auto &client : httpd->clients)
+ client.PushPage(page);
}
/**
* Broadcasts data from the encoder to all clients.
*/
static void
-httpd_output_encoder_to_clients(struct httpd_output *httpd)
+httpd_output_encoder_to_clients(httpd_output *httpd)
{
- struct page *page;
-
- g_mutex_lock(httpd->mutex);
- g_list_foreach(httpd->clients, httpd_client_check_queue, NULL);
- g_mutex_unlock(httpd->mutex);
+ httpd->mutex.lock();
+ for (auto &client : httpd->clients) {
+ if (client.GetQueueSize() > 256 * 1024) {
+ g_debug("client is too slow, flushing its queue");
+ client.CancelQueue();
+ }
+ }
+ httpd->mutex.unlock();
+ struct page *page;
while ((page = httpd_output_read_page(httpd)) != NULL) {
httpd_output_broadcast_page(httpd, page);
page_unref(page);
@@ -485,7 +447,7 @@ httpd_output_encoder_to_clients(struct httpd_output *httpd)
}
static bool
-httpd_output_encode_and_play(struct httpd_output *httpd,
+httpd_output_encode_and_play(httpd_output *httpd,
const void *chunk, size_t size, GError **error)
{
if (!encoder_write(httpd->encoder, chunk, size, error))
@@ -502,7 +464,7 @@ static size_t
httpd_output_play(struct audio_output *ao, const void *chunk, size_t size,
GError **error_r)
{
- struct httpd_output *httpd = (struct httpd_output *)ao;
+ httpd_output *httpd = (httpd_output *)ao;
if (httpd_output_lock_has_clients(httpd)) {
if (!httpd_output_encode_and_play(httpd, chunk, size, error_r))
@@ -519,10 +481,10 @@ httpd_output_play(struct audio_output *ao, const void *chunk, size_t size,
static bool
httpd_output_pause(struct audio_output *ao)
{
- struct httpd_output *httpd = (struct httpd_output *)ao;
+ httpd_output *httpd = (httpd_output *)ao;
if (httpd_output_lock_has_clients(httpd)) {
- static const char silence[1020];
+ static const char silence[1020] = { 0 };
return httpd_output_play(ao, silence, sizeof(silence),
NULL) > 0;
} else {
@@ -531,18 +493,9 @@ httpd_output_pause(struct audio_output *ao)
}
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(struct audio_output *ao, const struct tag *tag)
{
- struct httpd_output *httpd = (struct httpd_output *)ao;
+ httpd_output *httpd = (httpd_output *)ao;
assert(tag != NULL);
@@ -581,43 +534,37 @@ httpd_output_tag(struct audio_output *ao, const struct tag *tag)
TAG_ARTIST, TAG_TITLE,
TAG_NUM_OF_ITEM_TYPES);
if (httpd->metadata != NULL) {
- g_mutex_lock(httpd->mutex);
- g_list_foreach(httpd->clients,
- httpd_send_metadata, httpd->metadata);
- g_mutex_unlock(httpd->mutex);
+ const ScopeLock protect(httpd->mutex);
+ for (auto &client : httpd->clients)
+ client.PushMetaData(httpd->metadata);
}
}
}
static void
-httpd_client_cancel_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct httpd_client *client = data;
-
- httpd_client_cancel(client);
-}
-
-static void
httpd_output_cancel(struct audio_output *ao)
{
- struct httpd_output *httpd = (struct httpd_output *)ao;
+ httpd_output *httpd = (httpd_output *)ao;
- g_mutex_lock(httpd->mutex);
- g_list_foreach(httpd->clients, httpd_client_cancel_callback, NULL);
- g_mutex_unlock(httpd->mutex);
+ const ScopeLock protect(httpd->mutex);
+ for (auto &client : httpd->clients)
+ client.CancelQueue();
}
const struct audio_output_plugin httpd_output_plugin = {
- .name = "httpd",
- .init = httpd_output_init,
- .finish = httpd_output_finish,
- .enable = httpd_output_enable,
- .disable = httpd_output_disable,
- .open = httpd_output_open,
- .close = httpd_output_close,
- .delay = httpd_output_delay,
- .send_tag = httpd_output_tag,
- .play = httpd_output_play,
- .pause = httpd_output_pause,
- .cancel = httpd_output_cancel,
+ "httpd",
+ nullptr,
+ httpd_output_init,
+ httpd_output_finish,
+ httpd_output_enable,
+ httpd_output_disable,
+ httpd_output_open,
+ httpd_output_close,
+ httpd_output_delay,
+ httpd_output_tag,
+ httpd_output_play,
+ nullptr,
+ httpd_output_cancel,
+ httpd_output_pause,
+ nullptr,
};
diff --git a/src/output/httpd_output_plugin.h b/src/output/HttpdOutputPlugin.hxx
index d0eb1533f..c74d2bd4a 100644
--- a/src/output/httpd_output_plugin.h
+++ b/src/output/HttpdOutputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * Copyright (C) 2003-2013 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,8 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#ifndef MPD_HTTPD_OUTPUT_PLUGIN_H
-#define MPD_HTTPD_OUTPUT_PLUGIN_H
+#ifndef MPD_HTTPD_OUTPUT_PLUGIN_HXX
+#define MPD_HTTPD_OUTPUT_PLUGIN_HXX
extern const struct audio_output_plugin httpd_output_plugin;
diff --git a/src/output/httpd_client.c b/src/output/httpd_client.c
deleted file mode 100644
index 781858109..000000000
--- a/src/output/httpd_client.c
+++ /dev/null
@@ -1,764 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "httpd_client.h"
-#include "httpd_internal.h"
-#include "util/fifo_buffer.h"
-#include "page.h"
-#include "icy_server.h"
-#include "glib_socket.h"
-
-#include <stdbool.h>
-#include <assert.h>
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "httpd_output"
-
-struct httpd_client {
- /**
- * The httpd output object this client is connected to.
- */
- struct httpd_output *httpd;
-
- /**
- * The TCP socket.
- */
- GIOChannel *channel;
-
- /**
- * The GLib main loop source id for reading from the socket,
- * and to detect errors.
- */
- guint read_source_id;
-
- /**
- * The GLib main loop source id for writing to the socket. If
- * 0, then there is no event source currently (because there
- * are no queued pages).
- */
- guint write_source_id;
-
- /**
- * For buffered reading. This pointer is only valid while the
- * HTTP request is read.
- */
- struct fifo_buffer *input;
-
- /**
- * The current state of the client.
- */
- enum {
- /** reading the request line */
- REQUEST,
-
- /** reading the request headers */
- HEADERS,
-
- /** sending the HTTP response */
- RESPONSE,
- } state;
-
- /**
- * A queue of #page objects to be sent to the client.
- */
- GQueue *pages;
-
- /**
- * The #page which is currently being sent to the client.
- */
- struct page *current_page;
-
- /**
- * The amount of bytes which were already sent from
- * #current_page.
- */
- size_t current_position;
-
- /**
- * If DLNA streaming was an option.
- */
- bool dlna_streaming_requested;
-
- /* ICY */
-
- /**
- * Do we support sending Icy-Metadata to the client? This is
- * disabled if the httpd audio output uses encoder tags.
- */
- bool metadata_supported;
-
- /**
- * If we should sent icy metadata.
- */
- bool metadata_requested;
-
- /**
- * If the current metadata was already sent to the client.
- */
- bool 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
-httpd_client_unref_page(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct page *page = data;
-
- page_unref(page);
-}
-
-void
-httpd_client_free(struct httpd_client *client)
-{
- assert(client != NULL);
-
- if (client->state == RESPONSE) {
- if (client->write_source_id != 0)
- g_source_remove(client->write_source_id);
-
- if (client->current_page != NULL)
- page_unref(client->current_page);
-
- g_queue_foreach(client->pages, httpd_client_unref_page, NULL);
- g_queue_free(client->pages);
- } 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);
-}
-
-/**
- * Frees the client and removes it from the server's client list.
- */
-static void
-httpd_client_close(struct httpd_client *client)
-{
- assert(client != NULL);
-
- httpd_output_remove_client(client->httpd, client);
- httpd_client_free(client);
-}
-
-/**
- * Switch the client to the "RESPONSE" state.
- */
-static void
-httpd_client_begin_response(struct httpd_client *client)
-{
- assert(client != NULL);
- assert(client->state != RESPONSE);
-
- client->state = RESPONSE;
- client->write_source_id = 0;
- client->pages = g_queue_new();
- client->current_page = NULL;
-
- httpd_output_send_header(client->httpd, client);
-}
-
-/**
- * Handle a line of the HTTP request.
- */
-static bool
-httpd_client_handle_line(struct httpd_client *client, const char *line)
-{
- assert(client->state != RESPONSE);
-
- if (client->state == REQUEST) {
- if (strncmp(line, "GET /", 5) != 0) {
- /* only GET is supported */
- g_warning("malformed request line from client");
- return false;
- }
-
- line = strchr(line + 5, ' ');
- if (line == NULL || strncmp(line + 1, "HTTP/", 5) != 0) {
- /* HTTP/0.9 without request headers */
- httpd_client_begin_response(client);
- return true;
- }
-
- /* after the request line, request headers follow */
- client->state = HEADERS;
- return true;
- } else {
- if (*line == 0) {
- /* empty line: request is finished */
- httpd_client_begin_response(client);
- return true;
- }
-
- if (g_ascii_strncasecmp(line, "Icy-MetaData: 1", 15) == 0) {
- /* Send icy metadata */
- client->metadata_requested =
- client->metadata_supported;
- return true;
- }
-
- if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) {
- /* Send as dlna */
- client->dlna_streaming_requested = true;
- /* metadata is not supported by dlna streaming, so disable it */
- client->metadata_supported = false;
- client->metadata_requested = false;
- return true;
- }
-
- /* expect more request headers */
- return true;
- }
-}
-
-/**
- * Check if a complete line of input is present in the input buffer,
- * and duplicates it. It is removed from the input buffer. The
- * return value has to be freed with g_free().
- */
-static char *
-httpd_client_read_line(struct httpd_client *client)
-{
- assert(client != NULL);
- assert(client->state != RESPONSE);
-
- const char *p, *newline;
- size_t length;
- char *line;
-
- p = fifo_buffer_read(client->input, &length);
- if (p == NULL)
- /* empty input buffer */
- return NULL;
-
- newline = memchr(p, '\n', length);
- if (newline == NULL)
- /* incomplete line */
- return NULL;
-
- line = g_strndup(p, newline - p);
- fifo_buffer_consume(client->input, newline - p + 1);
-
- /* remove trailing whitespace (e.g. '\r') */
- return g_strchomp(line);
-}
-
-/**
- * Sends the status line and response headers to the client.
- */
-static bool
-httpd_client_send_response(struct httpd_client *client)
-{
- char buffer[1024];
- GError *error = NULL;
- GIOStatus status;
- gsize bytes_written;
-
- assert(client != NULL);
- assert(client->state == RESPONSE);
-
- if (client->dlna_streaming_requested) {
- g_snprintf(buffer, sizeof(buffer),
- "HTTP/1.1 206 OK\r\n"
- "Content-Type: %s\r\n"
- "Content-Length: 10000\r\n"
- "Content-RangeX: 0-1000000/1000000\r\n"
- "transferMode.dlna.org: Streaming\r\n"
- "Accept-Ranges: bytes\r\n"
- "Connection: close\r\n"
- "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
- "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n"
- "\r\n",
- client->httpd->content_type);
-
- } else if (client->metadata_requested) {
- gchar *metadata_header;
-
- metadata_header = icy_server_metadata_header(
- client->httpd->name,
- client->httpd->genre,
- client->httpd->website,
- client->httpd->content_type,
- client->metaint);
-
- g_strlcpy(buffer, metadata_header, sizeof(buffer));
-
- g_free(metadata_header);
-
- } else { /* revert to a normal HTTP request */
- 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);
- }
-
- 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:
- return true;
-
- case G_IO_STATUS_EOF:
- /* client has disconnected */
-
- httpd_client_close(client);
- return false;
-
- case G_IO_STATUS_ERROR:
- /* I/O error */
-
- g_warning("failed to write to client: %s", error->message);
- g_error_free(error);
-
- httpd_client_close(client);
- return false;
- }
-
- /* unreachable */
- httpd_client_close(client);
- return false;
-}
-
-/**
- * Data has been received from the client and it is appended to the
- * input buffer.
- */
-static bool
-httpd_client_received(struct httpd_client *client)
-{
- assert(client != NULL);
- assert(client->state != RESPONSE);
-
- char *line;
- bool success;
-
- while ((line = httpd_client_read_line(client)) != NULL) {
- success = httpd_client_handle_line(client, line);
- g_free(line);
- if (!success) {
- assert(client->state != RESPONSE);
- return false;
- }
-
- if (client->state == RESPONSE) {
- if (!fifo_buffer_is_empty(client->input)) {
- g_warning("unexpected input from client");
- return false;
- }
-
- fifo_buffer_free(client->input);
-
- return httpd_client_send_response(client);
- }
- }
-
- return true;
-}
-
-static bool
-httpd_client_read(struct httpd_client *client)
-{
- char *p;
- size_t max_length;
- GError *error = NULL;
- GIOStatus status;
- gsize bytes_read;
-
- if (client->state == RESPONSE) {
- /* the client has already sent the request, and he
- must not send more */
- char buffer[1];
-
- status = g_io_channel_read_chars(client->channel, buffer,
- sizeof(buffer), &bytes_read,
- NULL);
- if (status == G_IO_STATUS_NORMAL)
- g_warning("unexpected input from client");
-
- return false;
- }
-
- p = fifo_buffer_write(client->input, &max_length);
- if (p == NULL) {
- g_warning("buffer overflow");
- return false;
- }
-
- status = g_io_channel_read_chars(client->channel, p, max_length,
- &bytes_read, &error);
- switch (status) {
- case G_IO_STATUS_NORMAL:
- fifo_buffer_append(client->input, bytes_read);
- return httpd_client_received(client);
-
- case G_IO_STATUS_AGAIN:
- /* try again later, after select() */
- return true;
-
- case G_IO_STATUS_EOF:
- /* peer disconnected */
- return false;
-
- case G_IO_STATUS_ERROR:
- /* I/O error */
- g_warning("failed to read from client: %s",
- error->message);
- g_error_free(error);
- return false;
- }
-
- /* unreachable */
- return false;
-}
-
-static gboolean
-httpd_client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
- gpointer data)
-{
- struct httpd_client *client = data;
- struct httpd_output *httpd = client->httpd;
- bool ret;
-
- g_mutex_lock(httpd->mutex);
-
- if (condition == G_IO_IN && httpd_client_read(client)) {
- ret = true;
- } else {
- httpd_client_close(client);
- ret = false;
- }
-
- g_mutex_unlock(httpd->mutex);
-
- return ret;
-}
-
-struct httpd_client *
-httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported)
-{
- struct httpd_client *client = g_new(struct httpd_client, 1);
-
- client->httpd = httpd;
-
- client->channel = g_io_channel_new_socket(fd);
-
- /* GLib is responsible for closing the file descriptor */
- g_io_channel_set_close_on_unref(client->channel, true);
- /* NULL encoding means the stream is binary safe */
- g_io_channel_set_encoding(client->channel, NULL, NULL);
- /* we prefer to do buffering */
- g_io_channel_set_buffered(client->channel, false);
-
- client->read_source_id = g_io_add_watch(client->channel,
- G_IO_IN|G_IO_ERR|G_IO_HUP,
- httpd_client_in_event, client);
-
- client->input = fifo_buffer_new(4096);
- client->state = REQUEST;
-
- client->dlna_streaming_requested = false;
- client->metadata_supported = metadata_supported;
- 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;
-}
-
-static void
-httpd_client_add_page_size(gpointer data, gpointer user_data)
-{
- struct page *page = data;
- size_t *size = user_data;
-
- *size += page->size;
-}
-
-size_t
-httpd_client_queue_size(const struct httpd_client *client)
-{
- size_t size = 0;
-
- if (client->state != RESPONSE)
- return 0;
-
- g_queue_foreach(client->pages, httpd_client_add_page_size, &size);
- return size;
-}
-
-void
-httpd_client_cancel(struct httpd_client *client)
-{
- if (client->state != RESPONSE)
- return;
-
- g_queue_foreach(client->pages, httpd_client_unref_page, NULL);
- g_queue_clear(client->pages);
-
- if (client->write_source_id != 0 && client->current_page == NULL) {
- g_source_remove(client->write_source_id);
- client->write_source_id = 0;
- }
-}
-
-static GIOStatus
-write_page_to_channel(GIOChannel *channel,
- const struct page *page, size_t position,
- gsize *bytes_written_r, GError **error)
-{
- assert(channel != NULL);
- assert(page != NULL);
- assert(position < page->size);
-
- return g_io_channel_write_chars(channel,
- (const gchar*)page->data + position,
- page->size - position,
- 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)
-{
- struct httpd_client *client = data;
- struct httpd_output *httpd = client->httpd;
- GError *error = NULL;
- GIOStatus status;
- gsize bytes_written;
- gint bytes_to_write;
-
- g_mutex_lock(httpd->mutex);
-
- assert(condition == G_IO_OUT);
- assert(client->state == RESPONSE);
-
- if (client->write_source_id == 0) {
- /* another thread has removed the event source while
- this thread was waiting for httpd->mutex */
- g_mutex_unlock(httpd->mutex);
- return false;
- }
-
- if (client->current_page == NULL) {
- client->current_page = g_queue_pop_head(client->pages);
- client->current_position = 0;
- }
-
- 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;
-
- if (g_queue_is_empty(client->pages)) {
- /* all pages are sent: remove the
- event source */
- client->write_source_id = 0;
-
- g_mutex_unlock(httpd->mutex);
- return false;
- }
- }
-
- g_mutex_unlock(httpd->mutex);
- return true;
-
- case G_IO_STATUS_AGAIN:
- g_mutex_unlock(httpd->mutex);
- return true;
-
- case G_IO_STATUS_EOF:
- /* client has disconnected */
-
- httpd_client_close(client);
- g_mutex_unlock(httpd->mutex);
- return false;
-
- case G_IO_STATUS_ERROR:
- /* I/O error */
-
- g_warning("failed to write to client: %s", error->message);
- g_error_free(error);
-
- httpd_client_close(client);
- g_mutex_unlock(httpd->mutex);
- return false;
- }
-
- /* unreachable */
- httpd_client_close(client);
- g_mutex_unlock(httpd->mutex);
- return false;
-}
-
-void
-httpd_client_send(struct httpd_client *client, struct page *page)
-{
- if (client->state != RESPONSE)
- /* the client is still writing the HTTP request */
- return;
-
- page_ref(page);
- g_queue_push_tail(client->pages, page);
-
- if (client->write_source_id == 0)
- client->write_source_id =
- 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
deleted file mode 100644
index f0df829db..000000000
--- a/src/output/httpd_client.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_OUTPUT_HTTPD_CLIENT_H
-#define MPD_OUTPUT_HTTPD_CLIENT_H
-
-#include <stdbool.h>
-#include <stddef.h>
-
-struct httpd_client;
-struct httpd_output;
-struct page;
-
-/**
- * Creates a new #httpd_client object
- *
- * @param httpd the HTTP output device
- * @param fd the socket file descriptor
- */
-struct httpd_client *
-httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported);
-
-/**
- * Frees memory and resources allocated by the #httpd_client object.
- * This does not remove it from the #httpd_output object.
- */
-void
-httpd_client_free(struct httpd_client *client);
-
-/**
- * Returns the total size of this client's page queue.
- */
-size_t
-httpd_client_queue_size(const struct httpd_client *client);
-
-/**
- * Clears the page queue.
- */
-void
-httpd_client_cancel(struct httpd_client *client);
-
-/**
- * Appends a page to the client's queue.
- */
-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