From 70879356560d9b7f0343cc0f0cbd8331984cf96a Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 15 Jan 2013 18:22:17 +0100 Subject: output/httpd: convert to C++ --- Makefile.am | 7 +- src/OutputList.cxx | 2 +- src/output/HttpdClient.cxx | 592 ++++++++++++++++++++++++++++++ src/output/HttpdClient.hxx | 215 +++++++++++ src/output/HttpdInternal.hxx | 142 ++++++++ src/output/HttpdOutputPlugin.cxx | 570 +++++++++++++++++++++++++++++ src/output/HttpdOutputPlugin.hxx | 25 ++ src/output/httpd_client.c | 764 --------------------------------------- src/output/httpd_client.h | 70 ---- src/output/httpd_internal.h | 138 ------- src/output/httpd_output_plugin.c | 623 ------------------------------- src/output/httpd_output_plugin.h | 25 -- 12 files changed, 1548 insertions(+), 1625 deletions(-) create mode 100644 src/output/HttpdClient.cxx create mode 100644 src/output/HttpdClient.hxx create mode 100644 src/output/HttpdInternal.hxx create mode 100644 src/output/HttpdOutputPlugin.cxx create mode 100644 src/output/HttpdOutputPlugin.hxx delete mode 100644 src/output/httpd_client.c delete mode 100644 src/output/httpd_client.h delete mode 100644 src/output/httpd_internal.h delete mode 100644 src/output/httpd_output_plugin.c delete mode 100644 src/output/httpd_output_plugin.h diff --git a/Makefile.am b/Makefile.am index d4f4fdc62..371c43c2b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -101,8 +101,6 @@ mpd_headers = \ src/AudioCompress/compress.h \ src/path.h \ src/open.h \ - src/output/httpd_client.h \ - src/output/httpd_internal.h \ src/page.h \ src/Playlist.hxx \ src/playlist_error.h \ @@ -899,8 +897,9 @@ endif if ENABLE_HTTPD_OUTPUT liboutput_plugins_a_SOURCES += \ src/icy_server.c \ - src/output/httpd_client.c \ - src/output/httpd_output_plugin.c src/output/httpd_output_plugin.h + src/output/HttpdInternal.hxx \ + src/output/HttpdClient.cxx src/output/HttpdClient.hxx \ + src/output/HttpdOutputPlugin.cxx src/output/HttpdOutputPlugin.hxx endif if ENABLE_SOLARIS_OUTPUT diff --git a/src/OutputList.cxx b/src/OutputList.cxx index 3e469385f..df064219f 100644 --- a/src/OutputList.cxx +++ b/src/OutputList.cxx @@ -24,7 +24,7 @@ #include "output/ao_output_plugin.h" #include "output/ffado_output_plugin.h" #include "output/fifo_output_plugin.h" -#include "output/httpd_output_plugin.h" +#include "output/HttpdOutputPlugin.hxx" #include "output/jack_output_plugin.h" #include "output/mvp_output_plugin.h" #include "output/null_output_plugin.h" 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 +#include + +#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 + +#include + +#include + +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 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/HttpdInternal.hxx b/src/output/HttpdInternal.hxx new file mode 100644 index 000000000..601c43162 --- /dev/null +++ b/src/output/HttpdInternal.hxx @@ -0,0 +1,142 @@ +/* + * 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. + */ + +/** \file + * + * Internal declarations for the "httpd" audio output plugin. + */ + +#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 + +#include + +#include + +class HttpdClient; + +struct httpd_output { + struct audio_output base; + + /** + * True if the audio output is open and accepts client + * connections. + */ + bool open; + + /** + * The configured encoder plugin. + */ + struct encoder *encoder; + + /** + * Number of bytes which were fed into the encoder, without + * ever receiving new output. This is used to estimate + * whether MPD should manually flush the encoder, to avoid + * buffer underruns in the client. + */ + size_t unflushed_input; + + /** + * The MIME type produced by the #encoder. + */ + const char *content_type; + + /** + * This mutex protects the listener socket and the client + * list. + */ + mutable Mutex mutex; + + /** + * A #timer object to synchronize this output with the + * wallclock. + */ + struct timer *timer; + + /** + * The listener socket. + */ + struct server_socket *server_socket; + + /** + * The header page, which is sent to every client on connect. + */ + struct page *header; + + /** + * The metadata, which is sent to every client. + */ + struct page *metadata; + + /** + * The configured name. + */ + char const *name; + /** + * The configured genre. + */ + char const *genre; + /** + * The configured website address. + */ + char const *website; + + /** + * A linked list containing all clients which are currently + * connected. + */ + std::forward_list clients; + + /** + * A temporary buffer for the httpd_output_read_page() + * function. + */ + char buffer[32768]; + + /** + * The maximum and current number of clients connected + * at the same time. + */ + guint clients_max, clients_cnt; +}; + +/** + * Removes a client from the httpd_output.clients linked list. + */ +void +httpd_output_remove_client(struct httpd_output *httpd, + HttpdClient *client); + +/** + * Sends the encoder header to the client. This is called right after + * the response headers have been sent. + */ +void +httpd_output_send_header(struct httpd_output *httpd, + HttpdClient *client); + +#endif diff --git a/src/output/HttpdOutputPlugin.cxx b/src/output/HttpdOutputPlugin.cxx new file mode 100644 index 000000000..c686104c1 --- /dev/null +++ b/src/output/HttpdOutputPlugin.cxx @@ -0,0 +1,570 @@ +/* + * 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. + */ + +#include "config.h" +#include "HttpdOutputPlugin.hxx" +#include "HttpdInternal.hxx" +#include "HttpdClient.hxx" +#include "output_api.h" +#include "encoder_plugin.h" +#include "encoder_list.h" +#include "resolver.h" +#include "page.h" +#include "icy_server.h" +#include "fd_util.h" +#include "server_socket.h" + +#include + +#include +#include +#include + +#ifdef HAVE_LIBWRAP +#include /* needed for AF_UNIX */ +#include +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "httpd_output" + +/** + * The quark used for GError.domain. + */ +static inline GQuark +httpd_output_quark(void) +{ + return g_quark_from_static_string("httpd_output"); +} + +/** + * Check whether there is at least one client. + * + * Caller must lock the mutex. + */ +G_GNUC_PURE +static bool +httpd_output_has_clients(const httpd_output *httpd) +{ + return !httpd->clients.empty(); +} + +/** + * Check whether there is at least one client. + */ +G_GNUC_PURE +static bool +httpd_output_lock_has_clients(const httpd_output *httpd) +{ + const ScopeLock protect(httpd->mutex); + return httpd_output_has_clients(httpd); +} + +static void +httpd_listen_in_event(int fd, const struct sockaddr *address, + size_t address_length, int uid, void *ctx); + +static bool +httpd_output_bind(httpd_output *httpd, GError **error_r) +{ + httpd->open = false; + + const ScopeLock protect(httpd->mutex); + return server_socket_open(httpd->server_socket, error_r); +} + +static void +httpd_output_unbind(httpd_output *httpd) +{ + assert(!httpd->open); + + const ScopeLock protect(httpd->mutex); + server_socket_close(httpd->server_socket); +} + +static struct audio_output * +httpd_output_init(const struct config_param *param, + GError **error) +{ + httpd_output *httpd = new httpd_output(); + if (!ao_base_init(&httpd->base, &httpd_output_plugin, param, error)) { + g_free(httpd); + return NULL; + } + + /* read configuration */ + httpd->name = + config_get_block_string(param, "name", "Set name in config"); + httpd->genre = + config_get_block_string(param, "genre", "Set genre in config"); + httpd->website = + config_get_block_string(param, "website", "Set website in config"); + + guint port = config_get_block_unsigned(param, "port", 8000); + + const char *encoder_name = + config_get_block_string(param, "encoder", "vorbis"); + const struct encoder_plugin *encoder_plugin = + encoder_plugin_get(encoder_name); + if (encoder_plugin == NULL) { + g_set_error(error, httpd_output_quark(), 0, + "No such encoder: %s", encoder_name); + ao_base_finish(&httpd->base); + g_free(httpd); + return NULL; + } + + httpd->clients_max = config_get_block_unsigned(param,"max_clients", 0); + + /* set up bind_to_address */ + + httpd->server_socket = server_socket_new(httpd_listen_in_event, httpd); + + const char *bind_to_address = + config_get_block_string(param, "bind_to_address", NULL); + bool success = bind_to_address != NULL && + strcmp(bind_to_address, "any") != 0 + ? server_socket_add_host(httpd->server_socket, bind_to_address, + port, error) + : server_socket_add_port(httpd->server_socket, port, error); + if (!success) { + ao_base_finish(&httpd->base); + g_free(httpd); + return NULL; + } + + /* initialize metadata */ + httpd->metadata = NULL; + httpd->unflushed_input = 0; + + /* initialize encoder */ + + httpd->encoder = encoder_init(encoder_plugin, param, error); + if (httpd->encoder == NULL) { + ao_base_finish(&httpd->base); + g_free(httpd); + return NULL; + } + + /* determine content type */ + httpd->content_type = encoder_get_mime_type(httpd->encoder); + if (httpd->content_type == NULL) { + httpd->content_type = "application/octet-stream"; + } + + return &httpd->base; +} + +static void +httpd_output_finish(struct audio_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); + ao_base_finish(&httpd->base); + delete httpd; +} + +/** + * Creates a new #HttpdClient object and adds it into the + * httpd_output.clients linked list. + */ +static void +httpd_client_add(httpd_output *httpd, int fd) +{ + httpd->clients.emplace_front(httpd, fd, + httpd->encoder->plugin->tag == NULL); + httpd->clients_cnt++; + + /* pass metadata to client */ + if (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) +{ + httpd_output *httpd = (httpd_output *)ctx; + + /* the listener socket has become readable - a client has + connected */ + +#ifdef HAVE_LIBWRAP + if (address->sa_family != AF_UNIX) { + char *hostaddr = sockaddr_to_string(address, address_length, NULL); + const char *progname = g_get_prgname(); + + struct request_info req; + request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0); + + fromhost(&req); + + if (!hosts_access(&req)) { + /* tcp wrappers says no */ + g_warning("libwrap refused connection (libwrap=%s) from %s", + progname, hostaddr); + g_free(hostaddr); + close_socket(fd); + return; + } + + g_free(hostaddr); + } +#else + (void)address; + (void)address_length; +#endif /* HAVE_WRAP */ + + const ScopeLock protect(httpd->mutex); + + if (fd >= 0) { + /* can we allow additional client */ + if (httpd->open && + (httpd->clients_max == 0 || + httpd->clients_cnt < httpd->clients_max)) + httpd_client_add(httpd, fd); + else + close_socket(fd); + } else if (fd < 0 && errno != EINTR) { + g_warning("accept() failed: %s", g_strerror(errno)); + } +} + +/** + * Reads data from the encoder (as much as available) and returns it + * as a new #page object. + */ +static struct page * +httpd_output_read_page(httpd_output *httpd) +{ + if (httpd->unflushed_input >= 65536) { + /* we have fed a lot of input into the encoder, but it + didn't give anything back yet - flush now to avoid + buffer underruns */ + encoder_flush(httpd->encoder, NULL); + httpd->unflushed_input = 0; + } + + size_t size = 0; + do { + size_t nbytes = encoder_read(httpd->encoder, + httpd->buffer + size, + sizeof(httpd->buffer) - size); + if (nbytes == 0) + break; + + httpd->unflushed_input = 0; + + size += nbytes; + } while (size < sizeof(httpd->buffer)); + + if (size == 0) + return NULL; + + return page_new_copy(httpd->buffer, size); +} + +static bool +httpd_output_encoder_open(httpd_output *httpd, + struct audio_format *audio_format, + GError **error) +{ + if (!encoder_open(httpd->encoder, audio_format, error)) + return false; + + /* we have to remember the encoder header, i.e. the first + bytes of encoder output after opening it, because it has to + be sent to every new client */ + httpd->header = httpd_output_read_page(httpd); + + httpd->unflushed_input = 0; + + return true; +} + +static bool +httpd_output_enable(struct audio_output *ao, GError **error_r) +{ + httpd_output *httpd = (httpd_output *)ao; + + return httpd_output_bind(httpd, error_r); +} + +static void +httpd_output_disable(struct audio_output *ao) +{ + httpd_output *httpd = (httpd_output *)ao; + + httpd_output_unbind(httpd); +} + +static bool +httpd_output_open(struct audio_output *ao, struct audio_format *audio_format, + GError **error) +{ + httpd_output *httpd = (httpd_output *)ao; + + assert(httpd->clients.empty()); + + const ScopeLock protect(httpd->mutex); + + /* open the encoder */ + + if (!httpd_output_encoder_open(httpd, audio_format, error)) + return false; + + /* initialize other attributes */ + + httpd->clients_cnt = 0; + httpd->timer = timer_new(audio_format); + + httpd->open = true; + + return true; +} + +static void +httpd_output_close(struct audio_output *ao) +{ + httpd_output *httpd = (httpd_output *)ao; + + const ScopeLock protect(httpd->mutex); + + httpd->open = false; + + timer_free(httpd->timer); + + httpd->clients.clear(); + + if (httpd->header != NULL) + page_unref(httpd->header); + + encoder_close(httpd->encoder); +} + +void +httpd_output_remove_client(httpd_output *httpd, HttpdClient *client) +{ + assert(httpd != NULL); + assert(httpd->clients_cnt > 0); + assert(client != NULL); + + 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(httpd_output *httpd, HttpdClient *client) +{ + if (httpd->header != NULL) + client->PushPage(httpd->header); +} + +static unsigned +httpd_output_delay(struct audio_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, + then httpd_output_pause() will not do anything, it + will not fill the buffer and it will not update the + timer; therefore, we reset the timer here */ + timer_reset(httpd->timer); + + /* some arbitrary delay that is long enough to avoid + consuming too much CPU, and short enough to notice + new clients quickly enough */ + return 1000; + } + + return httpd->timer->started + ? timer_delay(httpd->timer) + : 0; +} + +/** + * Broadcasts a page struct to all clients. + */ +static void +httpd_output_broadcast_page(httpd_output *httpd, struct page *page) +{ + assert(page != NULL); + + 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(httpd_output *httpd) +{ + 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); + } +} + +static bool +httpd_output_encode_and_play(httpd_output *httpd, + const void *chunk, size_t size, GError **error) +{ + if (!encoder_write(httpd->encoder, chunk, size, error)) + return false; + + httpd->unflushed_input += size; + + httpd_output_encoder_to_clients(httpd); + + return true; +} + +static size_t +httpd_output_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error_r) +{ + httpd_output *httpd = (httpd_output *)ao; + + if (httpd_output_lock_has_clients(httpd)) { + if (!httpd_output_encode_and_play(httpd, chunk, size, error_r)) + return 0; + } + + if (!httpd->timer->started) + timer_start(httpd->timer); + timer_add(httpd->timer, size); + + return size; +} + +static bool +httpd_output_pause(struct audio_output *ao) +{ + httpd_output *httpd = (httpd_output *)ao; + + if (httpd_output_lock_has_clients(httpd)) { + static const char silence[1020] = { 0 }; + return httpd_output_play(ao, silence, sizeof(silence), + NULL) > 0; + } else { + return true; + } +} + +static void +httpd_output_tag(struct audio_output *ao, const struct tag *tag) +{ + httpd_output *httpd = (httpd_output *)ao; + + assert(tag != NULL); + + if (httpd->encoder->plugin->tag != NULL) { + /* embed encoder tags */ + + /* flush the current stream, and end it */ + + encoder_pre_tag(httpd->encoder, NULL); + httpd_output_encoder_to_clients(httpd); + + /* send the tag to the encoder - which starts a new + stream now */ + + encoder_tag(httpd->encoder, tag, NULL); + + /* the first page generated by the encoder will now be + used as the new "header" page, which is sent to all + new clients */ + + struct page *page = httpd_output_read_page(httpd); + if (page != NULL) { + if (httpd->header != NULL) + page_unref(httpd->header); + httpd->header = page; + httpd_output_broadcast_page(httpd, page); + } + } else { + /* use Icy-Metadata */ + + if (httpd->metadata != NULL) + page_unref (httpd->metadata); + + httpd->metadata = + icy_server_metadata_page(tag, TAG_ALBUM, + TAG_ARTIST, TAG_TITLE, + TAG_NUM_OF_ITEM_TYPES); + if (httpd->metadata != NULL) { + const ScopeLock protect(httpd->mutex); + for (auto &client : httpd->clients) + client.PushMetaData(httpd->metadata); + } + } +} + +static void +httpd_output_cancel(struct audio_output *ao) +{ + httpd_output *httpd = (httpd_output *)ao; + + const ScopeLock protect(httpd->mutex); + for (auto &client : httpd->clients) + client.CancelQueue(); +} + +const struct audio_output_plugin httpd_output_plugin = { + "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/HttpdOutputPlugin.hxx b/src/output/HttpdOutputPlugin.hxx new file mode 100644 index 000000000..c74d2bd4a --- /dev/null +++ b/src/output/HttpdOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * 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_HTTPD_OUTPUT_PLUGIN_HXX +#define MPD_HTTPD_OUTPUT_PLUGIN_HXX + +extern const struct audio_output_plugin httpd_output_plugin; + +#endif 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 -#include -#include - -#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 -#include - -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 diff --git a/src/output/httpd_internal.h b/src/output/httpd_internal.h deleted file mode 100644 index 5dcb8ab9b..000000000 --- a/src/output/httpd_internal.h +++ /dev/null @@ -1,138 +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. - */ - -/** \file - * - * Internal declarations for the "httpd" audio output plugin. - */ - -#ifndef MPD_OUTPUT_HTTPD_INTERNAL_H -#define MPD_OUTPUT_HTTPD_INTERNAL_H - -#include "output_internal.h" -#include "timer.h" - -#include - -#include - -struct httpd_client; - -struct httpd_output { - struct audio_output base; - - /** - * True if the audio output is open and accepts client - * connections. - */ - bool open; - - /** - * The configured encoder plugin. - */ - struct encoder *encoder; - - /** - * Number of bytes which were fed into the encoder, without - * ever receiving new output. This is used to estimate - * whether MPD should manually flush the encoder, to avoid - * buffer underruns in the client. - */ - size_t unflushed_input; - - /** - * The MIME type produced by the #encoder. - */ - const char *content_type; - - /** - * This mutex protects the listener socket and the client - * list. - */ - GMutex *mutex; - - /** - * A #timer object to synchronize this output with the - * wallclock. - */ - struct timer *timer; - - /** - * The listener socket. - */ - struct server_socket *server_socket; - - /** - * The header page, which is sent to every client on connect. - */ - struct page *header; - - /** - * The metadata, which is sent to every client. - */ - struct page *metadata; - - /** - * The configured name. - */ - char const *name; - /** - * The configured genre. - */ - char const *genre; - /** - * The configured website address. - */ - char const *website; - - /** - * A linked list containing all clients which are currently - * connected. - */ - GList *clients; - - /** - * A temporary buffer for the httpd_output_read_page() - * function. - */ - char buffer[32768]; - - /** - * The maximum and current number of clients connected - * at the same time. - */ - guint clients_max, clients_cnt; -}; - -/** - * Removes a client from the httpd_output.clients linked list. - */ -void -httpd_output_remove_client(struct httpd_output *httpd, - struct httpd_client *client); - -/** - * Sends the encoder header to the client. This is called right after - * the response headers have been sent. - */ -void -httpd_output_send_header(struct httpd_output *httpd, - struct httpd_client *client); - -#endif diff --git a/src/output/httpd_output_plugin.c b/src/output/httpd_output_plugin.c deleted file mode 100644 index 1d730df7f..000000000 --- a/src/output/httpd_output_plugin.c +++ /dev/null @@ -1,623 +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_output_plugin.h" -#include "httpd_internal.h" -#include "httpd_client.h" -#include "output_api.h" -#include "encoder_plugin.h" -#include "encoder_list.h" -#include "resolver.h" -#include "page.h" -#include "icy_server.h" -#include "fd_util.h" -#include "server_socket.h" - -#include - -#include -#include -#include - -#ifdef HAVE_LIBWRAP -#include /* needed for AF_UNIX */ -#include -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "httpd_output" - -/** - * The quark used for GError.domain. - */ -static inline GQuark -httpd_output_quark(void) -{ - return g_quark_from_static_string("httpd_output"); -} - -/** - * Check whether there is at least one client. - * - * Caller must lock the mutex. - */ -G_GNUC_PURE -static bool -httpd_output_has_clients(const struct httpd_output *httpd) -{ - return httpd->clients != NULL; -} - -/** - * Check whether there is at least one client. - */ -G_GNUC_PURE -static bool -httpd_output_lock_has_clients(const struct httpd_output *httpd) -{ - g_mutex_lock(httpd->mutex); - bool result = httpd_output_has_clients(httpd); - g_mutex_unlock(httpd->mutex); - return result; -} - -static void -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->open = false; - - g_mutex_lock(httpd->mutex); - bool success = server_socket_open(httpd->server_socket, error_r); - g_mutex_unlock(httpd->mutex); - - return success; -} - -static void -httpd_output_unbind(struct httpd_output *httpd) -{ - assert(!httpd->open); - - g_mutex_lock(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); - if (!ao_base_init(&httpd->base, &httpd_output_plugin, param, error)) { - g_free(httpd); - return NULL; - } - - /* read configuration */ - httpd->name = - config_get_block_string(param, "name", "Set name in config"); - httpd->genre = - config_get_block_string(param, "genre", "Set genre in config"); - httpd->website = - config_get_block_string(param, "website", "Set website in config"); - - guint port = config_get_block_unsigned(param, "port", 8000); - - const char *encoder_name = - config_get_block_string(param, "encoder", "vorbis"); - const struct encoder_plugin *encoder_plugin = - encoder_plugin_get(encoder_name); - if (encoder_plugin == NULL) { - g_set_error(error, httpd_output_quark(), 0, - "No such encoder: %s", encoder_name); - ao_base_finish(&httpd->base); - g_free(httpd); - return NULL; - } - - httpd->clients_max = config_get_block_unsigned(param,"max_clients", 0); - - /* set up bind_to_address */ - - httpd->server_socket = server_socket_new(httpd_listen_in_event, httpd); - - const char *bind_to_address = - config_get_block_string(param, "bind_to_address", NULL); - bool success = bind_to_address != NULL && - strcmp(bind_to_address, "any") != 0 - ? server_socket_add_host(httpd->server_socket, bind_to_address, - port, error) - : server_socket_add_port(httpd->server_socket, port, error); - if (!success) { - ao_base_finish(&httpd->base); - g_free(httpd); - return NULL; - } - - /* initialize metadata */ - httpd->metadata = NULL; - httpd->unflushed_input = 0; - - /* initialize encoder */ - - httpd->encoder = encoder_init(encoder_plugin, param, error); - if (httpd->encoder == NULL) { - ao_base_finish(&httpd->base); - g_free(httpd); - return NULL; - } - - /* determine content type */ - httpd->content_type = encoder_get_mime_type(httpd->encoder); - if (httpd->content_type == NULL) { - 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; - - 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); -} - -/** - * Creates a new #httpd_client object and adds it into the - * httpd_output.clients linked list. - */ -static void -httpd_client_add(struct 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_cnt++; - - /* pass metadata to client */ - if (httpd->metadata) - httpd_client_send_metadata(client, 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; - - /* the listener socket has become readable - a client has - connected */ - -#ifdef HAVE_LIBWRAP - if (address->sa_family != AF_UNIX) { - char *hostaddr = sockaddr_to_string(address, address_length, NULL); - const char *progname = g_get_prgname(); - - struct request_info req; - request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0); - - fromhost(&req); - - if (!hosts_access(&req)) { - /* tcp wrappers says no */ - g_warning("libwrap refused connection (libwrap=%s) from %s", - progname, hostaddr); - g_free(hostaddr); - close_socket(fd); - g_mutex_unlock(httpd->mutex); - return; - } - - g_free(hostaddr); - } -#else - (void)address; - (void)address_length; -#endif /* HAVE_WRAP */ - - g_mutex_lock(httpd->mutex); - - if (fd >= 0) { - /* can we allow additional client */ - if (httpd->open && - (httpd->clients_max == 0 || - httpd->clients_cnt < httpd->clients_max)) - httpd_client_add(httpd, fd); - else - close_socket(fd); - } else if (fd < 0 && errno != EINTR) { - g_warning("accept() failed: %s", g_strerror(errno)); - } - - g_mutex_unlock(httpd->mutex); -} - -/** - * Reads data from the encoder (as much as available) and returns it - * as a new #page object. - */ -static struct page * -httpd_output_read_page(struct httpd_output *httpd) -{ - if (httpd->unflushed_input >= 65536) { - /* we have fed a lot of input into the encoder, but it - didn't give anything back yet - flush now to avoid - buffer underruns */ - encoder_flush(httpd->encoder, NULL); - httpd->unflushed_input = 0; - } - - size_t size = 0; - do { - size_t nbytes = encoder_read(httpd->encoder, - httpd->buffer + size, - sizeof(httpd->buffer) - size); - if (nbytes == 0) - break; - - httpd->unflushed_input = 0; - - size += nbytes; - } while (size < sizeof(httpd->buffer)); - - if (size == 0) - return NULL; - - return page_new_copy(httpd->buffer, size); -} - -static bool -httpd_output_encoder_open(struct httpd_output *httpd, - struct audio_format *audio_format, - GError **error) -{ - if (!encoder_open(httpd->encoder, audio_format, error)) - return false; - - /* we have to remember the encoder header, i.e. the first - bytes of encoder output after opening it, because it has to - be sent to every new client */ - httpd->header = httpd_output_read_page(httpd); - - httpd->unflushed_input = 0; - - return true; -} - -static bool -httpd_output_enable(struct audio_output *ao, GError **error_r) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - return httpd_output_bind(httpd, error_r); -} - -static void -httpd_output_disable(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - httpd_output_unbind(httpd); -} - -static bool -httpd_output_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - g_mutex_lock(httpd->mutex); - - /* open the encoder */ - - if (!httpd_output_encoder_open(httpd, audio_format, error)) { - g_mutex_unlock(httpd->mutex); - 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; - - g_mutex_lock(httpd->mutex); - - httpd->open = false; - - timer_free(httpd->timer); - - g_list_foreach(httpd->clients, httpd_client_delete, NULL); - g_list_free(httpd->clients); - - 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) -{ - assert(httpd != NULL); - assert(client != NULL); - - httpd->clients = g_list_remove(httpd->clients, client); - httpd->clients_cnt--; -} - -void -httpd_output_send_header(struct httpd_output *httpd, - struct httpd_client *client) -{ - if (httpd->header != NULL) - httpd_client_send(client, httpd->header); -} - -static unsigned -httpd_output_delay(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - if (!httpd_output_lock_has_clients(httpd) && httpd->base.pause) { - /* if there's no client and this output is paused, - then httpd_output_pause() will not do anything, it - will not fill the buffer and it will not update the - timer; therefore, we reset the timer here */ - timer_reset(httpd->timer); - - /* some arbitrary delay that is long enough to avoid - consuming too much CPU, and short enough to notice - new clients quickly enough */ - return 1000; - } - - return httpd->timer->started - ? timer_delay(httpd->timer) - : 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) -{ - assert(page != NULL); - - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, httpd_client_send_page, page); - g_mutex_unlock(httpd->mutex); -} - -/** - * Broadcasts data from the encoder to all clients. - */ -static void -httpd_output_encoder_to_clients(struct 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); - - while ((page = httpd_output_read_page(httpd)) != NULL) { - httpd_output_broadcast_page(httpd, page); - page_unref(page); - } -} - -static bool -httpd_output_encode_and_play(struct httpd_output *httpd, - const void *chunk, size_t size, GError **error) -{ - if (!encoder_write(httpd->encoder, chunk, size, error)) - return false; - - httpd->unflushed_input += size; - - httpd_output_encoder_to_clients(httpd); - - return true; -} - -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; - - if (httpd_output_lock_has_clients(httpd)) { - if (!httpd_output_encode_and_play(httpd, chunk, size, error_r)) - return 0; - } - - if (!httpd->timer->started) - timer_start(httpd->timer); - timer_add(httpd->timer, size); - - return size; -} - -static bool -httpd_output_pause(struct audio_output *ao) -{ - struct httpd_output *httpd = (struct httpd_output *)ao; - - if (httpd_output_lock_has_clients(httpd)) { - static const char silence[1020]; - return httpd_output_play(ao, silence, sizeof(silence), - NULL) > 0; - } else { - return true; - } -} - -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; - - assert(tag != NULL); - - if (httpd->encoder->plugin->tag != NULL) { - /* embed encoder tags */ - - /* flush the current stream, and end it */ - - encoder_pre_tag(httpd->encoder, NULL); - httpd_output_encoder_to_clients(httpd); - - /* send the tag to the encoder - which starts a new - stream now */ - - encoder_tag(httpd->encoder, tag, NULL); - - /* the first page generated by the encoder will now be - used as the new "header" page, which is sent to all - new clients */ - - struct page *page = httpd_output_read_page(httpd); - if (page != NULL) { - if (httpd->header != NULL) - page_unref(httpd->header); - httpd->header = page; - httpd_output_broadcast_page(httpd, page); - } - } else { - /* use Icy-Metadata */ - - if (httpd->metadata != NULL) - page_unref (httpd->metadata); - - httpd->metadata = - icy_server_metadata_page(tag, TAG_ALBUM, - 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); - } - } -} - -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; - - g_mutex_lock(httpd->mutex); - g_list_foreach(httpd->clients, httpd_client_cancel_callback, NULL); - g_mutex_unlock(httpd->mutex); -} - -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, -}; diff --git a/src/output/httpd_output_plugin.h b/src/output/httpd_output_plugin.h deleted file mode 100644 index d0eb1533f..000000000 --- a/src/output/httpd_output_plugin.h +++ /dev/null @@ -1,25 +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_HTTPD_OUTPUT_PLUGIN_H -#define MPD_HTTPD_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin httpd_output_plugin; - -#endif -- cgit v1.2.3