aboutsummaryrefslogtreecommitdiffstats
path: root/src/input
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/input/ArchiveInputPlugin.cxx93
-rw-r--r--src/input/ArchiveInputPlugin.hxx25
-rw-r--r--src/input/CdioParanoiaInputPlugin.cxx379
-rw-r--r--src/input/CdioParanoiaInputPlugin.hxx28
-rw-r--r--src/input/CurlInputPlugin.cxx1187
-rw-r--r--src/input/CurlInputPlugin.hxx27
-rw-r--r--src/input/DespotifyInputPlugin.cxx243
-rw-r--r--src/input/DespotifyInputPlugin.hxx25
-rw-r--r--src/input/FfmpegInputPlugin.cxx184
-rw-r--r--src/input/FfmpegInputPlugin.hxx28
-rw-r--r--src/input/FileInputPlugin.cxx164
-rw-r--r--src/input/FileInputPlugin.hxx25
-rw-r--r--src/input/MmsInputPlugin.cxx147
-rw-r--r--src/input/MmsInputPlugin.hxx (renamed from src/input/mms_input_plugin.h)0
-rw-r--r--src/input/RewindInputPlugin.cxx256
-rw-r--r--src/input/RewindInputPlugin.hxx37
-rw-r--r--src/input/SoupInputPlugin.cxx492
-rw-r--r--src/input/SoupInputPlugin.hxx25
-rw-r--r--src/input/archive_input_plugin.c81
-rw-r--r--src/input/archive_input_plugin.h25
-rw-r--r--src/input/cdio_paranoia_input_plugin.c371
-rw-r--r--src/input/cdio_paranoia_input_plugin.h28
-rw-r--r--src/input/curl_input_plugin.c1301
-rw-r--r--src/input/curl_input_plugin.h27
-rw-r--r--src/input/despotify_input_plugin.c230
-rw-r--r--src/input/despotify_input_plugin.h25
-rw-r--r--src/input/ffmpeg_input_plugin.c204
-rw-r--r--src/input/ffmpeg_input_plugin.h28
-rw-r--r--src/input/file_input_plugin.c160
-rw-r--r--src/input/file_input_plugin.h25
-rw-r--r--src/input/mms_input_plugin.c140
-rw-r--r--src/input/rewind_input_plugin.c256
-rw-r--r--src/input/rewind_input_plugin.h37
-rw-r--r--src/input/soup_input_plugin.c473
-rw-r--r--src/input/soup_input_plugin.h25
-rw-r--r--src/input_init.c104
-rw-r--r--src/input_init.h42
-rw-r--r--src/input_internal.c73
-rw-r--r--src/input_internal.h43
-rw-r--r--src/input_plugin.h89
-rw-r--r--src/input_registry.c80
-rw-r--r--src/input_registry.h45
-rw-r--r--src/input_stream.c243
-rw-r--r--src/input_stream.h112
44 files changed, 3405 insertions, 4227 deletions
diff --git a/src/input/ArchiveInputPlugin.cxx b/src/input/ArchiveInputPlugin.cxx
new file mode 100644
index 000000000..0d856527f
--- /dev/null
+++ b/src/input/ArchiveInputPlugin.cxx
@@ -0,0 +1,93 @@
+/*
+ * 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 "ArchiveInputPlugin.hxx"
+#include "ArchiveLookup.hxx"
+#include "ArchiveList.hxx"
+#include "ArchivePlugin.hxx"
+#include "ArchiveFile.hxx"
+#include "InputPlugin.hxx"
+
+#include <glib.h>
+
+/**
+ * select correct archive plugin to handle the input stream
+ * may allow stacking of archive plugins. for example for handling
+ * tar.gz a gzip handler opens file (through inputfile stream)
+ * then it opens a tar handler and sets gzip inputstream as
+ * parent_stream so tar plugin fetches file data from gzip
+ * plugin and gzip fetches file from disk
+ */
+static struct input_stream *
+input_archive_open(const char *pathname,
+ Mutex &mutex, Cond &cond,
+ GError **error_r)
+{
+ const struct archive_plugin *arplug;
+ char *archive, *filename, *suffix, *pname;
+ struct input_stream *is;
+
+ if (!g_path_is_absolute(pathname))
+ return NULL;
+
+ pname = g_strdup(pathname);
+ // archive_lookup will modify pname when true is returned
+ if (!archive_lookup(pname, &archive, &filename, &suffix)) {
+ g_debug("not an archive, lookup %s failed\n", pname);
+ g_free(pname);
+ return NULL;
+ }
+
+ //check which archive plugin to use (by ext)
+ arplug = archive_plugin_from_suffix(suffix);
+ if (!arplug) {
+ g_warning("can't handle archive %s\n",archive);
+ g_free(pname);
+ return NULL;
+ }
+
+ auto file = archive_file_open(arplug, archive, error_r);
+ if (file == NULL) {
+ g_free(pname);
+ return NULL;
+ }
+
+ //setup fileops
+ is = file->OpenStream(filename, mutex, cond, error_r);
+ g_free(pname);
+ file->Close();
+
+ return is;
+}
+
+const struct input_plugin input_plugin_archive = {
+ "archive",
+ nullptr,
+ nullptr,
+ input_archive_open,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+};
diff --git a/src/input/ArchiveInputPlugin.hxx b/src/input/ArchiveInputPlugin.hxx
new file mode 100644
index 000000000..96fcd0dd1
--- /dev/null
+++ b/src/input/ArchiveInputPlugin.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_INPUT_ARCHIVE_HXX
+#define MPD_INPUT_ARCHIVE_HXX
+
+extern const struct input_plugin input_plugin_archive;
+
+#endif
diff --git a/src/input/CdioParanoiaInputPlugin.cxx b/src/input/CdioParanoiaInputPlugin.cxx
new file mode 100644
index 000000000..f0fa835b3
--- /dev/null
+++ b/src/input/CdioParanoiaInputPlugin.cxx
@@ -0,0 +1,379 @@
+/*
+ * 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.
+ */
+
+/**
+ * CD-Audio handling (requires libcdio_paranoia)
+ */
+
+#include "config.h"
+#include "CdioParanoiaInputPlugin.hxx"
+#include "InputInternal.hxx"
+#include "InputStream.hxx"
+#include "InputPlugin.hxx"
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+#include <glib.h>
+#include <assert.h>
+
+#include <cdio/paranoia.h>
+#include <cdio/cd_types.h>
+
+struct CdioParanoiaInputStream {
+ struct input_stream base;
+
+ cdrom_drive_t *drv;
+ CdIo_t *cdio;
+ cdrom_paranoia_t *para;
+
+ lsn_t lsn_from, lsn_to;
+ int lsn_relofs;
+
+ int trackno;
+
+ char buffer[CDIO_CD_FRAMESIZE_RAW];
+ int buffer_lsn;
+
+ CdioParanoiaInputStream(const char *uri, Mutex &mutex, Cond &cond,
+ int _trackno)
+ :base(input_plugin_cdio_paranoia, uri, mutex, cond),
+ drv(nullptr), cdio(nullptr), para(nullptr),
+ trackno(_trackno)
+ {
+ }
+
+ ~CdioParanoiaInputStream() {
+ if (para != nullptr)
+ cdio_paranoia_free(para);
+ if (drv != nullptr)
+ cdio_cddap_close_no_free_cdio(drv);
+ if (cdio != nullptr)
+ cdio_destroy(cdio);
+ }
+};
+
+static inline GQuark
+cdio_quark(void)
+{
+ return g_quark_from_static_string("cdio");
+}
+
+static void
+input_cdio_close(struct input_stream *is)
+{
+ CdioParanoiaInputStream *i = (CdioParanoiaInputStream *)is;
+
+ delete i;
+}
+
+struct cdio_uri {
+ char device[64];
+ int track;
+};
+
+static bool
+parse_cdio_uri(struct cdio_uri *dest, const char *src, GError **error_r)
+{
+ if (!g_str_has_prefix(src, "cdda://"))
+ return false;
+
+ src += 7;
+
+ if (*src == 0) {
+ /* play the whole CD in the default drive */
+ dest->device[0] = 0;
+ dest->track = -1;
+ return true;
+ }
+
+ const char *slash = strrchr(src, '/');
+ if (slash == nullptr) {
+ /* play the whole CD in the specified drive */
+ g_strlcpy(dest->device, src, sizeof(dest->device));
+ dest->track = -1;
+ return true;
+ }
+
+ size_t device_length = slash - src;
+ if (device_length >= sizeof(dest->device))
+ device_length = sizeof(dest->device) - 1;
+
+ memcpy(dest->device, src, device_length);
+ dest->device[device_length] = 0;
+
+ const char *track = slash + 1;
+
+ char *endptr;
+ dest->track = strtoul(track, &endptr, 10);
+ if (*endptr != 0) {
+ g_set_error(error_r, cdio_quark(), 0,
+ "Malformed track number");
+ return false;
+ }
+
+ if (endptr == track)
+ /* play the whole CD */
+ dest->track = -1;
+
+ return true;
+}
+
+static char *
+cdio_detect_device(void)
+{
+ char **devices = cdio_get_devices_with_cap(nullptr, CDIO_FS_AUDIO,
+ false);
+ if (devices == nullptr)
+ return nullptr;
+
+ char *device = g_strdup(devices[0]);
+ cdio_free_device_list(devices);
+
+ return device;
+}
+
+static struct input_stream *
+input_cdio_open(const char *uri,
+ Mutex &mutex, Cond &cond,
+ GError **error_r)
+{
+ struct cdio_uri parsed_uri;
+ if (!parse_cdio_uri(&parsed_uri, uri, error_r))
+ return nullptr;
+
+ CdioParanoiaInputStream *i =
+ new CdioParanoiaInputStream(uri, mutex, cond,
+ parsed_uri.track);
+
+ /* get list of CD's supporting CD-DA */
+ char *device = parsed_uri.device[0] != 0
+ ? g_strdup(parsed_uri.device)
+ : cdio_detect_device();
+ if (device == nullptr) {
+ g_set_error(error_r, cdio_quark(), 0,
+ "Unable find or access a CD-ROM drive with an audio CD in it.");
+ delete i;
+ return nullptr;
+ }
+
+ /* Found such a CD-ROM with a CD-DA loaded. Use the first drive in the list. */
+ i->cdio = cdio_open(device, DRIVER_UNKNOWN);
+ g_free(device);
+
+ i->drv = cdio_cddap_identify_cdio(i->cdio, 1, nullptr);
+
+ if ( !i->drv ) {
+ g_set_error(error_r, cdio_quark(), 0,
+ "Unable to identify audio CD disc.");
+ delete i;
+ return nullptr;
+ }
+
+ cdda_verbose_set(i->drv, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT);
+
+ if ( 0 != cdio_cddap_open(i->drv) ) {
+ g_set_error(error_r, cdio_quark(), 0, "Unable to open disc.");
+ delete i;
+ return nullptr;
+ }
+
+ bool reverse_endian;
+ switch (data_bigendianp(i->drv)) {
+ case -1:
+ g_debug("cdda: drive returns unknown audio data");
+ reverse_endian = false;
+ break;
+ case 0:
+ g_debug("cdda: drive returns audio data Little Endian.");
+ reverse_endian = G_BYTE_ORDER == G_BIG_ENDIAN;
+ break;
+ case 1:
+ g_debug("cdda: drive returns audio data Big Endian.");
+ reverse_endian = G_BYTE_ORDER == G_LITTLE_ENDIAN;
+ break;
+ default:
+ g_set_error(error_r, cdio_quark(), 0,
+ "Drive returns unknown data type %d",
+ data_bigendianp(i->drv));
+ delete i;
+ return nullptr;
+ }
+
+ i->lsn_relofs = 0;
+
+ if (i->trackno >= 0) {
+ i->lsn_from = cdio_get_track_lsn(i->cdio, i->trackno);
+ i->lsn_to = cdio_get_track_last_lsn(i->cdio, i->trackno);
+ } else {
+ i->lsn_from = 0;
+ i->lsn_to = cdio_get_disc_last_lsn(i->cdio);
+ }
+
+ i->para = cdio_paranoia_init(i->drv);
+
+ /* Set reading mode for full paranoia, but allow skipping sectors. */
+ paranoia_modeset(i->para, PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP);
+
+ /* seek to beginning of the track */
+ cdio_paranoia_seek(i->para, i->lsn_from, SEEK_SET);
+
+ i->base.ready = true;
+ i->base.seekable = true;
+ i->base.size = (i->lsn_to - i->lsn_from + 1) * CDIO_CD_FRAMESIZE_RAW;
+
+ /* hack to make MPD select the "pcm" decoder plugin */
+ i->base.mime = g_strdup(reverse_endian
+ ? "audio/x-mpd-cdda-pcm-reverse"
+ : "audio/x-mpd-cdda-pcm");
+
+ return &i->base;
+}
+
+static bool
+input_cdio_seek(struct input_stream *is,
+ goffset offset, int whence, GError **error_r)
+{
+ CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is;
+
+ /* calculate absolute offset */
+ switch (whence) {
+ case SEEK_SET:
+ break;
+ case SEEK_CUR:
+ offset += cis->base.offset;
+ break;
+ case SEEK_END:
+ offset += cis->base.size;
+ break;
+ }
+
+ if (offset < 0 || offset > cis->base.size) {
+ g_set_error(error_r, cdio_quark(), 0,
+ "Invalid offset to seek %ld (%ld)",
+ (long int)offset, (long int)cis->base.size);
+ return false;
+ }
+
+ /* simple case */
+ if (offset == cis->base.offset)
+ return true;
+
+ /* calculate current LSN */
+ cis->lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW;
+ cis->base.offset = offset;
+
+ cdio_paranoia_seek(cis->para, cis->lsn_from + cis->lsn_relofs, SEEK_SET);
+
+ return true;
+}
+
+static size_t
+input_cdio_read(struct input_stream *is, void *ptr, size_t length,
+ GError **error_r)
+{
+ CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is;
+ size_t nbytes = 0;
+ int diff;
+ size_t len, maxwrite;
+ int16_t *rbuf;
+ char *s_err, *s_mess;
+ char *wptr = (char *) ptr;
+
+ while (length > 0) {
+
+
+ /* end of track ? */
+ if (cis->lsn_from + cis->lsn_relofs > cis->lsn_to)
+ break;
+
+ //current sector was changed ?
+ if (cis->lsn_relofs != cis->buffer_lsn) {
+ rbuf = cdio_paranoia_read(cis->para, nullptr);
+
+ s_err = cdda_errors(cis->drv);
+ if (s_err) {
+ g_warning("paranoia_read: %s", s_err );
+ free(s_err);
+ }
+ s_mess = cdda_messages(cis->drv);
+ if (s_mess) {
+ free(s_mess);
+ }
+ if (!rbuf) {
+ g_set_error(error_r, cdio_quark(), 0,
+ "paranoia read error. Stopping.");
+ return 0;
+ }
+ //store current buffer
+ memcpy(cis->buffer, rbuf, CDIO_CD_FRAMESIZE_RAW);
+ cis->buffer_lsn = cis->lsn_relofs;
+ } else {
+ //use cached sector
+ rbuf = (int16_t*) cis->buffer;
+ }
+
+ //correct offset
+ diff = cis->base.offset - cis->lsn_relofs * CDIO_CD_FRAMESIZE_RAW;
+
+ assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW);
+
+ maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer
+ len = (length < maxwrite? length : maxwrite);
+
+ //skip diff bytes from this lsn
+ memcpy(wptr, ((char*)rbuf) + diff, len);
+ //update pointer
+ wptr += len;
+ nbytes += len;
+
+ //update offset
+ cis->base.offset += len;
+ cis->lsn_relofs = cis->base.offset / CDIO_CD_FRAMESIZE_RAW;
+ //update length
+ length -= len;
+ }
+
+ return nbytes;
+}
+
+static bool
+input_cdio_eof(struct input_stream *is)
+{
+ CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is;
+
+ return (cis->lsn_from + cis->lsn_relofs > cis->lsn_to);
+}
+
+const struct input_plugin input_plugin_cdio_paranoia = {
+ "cdio_paranoia",
+ nullptr,
+ nullptr,
+ input_cdio_open,
+ input_cdio_close,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ input_cdio_read,
+ input_cdio_eof,
+ input_cdio_seek,
+};
diff --git a/src/input/CdioParanoiaInputPlugin.hxx b/src/input/CdioParanoiaInputPlugin.hxx
new file mode 100644
index 000000000..80d98b4bf
--- /dev/null
+++ b/src/input/CdioParanoiaInputPlugin.hxx
@@ -0,0 +1,28 @@
+/*
+ * 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_CDIO_PARANOIA_INPUT_PLUGIN_HXX
+#define MPD_CDIO_PARANOIA_INPUT_PLUGIN_HXX
+
+/**
+ * An input plugin based on libcdio_paranoia library.
+ */
+extern const struct input_plugin input_plugin_cdio_paranoia;
+
+#endif
diff --git a/src/input/CurlInputPlugin.cxx b/src/input/CurlInputPlugin.cxx
new file mode 100644
index 000000000..fe944b752
--- /dev/null
+++ b/src/input/CurlInputPlugin.cxx
@@ -0,0 +1,1187 @@
+/*
+ * 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 "CurlInputPlugin.hxx"
+#include "InputInternal.hxx"
+#include "InputStream.hxx"
+#include "InputPlugin.hxx"
+#include "conf.h"
+#include "tag.h"
+#include "IcyMetaDataParser.hxx"
+#include "event/MultiSocketMonitor.hxx"
+#include "event/Loop.hxx"
+#include "IOThread.hxx"
+
+#include <assert.h>
+
+#if defined(WIN32)
+ #include <winsock2.h>
+#else
+ #include <sys/select.h>
+#endif
+
+#include <string.h>
+#include <errno.h>
+
+#include <list>
+#include <forward_list>
+
+#include <curl/curl.h>
+#include <glib.h>
+
+#if LIBCURL_VERSION_NUM < 0x071200
+#error libcurl is too old
+#endif
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "input_curl"
+
+/**
+ * Do not buffer more than this number of bytes. It should be a
+ * reasonable limit that doesn't make low-end machines suffer too
+ * much, but doesn't cause stuttering on high-latency lines.
+ */
+static const size_t CURL_MAX_BUFFERED = 512 * 1024;
+
+/**
+ * Resume the stream at this number of bytes after it has been paused.
+ */
+static const size_t CURL_RESUME_AT = 384 * 1024;
+
+/**
+ * Buffers created by input_curl_writefunction().
+ */
+class CurlInputBuffer {
+ /** size of the payload */
+ size_t size;
+
+ /** how much has been consumed yet? */
+ size_t consumed;
+
+ /** the payload */
+ uint8_t *data;
+
+public:
+ CurlInputBuffer(const void *_data, size_t _size)
+ :size(_size), consumed(0), data(new uint8_t[size]) {
+ memcpy(data, _data, size);
+ }
+
+ ~CurlInputBuffer() {
+ delete[] data;
+ }
+
+ CurlInputBuffer(const CurlInputBuffer &) = delete;
+ CurlInputBuffer &operator=(const CurlInputBuffer &) = delete;
+
+ const void *Begin() const {
+ return data + consumed;
+ }
+
+ size_t TotalSize() const {
+ return size;
+ }
+
+ size_t Available() const {
+ return size - consumed;
+ }
+
+ /**
+ * Mark a part of the buffer as consumed.
+ *
+ * @return false if the buffer is now empty
+ */
+ bool Consume(size_t length) {
+ assert(consumed < size);
+
+ consumed += length;
+ if (consumed < size)
+ return true;
+
+ assert(consumed == size);
+ return false;
+ }
+
+ bool Read(void *dest, size_t length) {
+ assert(consumed + length <= size);
+
+ memcpy(dest, data + consumed, length);
+ return Consume(length);
+ }
+};
+
+struct input_curl {
+ struct input_stream base;
+
+ /* some buffers which were passed to libcurl, which we have
+ too free */
+ char *range;
+ struct curl_slist *request_headers;
+
+ /** the curl handles */
+ CURL *easy;
+
+ /** list of buffers, where input_curl_writefunction() appends
+ to, and input_curl_read() reads from them */
+ std::list<CurlInputBuffer> buffers;
+
+ /**
+ * Is the connection currently paused? That happens when the
+ * buffer was getting too large. It will be unpaused when the
+ * buffer is below the threshold again.
+ */
+ bool paused;
+
+ /** error message provided by libcurl */
+ char error[CURL_ERROR_SIZE];
+
+ /** parser for icy-metadata */
+ IcyMetaDataParser icy;
+
+ /** the stream name from the icy-name response header */
+ char *meta_name;
+
+ /** the tag object ready to be requested via
+ input_stream_tag() */
+ struct tag *tag;
+
+ GError *postponed_error;
+
+ input_curl(const char *url, Mutex &mutex, Cond &cond)
+ :base(input_plugin_curl, url, mutex, cond),
+ range(nullptr), request_headers(nullptr),
+ paused(false),
+ meta_name(nullptr),
+ tag(nullptr),
+ postponed_error(nullptr) {
+ }
+
+ ~input_curl();
+
+ input_curl(const input_curl &) = delete;
+ input_curl &operator=(const input_curl &) = delete;
+};
+
+/**
+ * This class monitors all CURL file descriptors.
+ */
+class CurlSockets final : private MultiSocketMonitor {
+ /**
+ * Did CURL give us a timeout? If yes, then we need to call
+ * curl_multi_perform(), even if there was no event on any
+ * file descriptor.
+ */
+ bool have_timeout;
+
+ /**
+ * The absolute time stamp when the timeout expires.
+ */
+ gint64 absolute_timeout;
+
+public:
+ CurlSockets(EventLoop &_loop)
+ :MultiSocketMonitor(_loop) {}
+
+ using MultiSocketMonitor::InvalidateSockets;
+
+private:
+ void UpdateSockets();
+
+ virtual void PrepareSockets(gcc_unused gint *timeout_r) override;
+ virtual bool CheckSockets() const override;
+ virtual void DispatchSockets() override;
+};
+
+/** libcurl should accept "ICY 200 OK" */
+static struct curl_slist *http_200_aliases;
+
+/** HTTP proxy settings */
+static const char *proxy, *proxy_user, *proxy_password;
+static unsigned proxy_port;
+
+static struct {
+ CURLM *multi;
+
+ /**
+ * A linked list of all active HTTP requests. An active
+ * request is one that doesn't have the "eof" flag set.
+ */
+ std::forward_list<input_curl *> requests;
+
+ CurlSockets *sockets;
+} curl;
+
+static inline GQuark
+curl_quark(void)
+{
+ return g_quark_from_static_string("curl");
+}
+
+/**
+ * Find a request by its CURL "easy" handle.
+ *
+ * Runs in the I/O thread. No lock needed.
+ */
+static struct input_curl *
+input_curl_find_request(CURL *easy)
+{
+ assert(io_thread_inside());
+
+ for (auto c : curl.requests)
+ if (c->easy == easy)
+ return c;
+
+ return NULL;
+}
+
+static gpointer
+input_curl_resume(gpointer data)
+{
+ assert(io_thread_inside());
+
+ struct input_curl *c = (struct input_curl *)data;
+
+ if (c->paused) {
+ c->paused = false;
+ curl_easy_pause(c->easy, CURLPAUSE_CONT);
+ }
+
+ return NULL;
+}
+
+/**
+ * Calculates the GLib event bit mask for one file descriptor,
+ * obtained from three #fd_set objects filled by curl_multi_fdset().
+ */
+static unsigned
+input_curl_fd_events(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds)
+{
+ gushort events = 0;
+
+ if (FD_ISSET(fd, rfds)) {
+ events |= G_IO_IN | G_IO_HUP | G_IO_ERR;
+ FD_CLR(fd, rfds);
+ }
+
+ if (FD_ISSET(fd, wfds)) {
+ events |= G_IO_OUT | G_IO_ERR;
+ FD_CLR(fd, wfds);
+ }
+
+ if (FD_ISSET(fd, efds)) {
+ events |= G_IO_HUP | G_IO_ERR;
+ FD_CLR(fd, efds);
+ }
+
+ return events;
+}
+
+/**
+ * Updates all registered GPollFD objects, unregisters old ones,
+ * registers new ones.
+ *
+ * Runs in the I/O thread. No lock needed.
+ */
+void
+CurlSockets::UpdateSockets()
+{
+ assert(io_thread_inside());
+
+ fd_set rfds, wfds, efds;
+
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+ FD_ZERO(&efds);
+
+ int max_fd;
+ CURLMcode mcode = curl_multi_fdset(curl.multi, &rfds, &wfds,
+ &efds, &max_fd);
+ if (mcode != CURLM_OK) {
+ g_warning("curl_multi_fdset() failed: %s\n",
+ curl_multi_strerror(mcode));
+ return;
+ }
+
+ UpdateSocketList([&rfds, &wfds, &efds](int fd){
+ return input_curl_fd_events(fd, &rfds,
+ &wfds, &efds);
+ });
+
+ for (int fd = 0; fd <= max_fd; ++fd) {
+ unsigned events = input_curl_fd_events(fd, &rfds, &wfds, &efds);
+ if (events != 0)
+ AddSocket(fd, events);
+ }
+}
+
+/**
+ * Runs in the I/O thread. No lock needed.
+ */
+static bool
+input_curl_easy_add(struct input_curl *c, GError **error_r)
+{
+ assert(io_thread_inside());
+ assert(c != NULL);
+ assert(c->easy != NULL);
+ assert(input_curl_find_request(c->easy) == NULL);
+
+ curl.requests.push_front(c);
+
+ CURLMcode mcode = curl_multi_add_handle(curl.multi, c->easy);
+ if (mcode != CURLM_OK) {
+ g_set_error(error_r, curl_quark(), mcode,
+ "curl_multi_add_handle() failed: %s",
+ curl_multi_strerror(mcode));
+ return false;
+ }
+
+ curl.sockets->InvalidateSockets();
+
+ return true;
+}
+
+struct easy_add_params {
+ struct input_curl *c;
+ GError **error_r;
+};
+
+static gpointer
+input_curl_easy_add_callback(gpointer data)
+{
+ const struct easy_add_params *params =
+ (const struct easy_add_params *)data;
+
+ bool success = input_curl_easy_add(params->c, params->error_r);
+ return GUINT_TO_POINTER(success);
+}
+
+/**
+ * Call input_curl_easy_add() in the I/O thread. May be called from
+ * any thread. Caller must not hold a mutex.
+ */
+static bool
+input_curl_easy_add_indirect(struct input_curl *c, GError **error_r)
+{
+ assert(c != NULL);
+ assert(c->easy != NULL);
+
+ struct easy_add_params params = {
+ c,
+ error_r,
+ };
+
+ gpointer result =
+ io_thread_call(input_curl_easy_add_callback, &params);
+ return GPOINTER_TO_UINT(result);
+}
+
+/**
+ * Frees the current "libcurl easy" handle, and everything associated
+ * with it.
+ *
+ * Runs in the I/O thread.
+ */
+static void
+input_curl_easy_free(struct input_curl *c)
+{
+ assert(io_thread_inside());
+ assert(c != NULL);
+
+ if (c->easy == NULL)
+ return;
+
+ curl.requests.remove(c);
+
+ curl_multi_remove_handle(curl.multi, c->easy);
+ curl_easy_cleanup(c->easy);
+ c->easy = NULL;
+
+ curl_slist_free_all(c->request_headers);
+ c->request_headers = NULL;
+
+ g_free(c->range);
+ c->range = NULL;
+}
+
+static gpointer
+input_curl_easy_free_callback(gpointer data)
+{
+ struct input_curl *c = (struct input_curl *)data;
+
+ input_curl_easy_free(c);
+ curl.sockets->InvalidateSockets();
+
+ return NULL;
+}
+
+/**
+ * Frees the current "libcurl easy" handle, and everything associated
+ * with it.
+ *
+ * The mutex must not be locked.
+ */
+static void
+input_curl_easy_free_indirect(struct input_curl *c)
+{
+ io_thread_call(input_curl_easy_free_callback, c);
+ assert(c->easy == NULL);
+}
+
+/**
+ * Abort and free all HTTP requests.
+ *
+ * Runs in the I/O thread. The caller must not hold locks.
+ */
+static void
+input_curl_abort_all_requests(GError *error)
+{
+ assert(io_thread_inside());
+ assert(error != NULL);
+
+ while (!curl.requests.empty()) {
+ struct input_curl *c = curl.requests.front();
+ assert(c->postponed_error == NULL);
+
+ input_curl_easy_free(c);
+
+ const ScopeLock protect(c->base.mutex);
+
+ c->postponed_error = g_error_copy(error);
+ c->base.ready = true;
+
+ c->base.cond.broadcast();
+ }
+
+ g_error_free(error);
+
+}
+
+/**
+ * A HTTP request is finished.
+ *
+ * Runs in the I/O thread. The caller must not hold locks.
+ */
+static void
+input_curl_request_done(struct input_curl *c, CURLcode result, long status)
+{
+ assert(io_thread_inside());
+ assert(c != NULL);
+ assert(c->easy == NULL);
+ assert(c->postponed_error == NULL);
+
+ const ScopeLock protect(c->base.mutex);
+
+ if (result != CURLE_OK) {
+ c->postponed_error = g_error_new(curl_quark(), result,
+ "curl failed: %s",
+ c->error);
+ } else if (status < 200 || status >= 300) {
+ c->postponed_error = g_error_new(curl_quark(), 0,
+ "got HTTP status %ld",
+ status);
+ }
+
+ c->base.ready = true;
+
+ c->base.cond.broadcast();
+}
+
+static void
+input_curl_handle_done(CURL *easy_handle, CURLcode result)
+{
+ struct input_curl *c = input_curl_find_request(easy_handle);
+ assert(c != NULL);
+
+ long status = 0;
+ curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status);
+
+ input_curl_easy_free(c);
+ input_curl_request_done(c, result, status);
+}
+
+/**
+ * Check for finished HTTP responses.
+ *
+ * Runs in the I/O thread. The caller must not hold locks.
+ */
+static void
+input_curl_info_read(void)
+{
+ assert(io_thread_inside());
+
+ CURLMsg *msg;
+ int msgs_in_queue;
+
+ while ((msg = curl_multi_info_read(curl.multi,
+ &msgs_in_queue)) != NULL) {
+ if (msg->msg == CURLMSG_DONE)
+ input_curl_handle_done(msg->easy_handle, msg->data.result);
+ }
+}
+
+/**
+ * Give control to CURL.
+ *
+ * Runs in the I/O thread. The caller must not hold locks.
+ */
+static bool
+input_curl_perform(void)
+{
+ assert(io_thread_inside());
+
+ CURLMcode mcode;
+
+ do {
+ int running_handles;
+ mcode = curl_multi_perform(curl.multi, &running_handles);
+ } while (mcode == CURLM_CALL_MULTI_PERFORM);
+
+ if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) {
+ GError *error = g_error_new(curl_quark(), mcode,
+ "curl_multi_perform() failed: %s",
+ curl_multi_strerror(mcode));
+ input_curl_abort_all_requests(error);
+ return false;
+ }
+
+ return true;
+}
+
+void
+CurlSockets::PrepareSockets(gint *timeout_r)
+{
+ UpdateSockets();
+
+ have_timeout = false;
+
+ long timeout2;
+ CURLMcode mcode = curl_multi_timeout(curl.multi, &timeout2);
+ if (mcode == CURLM_OK) {
+ if (timeout2 >= 0)
+ absolute_timeout = GetTime() + timeout2 * 1000;
+
+ if (timeout2 >= 0 && timeout2 < 10)
+ /* CURL 7.21.1 likes to report "timeout=0",
+ which means we're running in a busy loop.
+ Quite a bad idea to waste so much CPU.
+ Let's use a lower limit of 10ms. */
+ timeout2 = 10;
+
+ *timeout_r = timeout2;
+
+ have_timeout = timeout2 >= 0;
+ } else
+ g_warning("curl_multi_timeout() failed: %s\n",
+ curl_multi_strerror(mcode));
+}
+
+bool
+CurlSockets::CheckSockets() const
+{
+ /* when a timeout has expired, we need to call
+ curl_multi_perform(), even if there was no file descriptor
+ event */
+ return have_timeout && GetTime() >= absolute_timeout;
+}
+
+void
+CurlSockets::DispatchSockets()
+{
+ if (input_curl_perform())
+ input_curl_info_read();
+}
+
+/*
+ * input_plugin methods
+ *
+ */
+
+static bool
+input_curl_init(const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ CURLcode code = curl_global_init(CURL_GLOBAL_ALL);
+ if (code != CURLE_OK) {
+ g_set_error(error_r, curl_quark(), code,
+ "curl_global_init() failed: %s\n",
+ curl_easy_strerror(code));
+ return false;
+ }
+
+ http_200_aliases = curl_slist_append(http_200_aliases, "ICY 200 OK");
+
+ proxy = config_get_block_string(param, "proxy", NULL);
+ proxy_port = config_get_block_unsigned(param, "proxy_port", 0);
+ proxy_user = config_get_block_string(param, "proxy_user", NULL);
+ proxy_password = config_get_block_string(param, "proxy_password",
+ NULL);
+
+ if (proxy == NULL) {
+ /* deprecated proxy configuration */
+ proxy = config_get_string(CONF_HTTP_PROXY_HOST, NULL);
+ proxy_port = config_get_positive(CONF_HTTP_PROXY_PORT, 0);
+ proxy_user = config_get_string(CONF_HTTP_PROXY_USER, NULL);
+ proxy_password = config_get_string(CONF_HTTP_PROXY_PASSWORD,
+ "");
+ }
+
+ curl.multi = curl_multi_init();
+ if (curl.multi == NULL) {
+ g_set_error(error_r, curl_quark(), 0,
+ "curl_multi_init() failed");
+ return false;
+ }
+
+ curl.sockets = new CurlSockets(io_thread_get());
+
+ return true;
+}
+
+static gpointer
+curl_destroy_sources(G_GNUC_UNUSED gpointer data)
+{
+ delete curl.sockets;
+
+ return NULL;
+}
+
+static void
+input_curl_finish(void)
+{
+ assert(curl.requests.empty());
+
+ io_thread_call(curl_destroy_sources, NULL);
+
+ curl_multi_cleanup(curl.multi);
+
+ curl_slist_free_all(http_200_aliases);
+
+ curl_global_cleanup();
+}
+
+/**
+ * Determine the total sizes of all buffers, including portions that
+ * have already been consumed.
+ *
+ * The caller must lock the mutex.
+ */
+G_GNUC_PURE
+static size_t
+curl_total_buffer_size(const struct input_curl *c)
+{
+ size_t total = 0;
+
+ for (const auto &i : c->buffers)
+ total += i.TotalSize();
+
+ return total;
+}
+
+input_curl::~input_curl()
+{
+ if (tag != NULL)
+ tag_free(tag);
+ g_free(meta_name);
+
+ input_curl_easy_free_indirect(this);
+
+ if (postponed_error != NULL)
+ g_error_free(postponed_error);
+}
+
+static bool
+input_curl_check(struct input_stream *is, GError **error_r)
+{
+ struct input_curl *c = (struct input_curl *)is;
+
+ bool success = c->postponed_error == NULL;
+ if (!success) {
+ g_propagate_error(error_r, c->postponed_error);
+ c->postponed_error = NULL;
+ }
+
+ return success;
+}
+
+static struct tag *
+input_curl_tag(struct input_stream *is)
+{
+ struct input_curl *c = (struct input_curl *)is;
+ struct tag *tag = c->tag;
+
+ c->tag = NULL;
+ return tag;
+}
+
+static bool
+fill_buffer(struct input_curl *c, GError **error_r)
+{
+ while (c->easy != NULL && c->buffers.empty())
+ c->base.cond.wait(c->base.mutex);
+
+ if (c->postponed_error != NULL) {
+ g_propagate_error(error_r, c->postponed_error);
+ c->postponed_error = NULL;
+ return false;
+ }
+
+ return !c->buffers.empty();
+}
+
+static size_t
+read_from_buffer(IcyMetaDataParser &icy, std::list<CurlInputBuffer> &buffers,
+ void *dest0, size_t length)
+{
+ auto &buffer = buffers.front();
+ uint8_t *dest = (uint8_t *)dest0;
+ size_t nbytes = 0;
+
+ if (length > buffer.Available())
+ length = buffer.Available();
+
+ while (true) {
+ size_t chunk;
+
+ chunk = icy.Data(length);
+ if (chunk > 0) {
+ const bool empty = !buffer.Read(dest, chunk);
+
+ nbytes += chunk;
+ dest += chunk;
+ length -= chunk;
+
+ if (empty) {
+ buffers.pop_front();
+ break;
+ }
+
+ if (length == 0)
+ break;
+ }
+
+ chunk = icy.Meta(buffer.Begin(), length);
+ if (chunk > 0) {
+ const bool empty = !buffer.Consume(chunk);
+
+ length -= chunk;
+
+ if (empty) {
+ buffers.pop_front();
+ break;
+ }
+
+ if (length == 0)
+ break;
+ }
+ }
+
+ return nbytes;
+}
+
+static void
+copy_icy_tag(struct input_curl *c)
+{
+ struct tag *tag = c->icy.ReadTag();
+
+ if (tag == NULL)
+ return;
+
+ if (c->tag != NULL)
+ tag_free(c->tag);
+
+ if (c->meta_name != NULL && !tag_has_type(tag, TAG_NAME))
+ tag_add_item(tag, TAG_NAME, c->meta_name);
+
+ c->tag = tag;
+}
+
+static bool
+input_curl_available(struct input_stream *is)
+{
+ struct input_curl *c = (struct input_curl *)is;
+
+ return c->postponed_error != NULL || c->easy == NULL ||
+ !c->buffers.empty();
+}
+
+static size_t
+input_curl_read(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r)
+{
+ struct input_curl *c = (struct input_curl *)is;
+ bool success;
+ size_t nbytes = 0;
+ char *dest = (char *)ptr;
+
+ do {
+ /* fill the buffer */
+
+ success = fill_buffer(c, error_r);
+ if (!success)
+ return 0;
+
+ /* send buffer contents */
+
+ while (size > 0 && !c->buffers.empty()) {
+ size_t copy = read_from_buffer(c->icy, c->buffers,
+ dest + nbytes, size);
+
+ nbytes += copy;
+ size -= copy;
+ }
+ } while (nbytes == 0);
+
+ if (c->icy.IsDefined())
+ copy_icy_tag(c);
+
+ is->offset += (goffset)nbytes;
+
+ if (c->paused && curl_total_buffer_size(c) < CURL_RESUME_AT) {
+ c->base.mutex.unlock();
+ io_thread_call(input_curl_resume, c);
+ c->base.mutex.lock();
+ }
+
+ return nbytes;
+}
+
+static void
+input_curl_close(struct input_stream *is)
+{
+ struct input_curl *c = (struct input_curl *)is;
+
+ delete c;
+}
+
+static bool
+input_curl_eof(G_GNUC_UNUSED struct input_stream *is)
+{
+ struct input_curl *c = (struct input_curl *)is;
+
+ return c->easy == NULL && c->buffers.empty();
+}
+
+/** called by curl when new data is available */
+static size_t
+input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
+{
+ struct input_curl *c = (struct input_curl *)stream;
+ char name[64];
+
+ size *= nmemb;
+
+ const char *header = (const char *)ptr;
+ const char *end = header + size;
+
+ const char *value = (const char *)memchr(header, ':', size);
+ if (value == NULL || (size_t)(value - header) >= sizeof(name))
+ return size;
+
+ memcpy(name, header, value - header);
+ name[value - header] = 0;
+
+ /* skip the colon */
+
+ ++value;
+
+ /* strip the value */
+
+ while (value < end && g_ascii_isspace(*value))
+ ++value;
+
+ while (end > value && g_ascii_isspace(end[-1]))
+ --end;
+
+ if (g_ascii_strcasecmp(name, "accept-ranges") == 0) {
+ /* a stream with icy-metadata is not seekable */
+ if (!c->icy.IsDefined())
+ c->base.seekable = true;
+ } else if (g_ascii_strcasecmp(name, "content-length") == 0) {
+ char buffer[64];
+
+ if ((size_t)(end - header) >= sizeof(buffer))
+ return size;
+
+ memcpy(buffer, value, end - value);
+ buffer[end - value] = 0;
+
+ c->base.size = c->base.offset + g_ascii_strtoull(buffer, NULL, 10);
+ } else if (g_ascii_strcasecmp(name, "content-type") == 0) {
+ c->base.mime.assign(value, end);
+ } else if (g_ascii_strcasecmp(name, "icy-name") == 0 ||
+ g_ascii_strcasecmp(name, "ice-name") == 0 ||
+ g_ascii_strcasecmp(name, "x-audiocast-name") == 0) {
+ g_free(c->meta_name);
+ c->meta_name = g_strndup(value, end - value);
+
+ if (c->tag != NULL)
+ tag_free(c->tag);
+
+ c->tag = tag_new();
+ tag_add_item(c->tag, TAG_NAME, c->meta_name);
+ } else if (g_ascii_strcasecmp(name, "icy-metaint") == 0) {
+ char buffer[64];
+ size_t icy_metaint;
+
+ if ((size_t)(end - header) >= sizeof(buffer) ||
+ c->icy.IsDefined())
+ return size;
+
+ memcpy(buffer, value, end - value);
+ buffer[end - value] = 0;
+
+ icy_metaint = g_ascii_strtoull(buffer, NULL, 10);
+ g_debug("icy-metaint=%zu", icy_metaint);
+
+ if (icy_metaint > 0) {
+ c->icy.Start(icy_metaint);
+
+ /* a stream with icy-metadata is not
+ seekable */
+ c->base.seekable = false;
+ }
+ }
+
+ return size;
+}
+
+/** called by curl when new data is available */
+static size_t
+input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream)
+{
+ struct input_curl *c = (struct input_curl *)stream;
+
+ size *= nmemb;
+ if (size == 0)
+ return 0;
+
+ const ScopeLock protect(c->base.mutex);
+
+ if (curl_total_buffer_size(c) + size >= CURL_MAX_BUFFERED) {
+ c->paused = true;
+ return CURL_WRITEFUNC_PAUSE;
+ }
+
+ c->buffers.emplace_back(ptr, size);
+ c->base.ready = true;
+
+ c->base.cond.broadcast();
+ return size;
+}
+
+static bool
+input_curl_easy_init(struct input_curl *c, GError **error_r)
+{
+ CURLcode code;
+
+ c->easy = curl_easy_init();
+ if (c->easy == NULL) {
+ g_set_error(error_r, curl_quark(), 0,
+ "curl_easy_init() failed");
+ return false;
+ }
+
+ curl_easy_setopt(c->easy, CURLOPT_USERAGENT,
+ "Music Player Daemon " VERSION);
+ curl_easy_setopt(c->easy, CURLOPT_HEADERFUNCTION,
+ input_curl_headerfunction);
+ curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, c);
+ curl_easy_setopt(c->easy, CURLOPT_WRITEFUNCTION,
+ input_curl_writefunction);
+ curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, c);
+ curl_easy_setopt(c->easy, CURLOPT_HTTP200ALIASES, http_200_aliases);
+ curl_easy_setopt(c->easy, CURLOPT_FOLLOWLOCATION, 1);
+ curl_easy_setopt(c->easy, CURLOPT_NETRC, 1);
+ curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5);
+ curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, true);
+ curl_easy_setopt(c->easy, CURLOPT_ERRORBUFFER, c->error);
+ curl_easy_setopt(c->easy, CURLOPT_NOPROGRESS, 1l);
+ curl_easy_setopt(c->easy, CURLOPT_NOSIGNAL, 1l);
+ curl_easy_setopt(c->easy, CURLOPT_CONNECTTIMEOUT, 10l);
+
+ if (proxy != NULL)
+ curl_easy_setopt(c->easy, CURLOPT_PROXY, proxy);
+
+ if (proxy_port > 0)
+ curl_easy_setopt(c->easy, CURLOPT_PROXYPORT, (long)proxy_port);
+
+ if (proxy_user != NULL && proxy_password != NULL) {
+ char *proxy_auth_str =
+ g_strconcat(proxy_user, ":", proxy_password, NULL);
+ curl_easy_setopt(c->easy, CURLOPT_PROXYUSERPWD, proxy_auth_str);
+ g_free(proxy_auth_str);
+ }
+
+ code = curl_easy_setopt(c->easy, CURLOPT_URL, c->base.uri.c_str());
+ if (code != CURLE_OK) {
+ g_set_error(error_r, curl_quark(), code,
+ "curl_easy_setopt() failed: %s",
+ curl_easy_strerror(code));
+ return false;
+ }
+
+ c->request_headers = NULL;
+ c->request_headers = curl_slist_append(c->request_headers,
+ "Icy-Metadata: 1");
+ curl_easy_setopt(c->easy, CURLOPT_HTTPHEADER, c->request_headers);
+
+ return true;
+}
+
+static bool
+input_curl_seek(struct input_stream *is, goffset offset, int whence,
+ GError **error_r)
+{
+ struct input_curl *c = (struct input_curl *)is;
+ bool ret;
+
+ assert(is->ready);
+
+ if (whence == SEEK_SET && offset == is->offset)
+ /* no-op */
+ return true;
+
+ if (!is->seekable)
+ return false;
+
+ /* calculate the absolute offset */
+
+ switch (whence) {
+ case SEEK_SET:
+ break;
+
+ case SEEK_CUR:
+ offset += is->offset;
+ break;
+
+ case SEEK_END:
+ if (is->size < 0)
+ /* stream size is not known */
+ return false;
+
+ offset += is->size;
+ break;
+
+ default:
+ return false;
+ }
+
+ if (offset < 0)
+ return false;
+
+ /* check if we can fast-forward the buffer */
+
+ while (offset > is->offset && !c->buffers.empty()) {
+ auto &buffer = c->buffers.front();
+ size_t length = buffer.Available();
+ if (offset - is->offset < (goffset)length)
+ length = offset - is->offset;
+
+ const bool empty = !buffer.Consume(length);
+ if (empty)
+ c->buffers.pop_front();
+
+ is->offset += length;
+ }
+
+ if (offset == is->offset)
+ return true;
+
+ /* close the old connection and open a new one */
+
+ c->base.mutex.unlock();
+
+ input_curl_easy_free_indirect(c);
+ c->buffers.clear();
+
+ is->offset = offset;
+ if (is->offset == is->size) {
+ /* seek to EOF: simulate empty result; avoid
+ triggering a "416 Requested Range Not Satisfiable"
+ response */
+ return true;
+ }
+
+ ret = input_curl_easy_init(c, error_r);
+ if (!ret)
+ return false;
+
+ /* send the "Range" header */
+
+ if (is->offset > 0) {
+ c->range = g_strdup_printf("%lld-", (long long)is->offset);
+ curl_easy_setopt(c->easy, CURLOPT_RANGE, c->range);
+ }
+
+ c->base.ready = false;
+
+ if (!input_curl_easy_add_indirect(c, error_r))
+ return false;
+
+ c->base.mutex.lock();
+
+ while (!c->base.ready)
+ c->base.cond.wait(c->base.mutex);
+
+ if (c->postponed_error != NULL) {
+ g_propagate_error(error_r, c->postponed_error);
+ c->postponed_error = NULL;
+ return false;
+ }
+
+ return true;
+}
+
+static struct input_stream *
+input_curl_open(const char *url, Mutex &mutex, Cond &cond,
+ GError **error_r)
+{
+ if (strncmp(url, "http://", 7) != 0)
+ return NULL;
+
+ struct input_curl *c = new input_curl(url, mutex, cond);
+
+ if (!input_curl_easy_init(c, error_r)) {
+ delete c;
+ return NULL;
+ }
+
+ if (!input_curl_easy_add_indirect(c, error_r)) {
+ delete c;
+ return NULL;
+ }
+
+ return &c->base;
+}
+
+const struct input_plugin input_plugin_curl = {
+ "curl",
+ input_curl_init,
+ input_curl_finish,
+ input_curl_open,
+ input_curl_close,
+ input_curl_check,
+ nullptr,
+ input_curl_tag,
+ input_curl_available,
+ input_curl_read,
+ input_curl_eof,
+ input_curl_seek,
+};
diff --git a/src/input/CurlInputPlugin.hxx b/src/input/CurlInputPlugin.hxx
new file mode 100644
index 000000000..20d1309d8
--- /dev/null
+++ b/src/input/CurlInputPlugin.hxx
@@ -0,0 +1,27 @@
+/*
+ * 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_INPUT_CURL_HXX
+#define MPD_INPUT_CURL_HXX
+
+struct input_stream;
+
+extern const struct input_plugin input_plugin_curl;
+
+#endif
diff --git a/src/input/DespotifyInputPlugin.cxx b/src/input/DespotifyInputPlugin.cxx
new file mode 100644
index 000000000..1e5a8c606
--- /dev/null
+++ b/src/input/DespotifyInputPlugin.cxx
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2011-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 "DespotifyInputPlugin.hxx"
+#include "DespotifyUtils.hxx"
+#include "InputInternal.hxx"
+#include "InputStream.hxx"
+#include "InputPlugin.hxx"
+#include "tag.h"
+
+extern "C" {
+#include <despotify.h>
+}
+
+#include <glib.h>
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include <stdio.h>
+
+struct DespotifyInputStream {
+ struct input_stream base;
+
+ struct despotify_session *session;
+ struct ds_track *track;
+ struct tag *tag;
+ struct ds_pcm_data pcm;
+ size_t len_available;
+ bool eof;
+
+ DespotifyInputStream(const char *uri,
+ Mutex &mutex, Cond &cond,
+ despotify_session *_session,
+ ds_track *_track)
+ :base(input_plugin_despotify, uri, mutex, cond),
+ session(_session), track(_track),
+ tag(mpd_despotify_tag_from_track(track)),
+ len_available(0), eof(false) {
+
+ memset(&pcm, 0, sizeof(pcm));
+
+ /* Despotify outputs pcm data */
+ base.mime = g_strdup("audio/x-mpd-cdda-pcm");
+ base.ready = true;
+ }
+
+ ~DespotifyInputStream() {
+ if (tag != NULL)
+ tag_free(tag);
+
+ despotify_free_track(track);
+ }
+};
+
+static void
+refill_buffer(DespotifyInputStream *ctx)
+{
+ /* Wait until there is data */
+ while (1) {
+ int rc = despotify_get_pcm(ctx->session, &ctx->pcm);
+
+ if (rc == 0 && ctx->pcm.len) {
+ ctx->len_available = ctx->pcm.len;
+ break;
+ }
+ if (ctx->eof == true)
+ break;
+
+ if (rc < 0) {
+ g_debug("despotify_get_pcm error\n");
+ ctx->eof = true;
+ break;
+ }
+
+ /* Wait a while until next iteration */
+ usleep(50 * 1000);
+ }
+}
+
+static void callback(G_GNUC_UNUSED struct despotify_session* ds,
+ int sig, G_GNUC_UNUSED void* data, void* callback_data)
+{
+ DespotifyInputStream *ctx = (DespotifyInputStream *)callback_data;
+
+ switch (sig) {
+ case DESPOTIFY_NEW_TRACK:
+ break;
+
+ case DESPOTIFY_TIME_TELL:
+ break;
+
+ case DESPOTIFY_TRACK_PLAY_ERROR:
+ g_debug("Track play error\n");
+ ctx->eof = true;
+ ctx->len_available = 0;
+ break;
+
+ case DESPOTIFY_END_OF_PLAYLIST:
+ ctx->eof = true;
+ g_debug("End of playlist: %d\n", ctx->eof);
+ break;
+ }
+}
+
+
+static struct input_stream *
+input_despotify_open(const char *url,
+ Mutex &mutex, Cond &cond,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct despotify_session *session;
+ struct ds_link *ds_link;
+ struct ds_track *track;
+
+ if (!g_str_has_prefix(url, "spt://"))
+ return NULL;
+
+ session = mpd_despotify_get_session();
+ if (!session)
+ return NULL;
+
+ ds_link = despotify_link_from_uri(url + 6);
+ if (!ds_link) {
+ g_debug("Can't find %s\n", url);
+ return NULL;
+ }
+ if (ds_link->type != LINK_TYPE_TRACK) {
+ despotify_free_link(ds_link);
+ return NULL;
+ }
+
+ track = despotify_link_get_track(session, ds_link);
+ despotify_free_link(ds_link);
+ if (!track)
+ return NULL;
+
+ DespotifyInputStream *ctx =
+ new DespotifyInputStream(url, mutex, cond,
+ session, track);
+
+ if (!mpd_despotify_register_callback(callback, ctx)) {
+ delete ctx;
+ return NULL;
+ }
+
+ if (despotify_play(ctx->session, ctx->track, false) == false) {
+ mpd_despotify_unregister_callback(callback);
+ delete ctx;
+ return NULL;
+ }
+
+ return &ctx->base;
+}
+
+static size_t
+input_despotify_read(struct input_stream *is, void *ptr, size_t size,
+ G_GNUC_UNUSED GError **error_r)
+{
+ DespotifyInputStream *ctx = (DespotifyInputStream *)is;
+ size_t to_cpy = size;
+
+ if (ctx->len_available == 0)
+ refill_buffer(ctx);
+
+ if (ctx->len_available < size)
+ to_cpy = ctx->len_available;
+ memcpy(ptr, ctx->pcm.buf, to_cpy);
+ ctx->len_available -= to_cpy;
+
+ is->offset += to_cpy;
+
+ return to_cpy;
+}
+
+static void
+input_despotify_close(struct input_stream *is)
+{
+ DespotifyInputStream *ctx = (DespotifyInputStream *)is;
+
+ mpd_despotify_unregister_callback(callback);
+ delete ctx;
+}
+
+static bool
+input_despotify_eof(struct input_stream *is)
+{
+ DespotifyInputStream *ctx = (DespotifyInputStream *)is;
+
+ return ctx->eof;
+}
+
+static bool
+input_despotify_seek(G_GNUC_UNUSED struct input_stream *is,
+ G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence,
+ G_GNUC_UNUSED GError **error_r)
+{
+ return false;
+}
+
+static struct tag *
+input_despotify_tag(struct input_stream *is)
+{
+ DespotifyInputStream *ctx = (DespotifyInputStream *)is;
+ struct tag *tag = ctx->tag;
+
+ ctx->tag = NULL;
+
+ return tag;
+}
+
+const struct input_plugin input_plugin_despotify = {
+ "spt",
+ nullptr,
+ nullptr,
+ input_despotify_open,
+ input_despotify_close,
+ nullptr,
+ nullptr,
+ .tag = input_despotify_tag,
+ nullptr,
+ input_despotify_read,
+ input_despotify_eof,
+ input_despotify_seek,
+};
diff --git a/src/input/DespotifyInputPlugin.hxx b/src/input/DespotifyInputPlugin.hxx
new file mode 100644
index 000000000..00d699408
--- /dev/null
+++ b/src/input/DespotifyInputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2011-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 INPUT_DESPOTIFY_HXX
+#define INPUT_DESPOTIFY_HXX
+
+extern const struct input_plugin input_plugin_despotify;
+
+#endif
diff --git a/src/input/FfmpegInputPlugin.cxx b/src/input/FfmpegInputPlugin.cxx
new file mode 100644
index 000000000..1660f177d
--- /dev/null
+++ b/src/input/FfmpegInputPlugin.cxx
@@ -0,0 +1,184 @@
+/*
+ * 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.
+ */
+
+/* necessary because libavutil/common.h uses UINT64_C */
+#define __STDC_CONSTANT_MACROS
+
+#include "config.h"
+#include "FfmpegInputPlugin.hxx"
+#include "InputInternal.hxx"
+#include "InputStream.hxx"
+#include "InputPlugin.hxx"
+
+extern "C" {
+#include <libavutil/avutil.h>
+#include <libavformat/avio.h>
+#include <libavformat/avformat.h>
+}
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "input_ffmpeg"
+
+struct FfmpegInputStream {
+ struct input_stream base;
+
+ AVIOContext *h;
+
+ bool eof;
+
+ FfmpegInputStream(const char *uri, Mutex &mutex, Cond &cond,
+ AVIOContext *_h)
+ :base(input_plugin_ffmpeg, uri, mutex, cond),
+ h(_h), eof(false) {
+ base.ready = true;
+ base.seekable = (h->seekable & AVIO_SEEKABLE_NORMAL) != 0;
+ base.size = avio_size(h);
+
+ /* hack to make MPD select the "ffmpeg" decoder plugin
+ - since avio.h doesn't tell us the MIME type of the
+ resource, we can't select a decoder plugin, but the
+ "ffmpeg" plugin is quite good at auto-detection */
+ base.mime = g_strdup("audio/x-mpd-ffmpeg");
+ }
+
+ ~FfmpegInputStream() {
+ avio_close(h);
+ }
+};
+
+static inline GQuark
+ffmpeg_quark(void)
+{
+ return g_quark_from_static_string("ffmpeg");
+}
+
+static inline bool
+input_ffmpeg_supported(void)
+{
+ void *opaque = nullptr;
+ return avio_enum_protocols(&opaque, 0) != nullptr;
+}
+
+static bool
+input_ffmpeg_init(G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ av_register_all();
+
+ /* disable this plugin if there's no registered protocol */
+ if (!input_ffmpeg_supported()) {
+ g_set_error(error_r, ffmpeg_quark(), 0,
+ "No protocol");
+ return false;
+ }
+
+ return true;
+}
+
+static struct input_stream *
+input_ffmpeg_open(const char *uri,
+ Mutex &mutex, Cond &cond,
+ GError **error_r)
+{
+ if (!g_str_has_prefix(uri, "gopher://") &&
+ !g_str_has_prefix(uri, "rtp://") &&
+ !g_str_has_prefix(uri, "rtsp://") &&
+ !g_str_has_prefix(uri, "rtmp://") &&
+ !g_str_has_prefix(uri, "rtmpt://") &&
+ !g_str_has_prefix(uri, "rtmps://"))
+ return nullptr;
+
+ AVIOContext *h;
+ int ret = avio_open(&h, uri, AVIO_FLAG_READ);
+ if (ret != 0) {
+ g_set_error(error_r, ffmpeg_quark(), ret,
+ "libavformat failed to open the URI");
+ return nullptr;
+ }
+
+ auto *i = new FfmpegInputStream(uri, mutex, cond, h);
+ return &i->base;
+}
+
+static size_t
+input_ffmpeg_read(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r)
+{
+ FfmpegInputStream *i = (FfmpegInputStream *)is;
+
+ int ret = avio_read(i->h, (unsigned char *)ptr, size);
+ if (ret <= 0) {
+ if (ret < 0)
+ g_set_error(error_r, ffmpeg_quark(), 0,
+ "url_read() failed");
+
+ i->eof = true;
+ return false;
+ }
+
+ is->offset += ret;
+ return (size_t)ret;
+}
+
+static void
+input_ffmpeg_close(struct input_stream *is)
+{
+ FfmpegInputStream *i = (FfmpegInputStream *)is;
+
+ delete i;
+}
+
+static bool
+input_ffmpeg_eof(struct input_stream *is)
+{
+ FfmpegInputStream *i = (FfmpegInputStream *)is;
+
+ return i->eof;
+}
+
+static bool
+input_ffmpeg_seek(struct input_stream *is, goffset offset, int whence,
+ G_GNUC_UNUSED GError **error_r)
+{
+ FfmpegInputStream *i = (FfmpegInputStream *)is;
+ int64_t ret = avio_seek(i->h, offset, whence);
+
+ if (ret >= 0) {
+ i->eof = false;
+ return true;
+ } else {
+ g_set_error(error_r, ffmpeg_quark(), 0, "url_seek() failed");
+ return false;
+ }
+}
+
+const struct input_plugin input_plugin_ffmpeg = {
+ "ffmpeg",
+ input_ffmpeg_init,
+ nullptr,
+ input_ffmpeg_open,
+ input_ffmpeg_close,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ input_ffmpeg_read,
+ input_ffmpeg_eof,
+ input_ffmpeg_seek,
+};
diff --git a/src/input/FfmpegInputPlugin.hxx b/src/input/FfmpegInputPlugin.hxx
new file mode 100644
index 000000000..d5e3a8d9b
--- /dev/null
+++ b/src/input/FfmpegInputPlugin.hxx
@@ -0,0 +1,28 @@
+/*
+ * 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_FFMPEG_INPUT_PLUGIN_HXX
+#define MPD_FFMPEG_INPUT_PLUGIN_HXX
+
+/**
+ * An input plugin based on libavformat's "avio" library.
+ */
+extern const struct input_plugin input_plugin_ffmpeg;
+
+#endif
diff --git a/src/input/FileInputPlugin.cxx b/src/input/FileInputPlugin.cxx
new file mode 100644
index 000000000..2eecf32b6
--- /dev/null
+++ b/src/input/FileInputPlugin.cxx
@@ -0,0 +1,164 @@
+/*
+ * 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" /* must be first for large file support */
+#include "FileInputPlugin.hxx"
+#include "InputInternal.hxx"
+#include "InputStream.hxx"
+#include "InputPlugin.hxx"
+#include "fd_util.h"
+#include "open.h"
+#include "io_error.h"
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <glib.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "input_file"
+
+struct FileInputStream {
+ struct input_stream base;
+
+ int fd;
+
+ FileInputStream(const char *path, int _fd, off_t size,
+ Mutex &mutex, Cond &cond)
+ :base(input_plugin_file, path, mutex, cond),
+ fd(_fd) {
+ base.size = size;
+ base.seekable = true;
+ base.ready = true;
+ }
+
+ ~FileInputStream() {
+ close(fd);
+ }
+};
+
+static struct input_stream *
+input_file_open(const char *filename,
+ Mutex &mutex, Cond &cond,
+ GError **error_r)
+{
+ int fd, ret;
+ struct stat st;
+
+ if (!g_path_is_absolute(filename))
+ return nullptr;
+
+ fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0);
+ if (fd < 0) {
+ if (errno != ENOENT && errno != ENOTDIR)
+ g_set_error(error_r, errno_quark(), errno,
+ "Failed to open \"%s\": %s",
+ filename, g_strerror(errno));
+ return nullptr;
+ }
+
+ ret = fstat(fd, &st);
+ if (ret < 0) {
+ g_set_error(error_r, errno_quark(), errno,
+ "Failed to stat \"%s\": %s",
+ filename, g_strerror(errno));
+ close(fd);
+ return nullptr;
+ }
+
+ if (!S_ISREG(st.st_mode)) {
+ g_set_error(error_r, errno_quark(), 0,
+ "Not a regular file: %s", filename);
+ close(fd);
+ return nullptr;
+ }
+
+#ifdef POSIX_FADV_SEQUENTIAL
+ posix_fadvise(fd, (off_t)0, st.st_size, POSIX_FADV_SEQUENTIAL);
+#endif
+
+ FileInputStream *fis = new FileInputStream(filename, fd, st.st_size,
+ mutex, cond);
+ return &fis->base;
+}
+
+static bool
+input_file_seek(struct input_stream *is, goffset offset, int whence,
+ GError **error_r)
+{
+ FileInputStream *fis = (FileInputStream *)is;
+
+ offset = (goffset)lseek(fis->fd, (off_t)offset, whence);
+ if (offset < 0) {
+ g_set_error(error_r, errno_quark(), errno,
+ "Failed to seek: %s", g_strerror(errno));
+ return false;
+ }
+
+ is->offset = offset;
+ return true;
+}
+
+static size_t
+input_file_read(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r)
+{
+ FileInputStream *fis = (FileInputStream *)is;
+ ssize_t nbytes;
+
+ nbytes = read(fis->fd, ptr, size);
+ if (nbytes < 0) {
+ g_set_error(error_r, errno_quark(), errno,
+ "Failed to read: %s", g_strerror(errno));
+ return 0;
+ }
+
+ is->offset += nbytes;
+ return (size_t)nbytes;
+}
+
+static void
+input_file_close(struct input_stream *is)
+{
+ FileInputStream *fis = (FileInputStream *)is;
+
+ delete fis;
+}
+
+static bool
+input_file_eof(struct input_stream *is)
+{
+ return is->offset >= is->size;
+}
+
+const struct input_plugin input_plugin_file = {
+ "file",
+ nullptr,
+ nullptr,
+ input_file_open,
+ input_file_close,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ input_file_read,
+ input_file_eof,
+ input_file_seek,
+};
diff --git a/src/input/FileInputPlugin.hxx b/src/input/FileInputPlugin.hxx
new file mode 100644
index 000000000..aacfd0b5d
--- /dev/null
+++ b/src/input/FileInputPlugin.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_INPUT_FILE_HXX
+#define MPD_INPUT_FILE_HXX
+
+extern const struct input_plugin input_plugin_file;
+
+#endif
diff --git a/src/input/MmsInputPlugin.cxx b/src/input/MmsInputPlugin.cxx
new file mode 100644
index 000000000..b347eb92b
--- /dev/null
+++ b/src/input/MmsInputPlugin.cxx
@@ -0,0 +1,147 @@
+/*
+ * 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 "MmsInputPlugin.hxx"
+#include "InputInternal.hxx"
+#include "InputStream.hxx"
+#include "InputPlugin.hxx"
+
+#include <glib.h>
+#include <libmms/mmsx.h>
+
+#include <string.h>
+#include <errno.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "input_mms"
+
+struct MmsInputStream {
+ struct input_stream base;
+
+ mmsx_t *mms;
+
+ bool eof;
+
+ MmsInputStream(const char *uri,
+ Mutex &mutex, Cond &cond,
+ mmsx_t *_mms)
+ :base(input_plugin_mms, uri, mutex, cond),
+ mms(_mms), eof(false) {
+ /* XX is this correct? at least this selects the ffmpeg
+ decoder, which seems to work fine*/
+ base.mime = g_strdup("audio/x-ms-wma");
+
+ base.ready = true;
+ }
+
+ ~MmsInputStream() {
+ mmsx_close(mms);
+ }
+};
+
+static inline GQuark
+mms_quark(void)
+{
+ return g_quark_from_static_string("mms");
+}
+
+static struct input_stream *
+input_mms_open(const char *url,
+ Mutex &mutex, Cond &cond,
+ GError **error_r)
+{
+ if (!g_str_has_prefix(url, "mms://") &&
+ !g_str_has_prefix(url, "mmsh://") &&
+ !g_str_has_prefix(url, "mmst://") &&
+ !g_str_has_prefix(url, "mmsu://"))
+ return nullptr;
+
+ const auto mms = mmsx_connect(nullptr, nullptr, url, 128 * 1024);
+ if (mms == nullptr) {
+ g_set_error(error_r, mms_quark(), 0, "mmsx_connect() failed");
+ return nullptr;
+ }
+
+ auto m = new MmsInputStream(url, mutex, cond, mms);
+ return &m->base;
+}
+
+static size_t
+input_mms_read(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r)
+{
+ MmsInputStream *m = (MmsInputStream *)is;
+ int ret;
+
+ ret = mmsx_read(nullptr, m->mms, (char *)ptr, size);
+ if (ret <= 0) {
+ if (ret < 0) {
+ g_set_error(error_r, mms_quark(), errno,
+ "mmsx_read() failed: %s",
+ g_strerror(errno));
+ }
+
+ m->eof = true;
+ return false;
+ }
+
+ is->offset += ret;
+
+ return (size_t)ret;
+}
+
+static void
+input_mms_close(struct input_stream *is)
+{
+ MmsInputStream *m = (MmsInputStream *)is;
+
+ delete m;
+}
+
+static bool
+input_mms_eof(struct input_stream *is)
+{
+ MmsInputStream *m = (MmsInputStream *)is;
+
+ return m->eof;
+}
+
+static bool
+input_mms_seek(G_GNUC_UNUSED struct input_stream *is,
+ G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence,
+ G_GNUC_UNUSED GError **error_r)
+{
+ return false;
+}
+
+const struct input_plugin input_plugin_mms = {
+ "mms",
+ nullptr,
+ nullptr,
+ input_mms_open,
+ input_mms_close,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ input_mms_read,
+ input_mms_eof,
+ input_mms_seek,
+};
diff --git a/src/input/mms_input_plugin.h b/src/input/MmsInputPlugin.hxx
index d6aa593f2..d6aa593f2 100644
--- a/src/input/mms_input_plugin.h
+++ b/src/input/MmsInputPlugin.hxx
diff --git a/src/input/RewindInputPlugin.cxx b/src/input/RewindInputPlugin.cxx
new file mode 100644
index 000000000..d93d7d1ce
--- /dev/null
+++ b/src/input/RewindInputPlugin.cxx
@@ -0,0 +1,256 @@
+/*
+ * 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 "RewindInputPlugin.hxx"
+#include "InputInternal.hxx"
+#include "InputStream.hxx"
+#include "InputPlugin.hxx"
+#include "tag.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <stdio.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "input_rewind"
+
+extern const struct input_plugin rewind_input_plugin;
+
+struct RewindInputStream {
+ struct input_stream base;
+
+ struct input_stream *input;
+
+ /**
+ * The read position within the buffer. Undefined as long as
+ * ReadingFromBuffer() returns false.
+ */
+ size_t head;
+
+ /**
+ * The write/append position within the buffer.
+ */
+ size_t tail;
+
+ /**
+ * The size of this buffer is the maximum number of bytes
+ * which can be rewinded cheaply without passing the "seek"
+ * call to CURL.
+ *
+ * The origin of this buffer is always the beginning of the
+ * stream (offset 0).
+ */
+ char buffer[64 * 1024];
+
+ RewindInputStream(input_stream *_input)
+ :base(rewind_input_plugin, _input->uri.c_str(),
+ _input->mutex, _input->cond),
+ input(_input), tail(0) {
+ }
+
+ ~RewindInputStream() {
+ input_stream_close(input);
+ }
+
+ /**
+ * Are we currently reading from the buffer, and does the
+ * buffer contain more data for the next read operation?
+ */
+ bool ReadingFromBuffer() const {
+ return tail > 0 && base.offset < input->offset;
+ }
+
+ /**
+ * Copy public attributes from the underlying input stream to the
+ * "rewind" input stream. This function is called when a method of
+ * the underlying stream has returned, which may have modified these
+ * attributes.
+ */
+ void CopyAttributes() {
+ struct input_stream *dest = &base;
+ const struct input_stream *src = input;
+
+ assert(dest != src);
+
+ bool dest_ready = dest->ready;
+
+ dest->ready = src->ready;
+ dest->seekable = src->seekable;
+ dest->size = src->size;
+ dest->offset = src->offset;
+
+ if (!dest_ready && src->ready)
+ dest->mime = src->mime;
+ }
+};
+
+static void
+input_rewind_close(struct input_stream *is)
+{
+ RewindInputStream *r = (RewindInputStream *)is;
+
+ delete r;
+}
+
+static bool
+input_rewind_check(struct input_stream *is, GError **error_r)
+{
+ RewindInputStream *r = (RewindInputStream *)is;
+
+ return input_stream_check(r->input, error_r);
+}
+
+static void
+input_rewind_update(struct input_stream *is)
+{
+ RewindInputStream *r = (RewindInputStream *)is;
+
+ if (!r->ReadingFromBuffer())
+ r->CopyAttributes();
+}
+
+static struct tag *
+input_rewind_tag(struct input_stream *is)
+{
+ RewindInputStream *r = (RewindInputStream *)is;
+
+ return input_stream_tag(r->input);
+}
+
+static bool
+input_rewind_available(struct input_stream *is)
+{
+ RewindInputStream *r = (RewindInputStream *)is;
+
+ return input_stream_available(r->input);
+}
+
+static size_t
+input_rewind_read(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r)
+{
+ RewindInputStream *r = (RewindInputStream *)is;
+
+ if (r->ReadingFromBuffer()) {
+ /* buffered read */
+
+ assert(r->head == (size_t)is->offset);
+ assert(r->tail == (size_t)r->input->offset);
+
+ if (size > r->tail - r->head)
+ size = r->tail - r->head;
+
+ memcpy(ptr, r->buffer + r->head, size);
+ r->head += size;
+ is->offset += size;
+
+ return size;
+ } else {
+ /* pass method call to underlying stream */
+
+ size_t nbytes = input_stream_read(r->input, ptr, size, error_r);
+
+ if (r->input->offset > (goffset)sizeof(r->buffer))
+ /* disable buffering */
+ r->tail = 0;
+ else if (r->tail == (size_t)is->offset) {
+ /* append to buffer */
+
+ memcpy(r->buffer + r->tail, ptr, nbytes);
+ r->tail += nbytes;
+
+ assert(r->tail == (size_t)r->input->offset);
+ }
+
+ r->CopyAttributes();
+
+ return nbytes;
+ }
+}
+
+static bool
+input_rewind_eof(struct input_stream *is)
+{
+ RewindInputStream *r = (RewindInputStream *)is;
+
+ return !r->ReadingFromBuffer() && input_stream_eof(r->input);
+}
+
+static bool
+input_rewind_seek(struct input_stream *is, goffset offset, int whence,
+ GError **error_r)
+{
+ RewindInputStream *r = (RewindInputStream *)is;
+
+ assert(is->ready);
+
+ if (whence == SEEK_SET && r->tail > 0 && offset <= (goffset)r->tail) {
+ /* buffered seek */
+
+ assert(!r->ReadingFromBuffer() ||
+ r->head == (size_t)is->offset);
+ assert(r->tail == (size_t)r->input->offset);
+
+ r->head = (size_t)offset;
+ is->offset = offset;
+
+ return true;
+ } else {
+ bool success = input_stream_seek(r->input, offset, whence,
+ error_r);
+ r->CopyAttributes();
+
+ /* disable the buffer, because r->input has left the
+ buffered range now */
+ r->tail = 0;
+
+ return success;
+ }
+}
+
+const struct input_plugin rewind_input_plugin = {
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ input_rewind_close,
+ input_rewind_check,
+ input_rewind_update,
+ input_rewind_tag,
+ input_rewind_available,
+ input_rewind_read,
+ input_rewind_eof,
+ input_rewind_seek,
+};
+
+struct input_stream *
+input_rewind_open(struct input_stream *is)
+{
+ assert(is != NULL);
+ assert(is->offset == 0);
+
+ if (is->seekable)
+ /* seekable resources don't need this plugin */
+ return is;
+
+ RewindInputStream *c = new RewindInputStream(is);
+ return &c->base;
+}
diff --git a/src/input/RewindInputPlugin.hxx b/src/input/RewindInputPlugin.hxx
new file mode 100644
index 000000000..cf21e92f1
--- /dev/null
+++ b/src/input/RewindInputPlugin.hxx
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+/** \file
+ *
+ * A wrapper for an input_stream object which allows cheap buffered
+ * rewinding. This is useful while detecting the stream codec (let
+ * each decoder plugin peek a portion from the stream).
+ */
+
+#ifndef MPD_INPUT_REWIND_HXX
+#define MPD_INPUT_REWIND_HXX
+
+#include "check.h"
+
+struct input_stream;
+
+struct input_stream *
+input_rewind_open(struct input_stream *is);
+
+#endif
diff --git a/src/input/SoupInputPlugin.cxx b/src/input/SoupInputPlugin.cxx
new file mode 100644
index 000000000..e9767c20e
--- /dev/null
+++ b/src/input/SoupInputPlugin.cxx
@@ -0,0 +1,492 @@
+/*
+ * 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 "SoupInputPlugin.hxx"
+#include "InputPlugin.hxx"
+#include "InputStream.hxx"
+#include "InputInternal.hxx"
+#include "IOThread.hxx"
+#include "event/Loop.hxx"
+#include "conf.h"
+
+extern "C" {
+#include <libsoup/soup-uri.h>
+#include <libsoup/soup-session-async.h>
+}
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "input_soup"
+
+/**
+ * Do not buffer more than this number of bytes. It should be a
+ * reasonable limit that doesn't make low-end machines suffer too
+ * much, but doesn't cause stuttering on high-latency lines.
+ */
+static const size_t SOUP_MAX_BUFFERED = 512 * 1024;
+
+/**
+ * Resume the stream at this number of bytes after it has been paused.
+ */
+static const size_t SOUP_RESUME_AT = 384 * 1024;
+
+static SoupURI *soup_proxy;
+static SoupSession *soup_session;
+
+struct SoupInputStream {
+ struct input_stream base;
+
+ SoupMessage *msg;
+
+ GQueue *buffers;
+
+ size_t current_consumed;
+
+ size_t total_buffered;
+
+ bool alive, pause, eof;
+
+ /**
+ * Set when the session callback has been invoked, when it is
+ * safe to free this object.
+ */
+ bool completed;
+
+ GError *postponed_error;
+
+ SoupInputStream(const char *uri, Mutex &mutex, Cond &cond);
+ ~SoupInputStream();
+
+ bool CopyError(const SoupMessage *msg);
+
+ bool WaitData();
+
+ size_t Read(void *ptr, size_t size, GError **error_r);
+};
+
+static inline GQuark
+soup_quark(void)
+{
+ return g_quark_from_static_string("soup");
+}
+
+static bool
+input_soup_init(const struct config_param *param, GError **error_r)
+{
+ assert(soup_proxy == NULL);
+ assert(soup_session == NULL);
+
+ g_type_init();
+
+ const char *proxy = config_get_block_string(param, "proxy", NULL);
+
+ if (proxy != NULL) {
+ soup_proxy = soup_uri_new(proxy);
+ if (soup_proxy == NULL) {
+ g_set_error(error_r, soup_quark(), 0,
+ "failed to parse proxy setting");
+ return false;
+ }
+ }
+
+ soup_session =
+ soup_session_async_new_with_options(SOUP_SESSION_PROXY_URI,
+ soup_proxy,
+ SOUP_SESSION_ASYNC_CONTEXT,
+ io_thread_get().GetContext(),
+ NULL);
+
+ return true;
+}
+
+static void
+input_soup_finish(void)
+{
+ assert(soup_session != NULL);
+
+ soup_session_abort(soup_session);
+ g_object_unref(G_OBJECT(soup_session));
+
+ if (soup_proxy != NULL)
+ soup_uri_free(soup_proxy);
+}
+
+/**
+ * Copy the error from the SoupMessage object to
+ * input_soup::postponed_error.
+ *
+ * @return true if there was no error
+ */
+bool
+SoupInputStream::CopyError(const SoupMessage *src)
+{
+ if (SOUP_STATUS_IS_SUCCESSFUL(src->status_code))
+ return true;
+
+ if (src->status_code == SOUP_STATUS_CANCELLED)
+ /* failure, but don't generate a GError, because this
+ status was caused by _close() */
+ return false;
+
+ if (postponed_error != nullptr)
+ /* there's already a GError, don't overwrite it */
+ return false;
+
+ if (SOUP_STATUS_IS_TRANSPORT_ERROR(src->status_code))
+ postponed_error =
+ g_error_new(soup_quark(), src->status_code,
+ "HTTP client error: %s",
+ src->reason_phrase);
+ else
+ postponed_error =
+ g_error_new(soup_quark(), src->status_code,
+ "got HTTP status: %d %s",
+ src->status_code, src->reason_phrase);
+
+ return false;
+}
+
+static void
+input_soup_session_callback(G_GNUC_UNUSED SoupSession *session,
+ SoupMessage *msg, gpointer user_data)
+{
+ SoupInputStream *s = (SoupInputStream *)user_data;
+
+ assert(msg == s->msg);
+ assert(!s->completed);
+
+ const ScopeLock protect(s->base.mutex);
+
+ if (!s->base.ready)
+ s->CopyError(msg);
+
+ s->base.ready = true;
+ s->alive = false;
+ s->completed = true;
+
+ s->base.cond.broadcast();
+}
+
+static void
+input_soup_got_headers(SoupMessage *msg, gpointer user_data)
+{
+ SoupInputStream *s = (SoupInputStream *)user_data;
+
+ s->base.mutex.lock();
+
+ if (!s->CopyError(msg)) {
+ s->base.mutex.unlock();
+
+ soup_session_cancel_message(soup_session, msg,
+ SOUP_STATUS_CANCELLED);
+ return;
+ }
+
+ s->base.ready = true;
+ s->base.cond.broadcast();
+ s->base.mutex.unlock();
+
+ soup_message_body_set_accumulate(msg->response_body, false);
+}
+
+static void
+input_soup_got_chunk(SoupMessage *msg, SoupBuffer *chunk, gpointer user_data)
+{
+ SoupInputStream *s = (SoupInputStream *)user_data;
+
+ assert(msg == s->msg);
+
+ const ScopeLock protect(s->base.mutex);
+
+ g_queue_push_tail(s->buffers, soup_buffer_copy(chunk));
+ s->total_buffered += chunk->length;
+
+ if (s->total_buffered >= SOUP_MAX_BUFFERED && !s->pause) {
+ s->pause = true;
+ soup_session_pause_message(soup_session, msg);
+ }
+
+ s->base.cond.broadcast();
+ s->base.mutex.unlock();
+}
+
+static void
+input_soup_got_body(G_GNUC_UNUSED SoupMessage *msg, gpointer user_data)
+{
+ SoupInputStream *s = (SoupInputStream *)user_data;
+
+ assert(msg == s->msg);
+
+ const ScopeLock protect(s->base.mutex);
+
+ s->base.ready = true;
+ s->eof = true;
+ s->alive = false;
+
+ s->base.cond.broadcast();
+ s->base.mutex.unlock();
+}
+
+inline bool
+SoupInputStream::WaitData()
+{
+ while (true) {
+ if (eof)
+ return true;
+
+ if (!alive)
+ return false;
+
+ if (!g_queue_is_empty(buffers))
+ return true;
+
+ assert(current_consumed == 0);
+
+ base.cond.wait(base.mutex);
+ }
+}
+
+static gpointer
+input_soup_queue(gpointer data)
+{
+ SoupInputStream *s = (SoupInputStream *)data;
+
+ soup_session_queue_message(soup_session, s->msg,
+ input_soup_session_callback, s);
+
+ return NULL;
+}
+
+SoupInputStream::SoupInputStream(const char *uri,
+ Mutex &mutex, Cond &cond)
+ :base(input_plugin_soup, uri, mutex, cond),
+ buffers(g_queue_new()),
+ current_consumed(0), total_buffered(0),
+ alive(false), pause(false), eof(false), completed(false),
+ postponed_error(nullptr)
+{
+#if GCC_CHECK_VERSION(4,6)
+#pragma GCC diagnostic push
+ /* the libsoup macro SOUP_METHOD_GET discards the "const"
+ attribute of the g_intern_static_string() return value;
+ don't make the gcc warning fatal: */
+#pragma GCC diagnostic ignored "-Wcast-qual"
+#endif
+
+ msg = soup_message_new(SOUP_METHOD_GET, uri);
+
+#if GCC_CHECK_VERSION(4,6)
+#pragma GCC diagnostic pop
+#endif
+
+ soup_message_set_flags(msg, SOUP_MESSAGE_NO_REDIRECT);
+
+ soup_message_headers_append(msg->request_headers, "User-Agent",
+ "Music Player Daemon " VERSION);
+
+ g_signal_connect(msg, "got-headers",
+ G_CALLBACK(input_soup_got_headers), this);
+ g_signal_connect(msg, "got-chunk",
+ G_CALLBACK(input_soup_got_chunk), this);
+ g_signal_connect(msg, "got-body",
+ G_CALLBACK(input_soup_got_body), this);
+
+ io_thread_call(input_soup_queue, this);
+}
+
+static struct input_stream *
+input_soup_open(const char *uri,
+ Mutex &mutex, Cond &cond,
+ G_GNUC_UNUSED GError **error_r)
+{
+ if (strncmp(uri, "http://", 7) != 0)
+ return NULL;
+
+ SoupInputStream *s = new SoupInputStream(uri, mutex, cond);
+ return &s->base;
+}
+
+static gpointer
+input_soup_cancel(gpointer data)
+{
+ SoupInputStream *s = (SoupInputStream *)data;
+
+ if (!s->completed)
+ soup_session_cancel_message(soup_session, s->msg,
+ SOUP_STATUS_CANCELLED);
+
+ return NULL;
+}
+
+SoupInputStream::~SoupInputStream()
+{
+ base.mutex.lock();
+
+ if (!completed) {
+ /* the messages's session callback hasn't been invoked
+ yet; cancel it and wait for completion */
+
+ base.mutex.unlock();
+
+ io_thread_call(input_soup_cancel, this);
+
+ base.mutex.lock();
+ while (!completed)
+ base.cond.wait(base.mutex);
+ }
+
+ base.mutex.unlock();
+
+ SoupBuffer *buffer;
+ while ((buffer = (SoupBuffer *)g_queue_pop_head(buffers)) != NULL)
+ soup_buffer_free(buffer);
+ g_queue_free(buffers);
+
+ if (postponed_error != NULL)
+ g_error_free(postponed_error);
+}
+
+static void
+input_soup_close(struct input_stream *is)
+{
+ SoupInputStream *s = (SoupInputStream *)is;
+
+ delete s;
+}
+
+static bool
+input_soup_check(struct input_stream *is, GError **error_r)
+{
+ SoupInputStream *s = (SoupInputStream *)is;
+
+ bool success = s->postponed_error == NULL;
+ if (!success) {
+ g_propagate_error(error_r, s->postponed_error);
+ s->postponed_error = NULL;
+ }
+
+ return success;
+}
+
+static bool
+input_soup_available(struct input_stream *is)
+{
+ SoupInputStream *s = (SoupInputStream *)is;
+
+ return s->eof || !s->alive || !g_queue_is_empty(s->buffers);
+}
+
+inline size_t
+SoupInputStream::Read(void *ptr, size_t size, GError **error_r)
+{
+ if (!WaitData()) {
+ assert(!alive);
+
+ if (postponed_error != nullptr) {
+ g_propagate_error(error_r, postponed_error);
+ postponed_error = nullptr;
+ } else
+ g_set_error_literal(error_r, soup_quark(), 0,
+ "HTTP failure");
+ return 0;
+ }
+
+ char *p0 = (char *)ptr, *p = p0, *p_end = p0 + size;
+
+ while (p < p_end) {
+ SoupBuffer *buffer = (SoupBuffer *)
+ g_queue_pop_head(buffers);
+ if (buffer == NULL) {
+ assert(current_consumed == 0);
+ break;
+ }
+
+ assert(current_consumed < buffer->length);
+ assert(total_buffered >= buffer->length);
+
+ const char *q = buffer->data;
+ q += current_consumed;
+
+ size_t remaining = buffer->length - current_consumed;
+ size_t nbytes = p_end - p;
+ if (nbytes > remaining)
+ nbytes = remaining;
+
+ memcpy(p, q, nbytes);
+ p += nbytes;
+
+ current_consumed += remaining;
+ if (current_consumed >= buffer->length) {
+ /* done with this buffer */
+ total_buffered -= buffer->length;
+ soup_buffer_free(buffer);
+ current_consumed = 0;
+ } else {
+ /* partial read */
+ assert(p == p_end);
+
+ g_queue_push_head(buffers, buffer);
+ }
+ }
+
+ if (pause && total_buffered < SOUP_RESUME_AT) {
+ pause = false;
+ soup_session_unpause_message(soup_session, msg);
+ }
+
+ size_t nbytes = p - p0;
+ base.offset += nbytes;
+
+ return nbytes;
+}
+
+static size_t
+input_soup_read(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r)
+{
+ SoupInputStream *s = (SoupInputStream *)is;
+
+ return s->Read(ptr, size, error_r);
+}
+
+static bool
+input_soup_eof(G_GNUC_UNUSED struct input_stream *is)
+{
+ SoupInputStream *s = (SoupInputStream *)is;
+
+ return !s->alive && g_queue_is_empty(s->buffers);
+}
+
+const struct input_plugin input_plugin_soup = {
+ "soup",
+ input_soup_init,
+ input_soup_finish,
+ input_soup_open,
+ input_soup_close,
+ input_soup_check,
+ nullptr,
+ nullptr,
+ input_soup_available,
+ input_soup_read,
+ input_soup_eof,
+ nullptr,
+};
diff --git a/src/input/SoupInputPlugin.hxx b/src/input/SoupInputPlugin.hxx
new file mode 100644
index 000000000..4c089b39b
--- /dev/null
+++ b/src/input/SoupInputPlugin.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_INPUT_SOUP_HXX
+#define MPD_INPUT_SOUP_HXX
+
+extern const struct input_plugin input_plugin_soup;
+
+#endif
diff --git a/src/input/archive_input_plugin.c b/src/input/archive_input_plugin.c
deleted file mode 100644
index 4a038b9e2..000000000
--- a/src/input/archive_input_plugin.c
+++ /dev/null
@@ -1,81 +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 "input/archive_input_plugin.h"
-#include "archive_api.h"
-#include "archive_list.h"
-#include "input_plugin.h"
-
-#include <glib.h>
-
-/**
- * select correct archive plugin to handle the input stream
- * may allow stacking of archive plugins. for example for handling
- * tar.gz a gzip handler opens file (through inputfile stream)
- * then it opens a tar handler and sets gzip inputstream as
- * parent_stream so tar plugin fetches file data from gzip
- * plugin and gzip fetches file from disk
- */
-static struct input_stream *
-input_archive_open(const char *pathname,
- GMutex *mutex, GCond *cond,
- GError **error_r)
-{
- const struct archive_plugin *arplug;
- struct archive_file *file;
- char *archive, *filename, *suffix, *pname;
- struct input_stream *is;
-
- if (!g_path_is_absolute(pathname))
- return NULL;
-
- pname = g_strdup(pathname);
- // archive_lookup will modify pname when true is returned
- if (!archive_lookup(pname, &archive, &filename, &suffix)) {
- g_debug("not an archive, lookup %s failed\n", pname);
- g_free(pname);
- return NULL;
- }
-
- //check which archive plugin to use (by ext)
- arplug = archive_plugin_from_suffix(suffix);
- if (!arplug) {
- g_warning("can't handle archive %s\n",archive);
- g_free(pname);
- return NULL;
- }
-
- file = archive_file_open(arplug, archive, error_r);
- if (file == NULL)
- return NULL;
-
- //setup fileops
- is = archive_file_open_stream(file, filename, mutex, cond,
- error_r);
- archive_file_close(file);
- g_free(pname);
-
- return is;
-}
-
-const struct input_plugin input_plugin_archive = {
- .name = "archive",
- .open = input_archive_open,
-};
diff --git a/src/input/archive_input_plugin.h b/src/input/archive_input_plugin.h
deleted file mode 100644
index 51095f37f..000000000
--- a/src/input/archive_input_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_INPUT_ARCHIVE_H
-#define MPD_INPUT_ARCHIVE_H
-
-extern const struct input_plugin input_plugin_archive;
-
-#endif
diff --git a/src/input/cdio_paranoia_input_plugin.c b/src/input/cdio_paranoia_input_plugin.c
deleted file mode 100644
index 1de7623a1..000000000
--- a/src/input/cdio_paranoia_input_plugin.c
+++ /dev/null
@@ -1,371 +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.
- */
-
-/**
- * CD-Audio handling (requires libcdio_paranoia)
- */
-
-#include "config.h"
-#include "input/cdio_paranoia_input_plugin.h"
-#include "input_internal.h"
-#include "input_plugin.h"
-#include "refcount.h"
-
-#include <stdio.h>
-#include <stdint.h>
-#include <stddef.h>
-#include <string.h>
-#include <stdlib.h>
-#include <glib.h>
-#include <assert.h>
-
-#include <cdio/paranoia.h>
-#include <cdio/cd_types.h>
-
-struct input_cdio_paranoia {
- struct input_stream base;
-
- cdrom_drive_t *drv;
- CdIo_t *cdio;
- cdrom_paranoia_t *para;
-
- lsn_t lsn_from, lsn_to;
- int lsn_relofs;
-
- int trackno;
-
- char buffer[CDIO_CD_FRAMESIZE_RAW];
- int buffer_lsn;
-};
-
-static inline GQuark
-cdio_quark(void)
-{
- return g_quark_from_static_string("cdio");
-}
-
-static void
-input_cdio_close(struct input_stream *is)
-{
- struct input_cdio_paranoia *i = (struct input_cdio_paranoia *)is;
-
- if (i->para)
- cdio_paranoia_free(i->para);
- if (i->drv)
- cdio_cddap_close_no_free_cdio( i->drv);
- if (i->cdio)
- cdio_destroy( i->cdio );
-
- input_stream_deinit(&i->base);
- g_free(i);
-}
-
-struct cdio_uri {
- char device[64];
- int track;
-};
-
-static bool
-parse_cdio_uri(struct cdio_uri *dest, const char *src, GError **error_r)
-{
- if (!g_str_has_prefix(src, "cdda://"))
- return false;
-
- src += 7;
-
- if (*src == 0) {
- /* play the whole CD in the default drive */
- dest->device[0] = 0;
- dest->track = -1;
- return true;
- }
-
- const char *slash = strrchr(src, '/');
- if (slash == NULL) {
- /* play the whole CD in the specified drive */
- g_strlcpy(dest->device, src, sizeof(dest->device));
- dest->track = -1;
- return true;
- }
-
- size_t device_length = slash - src;
- if (device_length >= sizeof(dest->device))
- device_length = sizeof(dest->device) - 1;
-
- memcpy(dest->device, src, device_length);
- dest->device[device_length] = 0;
-
- const char *track = slash + 1;
-
- char *endptr;
- dest->track = strtoul(track, &endptr, 10);
- if (*endptr != 0) {
- g_set_error(error_r, cdio_quark(), 0,
- "Malformed track number");
- return false;
- }
-
- if (endptr == track)
- /* play the whole CD */
- dest->track = -1;
-
- return true;
-}
-
-static char *
-cdio_detect_device(void)
-{
- char **devices = cdio_get_devices_with_cap(NULL, CDIO_FS_AUDIO, false);
- if (devices == NULL)
- return NULL;
-
- char *device = g_strdup(devices[0]);
- cdio_free_device_list(devices);
-
- return device;
-}
-
-static struct input_stream *
-input_cdio_open(const char *uri,
- GMutex *mutex, GCond *cond,
- GError **error_r)
-{
- struct input_cdio_paranoia *i;
-
- struct cdio_uri parsed_uri;
- if (!parse_cdio_uri(&parsed_uri, uri, error_r))
- return NULL;
-
- i = g_new(struct input_cdio_paranoia, 1);
- input_stream_init(&i->base, &input_plugin_cdio_paranoia, uri,
- mutex, cond);
-
- /* initialize everything (should be already) */
- i->drv = NULL;
- i->cdio = NULL;
- i->para = NULL;
- i->trackno = parsed_uri.track;
-
- /* get list of CD's supporting CD-DA */
- char *device = parsed_uri.device[0] != 0
- ? g_strdup(parsed_uri.device)
- : cdio_detect_device();
- if (device == NULL) {
- g_set_error(error_r, cdio_quark(), 0,
- "Unable find or access a CD-ROM drive with an audio CD in it.");
- input_cdio_close(&i->base);
- return NULL;
- }
-
- /* Found such a CD-ROM with a CD-DA loaded. Use the first drive in the list. */
- i->cdio = cdio_open(device, DRIVER_UNKNOWN);
- g_free(device);
-
- i->drv = cdio_cddap_identify_cdio(i->cdio, 1, NULL);
-
- if ( !i->drv ) {
- g_set_error(error_r, cdio_quark(), 0,
- "Unable to identify audio CD disc.");
- input_cdio_close(&i->base);
- return NULL;
- }
-
- cdda_verbose_set(i->drv, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT);
-
- if ( 0 != cdio_cddap_open(i->drv) ) {
- g_set_error(error_r, cdio_quark(), 0, "Unable to open disc.");
- input_cdio_close(&i->base);
- return NULL;
- }
-
- bool reverse_endian;
- switch (data_bigendianp(i->drv)) {
- case -1:
- g_debug("cdda: drive returns unknown audio data");
- reverse_endian = false;
- break;
- case 0:
- g_debug("cdda: drive returns audio data Little Endian.");
- reverse_endian = G_BYTE_ORDER == G_BIG_ENDIAN;
- break;
- case 1:
- g_debug("cdda: drive returns audio data Big Endian.");
- reverse_endian = G_BYTE_ORDER == G_LITTLE_ENDIAN;
- break;
- default:
- g_set_error(error_r, cdio_quark(), 0,
- "Drive returns unknown data type %d",
- data_bigendianp(i->drv));
- input_cdio_close(&i->base);
- return NULL;
- }
-
- i->lsn_relofs = 0;
-
- if (i->trackno >= 0) {
- i->lsn_from = cdio_get_track_lsn(i->cdio, i->trackno);
- i->lsn_to = cdio_get_track_last_lsn(i->cdio, i->trackno);
- } else {
- i->lsn_from = 0;
- i->lsn_to = cdio_get_disc_last_lsn(i->cdio);
- }
-
- i->para = cdio_paranoia_init(i->drv);
-
- /* Set reading mode for full paranoia, but allow skipping sectors. */
- paranoia_modeset(i->para, PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP);
-
- /* seek to beginning of the track */
- cdio_paranoia_seek(i->para, i->lsn_from, SEEK_SET);
-
- i->base.ready = true;
- i->base.seekable = true;
- i->base.size = (i->lsn_to - i->lsn_from + 1) * CDIO_CD_FRAMESIZE_RAW;
-
- /* hack to make MPD select the "pcm" decoder plugin */
- i->base.mime = g_strdup(reverse_endian
- ? "audio/x-mpd-cdda-pcm-reverse"
- : "audio/x-mpd-cdda-pcm");
-
- return &i->base;
-}
-
-static bool
-input_cdio_seek(struct input_stream *is,
- goffset offset, int whence, GError **error_r)
-{
- struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is;
-
- /* calculate absolute offset */
- switch (whence) {
- case SEEK_SET:
- break;
- case SEEK_CUR:
- offset += cis->base.offset;
- break;
- case SEEK_END:
- offset += cis->base.size;
- break;
- }
-
- if (offset < 0 || offset > cis->base.size) {
- g_set_error(error_r, cdio_quark(), 0,
- "Invalid offset to seek %ld (%ld)",
- (long int)offset, (long int)cis->base.size);
- return false;
- }
-
- /* simple case */
- if (offset == cis->base.offset)
- return true;
-
- /* calculate current LSN */
- cis->lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW;
- cis->base.offset = offset;
-
- cdio_paranoia_seek(cis->para, cis->lsn_from + cis->lsn_relofs, SEEK_SET);
-
- return true;
-}
-
-static size_t
-input_cdio_read(struct input_stream *is, void *ptr, size_t length,
- GError **error_r)
-{
- struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is;
- size_t nbytes = 0;
- int diff;
- size_t len, maxwrite;
- int16_t *rbuf;
- char *s_err, *s_mess;
- char *wptr = (char *) ptr;
-
- while (length > 0) {
-
-
- /* end of track ? */
- if (cis->lsn_from + cis->lsn_relofs > cis->lsn_to)
- break;
-
- //current sector was changed ?
- if (cis->lsn_relofs != cis->buffer_lsn) {
- rbuf = cdio_paranoia_read(cis->para, NULL);
-
- s_err = cdda_errors(cis->drv);
- if (s_err) {
- g_warning("paranoia_read: %s", s_err );
- free(s_err);
- }
- s_mess = cdda_messages(cis->drv);
- if (s_mess) {
- free(s_mess);
- }
- if (!rbuf) {
- g_set_error(error_r, cdio_quark(), 0,
- "paranoia read error. Stopping.");
- return 0;
- }
- //store current buffer
- memcpy(cis->buffer, rbuf, CDIO_CD_FRAMESIZE_RAW);
- cis->buffer_lsn = cis->lsn_relofs;
- } else {
- //use cached sector
- rbuf = (int16_t*) cis->buffer;
- }
-
- //correct offset
- diff = cis->base.offset - cis->lsn_relofs * CDIO_CD_FRAMESIZE_RAW;
-
- assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW);
-
- maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer
- len = (length < maxwrite? length : maxwrite);
-
- //skip diff bytes from this lsn
- memcpy(wptr, ((char*)rbuf) + diff, len);
- //update pointer
- wptr += len;
- nbytes += len;
-
- //update offset
- cis->base.offset += len;
- cis->lsn_relofs = cis->base.offset / CDIO_CD_FRAMESIZE_RAW;
- //update length
- length -= len;
- }
-
- return nbytes;
-}
-
-static bool
-input_cdio_eof(struct input_stream *is)
-{
- struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is;
-
- return (cis->lsn_from + cis->lsn_relofs > cis->lsn_to);
-}
-
-const struct input_plugin input_plugin_cdio_paranoia = {
- .name = "cdio_paranoia",
- .open = input_cdio_open,
- .close = input_cdio_close,
- .seek = input_cdio_seek,
- .read = input_cdio_read,
- .eof = input_cdio_eof
-};
diff --git a/src/input/cdio_paranoia_input_plugin.h b/src/input/cdio_paranoia_input_plugin.h
deleted file mode 100644
index 71c5cbe8d..000000000
--- a/src/input/cdio_paranoia_input_plugin.h
+++ /dev/null
@@ -1,28 +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_CDIO_PARANOIA_INPUT_PLUGIN_H
-#define MPD_CDIO_PARANOIA_INPUT_PLUGIN_H
-
-/**
- * An input plugin based on libcdio_paranoia library.
- */
-extern const struct input_plugin input_plugin_cdio_paranoia;
-
-#endif
diff --git a/src/input/curl_input_plugin.c b/src/input/curl_input_plugin.c
deleted file mode 100644
index 3f191141e..000000000
--- a/src/input/curl_input_plugin.c
+++ /dev/null
@@ -1,1301 +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 "input/curl_input_plugin.h"
-#include "input_internal.h"
-#include "input_plugin.h"
-#include "conf.h"
-#include "tag.h"
-#include "icy_metadata.h"
-#include "io_thread.h"
-#include "glib_compat.h"
-
-#include <assert.h>
-
-#if defined(WIN32)
- #include <winsock2.h>
-#else
- #include <sys/select.h>
-#endif
-
-#include <string.h>
-#include <errno.h>
-
-#include <curl/curl.h>
-#include <glib.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "input_curl"
-
-/**
- * Do not buffer more than this number of bytes. It should be a
- * reasonable limit that doesn't make low-end machines suffer too
- * much, but doesn't cause stuttering on high-latency lines.
- */
-static const size_t CURL_MAX_BUFFERED = 512 * 1024;
-
-/**
- * Resume the stream at this number of bytes after it has been paused.
- */
-static const size_t CURL_RESUME_AT = 384 * 1024;
-
-/**
- * Buffers created by input_curl_writefunction().
- */
-struct buffer {
- /** size of the payload */
- size_t size;
-
- /** how much has been consumed yet? */
- size_t consumed;
-
- /** the payload */
- unsigned char data[sizeof(long)];
-};
-
-struct input_curl {
- struct input_stream base;
-
- /* some buffers which were passed to libcurl, which we have
- too free */
- char *url, *range;
- struct curl_slist *request_headers;
-
- /** the curl handles */
- CURL *easy;
-
- /** the GMainLoop source used to poll all CURL file
- descriptors */
- GSource *source;
-
- /** the source id of #source */
- guint source_id;
-
- /** a linked list of all registered GPollFD objects */
- GSList *fds;
-
- /** list of buffers, where input_curl_writefunction() appends
- to, and input_curl_read() reads from them */
- GQueue *buffers;
-
-#if LIBCURL_VERSION_NUM >= 0x071200
- /**
- * Is the connection currently paused? That happens when the
- * buffer was getting too large. It will be unpaused when the
- * buffer is below the threshold again.
- */
- bool paused;
-#endif
-
- /** error message provided by libcurl */
- char error[CURL_ERROR_SIZE];
-
- /** parser for icy-metadata */
- struct icy_metadata icy_metadata;
-
- /** the stream name from the icy-name response header */
- char *meta_name;
-
- /** the tag object ready to be requested via
- input_stream_tag() */
- struct tag *tag;
-
- GError *postponed_error;
-};
-
-/** libcurl should accept "ICY 200 OK" */
-static struct curl_slist *http_200_aliases;
-
-/** HTTP proxy settings */
-static const char *proxy, *proxy_user, *proxy_password;
-static unsigned proxy_port;
-
-static struct {
- CURLM *multi;
-
- /**
- * A linked list of all active HTTP requests. An active
- * request is one that doesn't have the "eof" flag set.
- */
- GSList *requests;
-
- /**
- * The GMainLoop source used to poll all CURL file
- * descriptors.
- */
- GSource *source;
-
- /**
- * The source id of #source.
- */
- guint source_id;
-
- GSList *fds;
-
-#if LIBCURL_VERSION_NUM >= 0x070f04
- /**
- * Did CURL give us a timeout? If yes, then we need to call
- * curl_multi_perform(), even if there was no event on any
- * file descriptor.
- */
- bool timeout;
-
- /**
- * The absolute time stamp when the timeout expires. This is
- * used in the GSource method check().
- */
- gint64 absolute_timeout;
-#endif
-} curl;
-
-static inline GQuark
-curl_quark(void)
-{
- return g_quark_from_static_string("curl");
-}
-
-/**
- * Find a request by its CURL "easy" handle.
- *
- * Runs in the I/O thread. No lock needed.
- */
-static struct input_curl *
-input_curl_find_request(CURL *easy)
-{
- assert(io_thread_inside());
-
- for (GSList *i = curl.requests; i != NULL; i = g_slist_next(i)) {
- struct input_curl *c = i->data;
- if (c->easy == easy)
- return c;
- }
-
- return NULL;
-}
-
-#if LIBCURL_VERSION_NUM >= 0x071200
-
-static gpointer
-input_curl_resume(gpointer data)
-{
- assert(io_thread_inside());
-
- struct input_curl *c = data;
-
- if (c->paused) {
- c->paused = false;
- curl_easy_pause(c->easy, CURLPAUSE_CONT);
- }
-
- return NULL;
-}
-
-#endif
-
-/**
- * Calculates the GLib event bit mask for one file descriptor,
- * obtained from three #fd_set objects filled by curl_multi_fdset().
- */
-static gushort
-input_curl_fd_events(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds)
-{
- gushort events = 0;
-
- if (FD_ISSET(fd, rfds)) {
- events |= G_IO_IN | G_IO_HUP | G_IO_ERR;
- FD_CLR(fd, rfds);
- }
-
- if (FD_ISSET(fd, wfds)) {
- events |= G_IO_OUT | G_IO_ERR;
- FD_CLR(fd, wfds);
- }
-
- if (FD_ISSET(fd, efds)) {
- events |= G_IO_HUP | G_IO_ERR;
- FD_CLR(fd, efds);
- }
-
- return events;
-}
-
-/**
- * Updates all registered GPollFD objects, unregisters old ones,
- * registers new ones.
- *
- * Runs in the I/O thread. No lock needed.
- */
-static void
-curl_update_fds(void)
-{
- assert(io_thread_inside());
-
- fd_set rfds, wfds, efds;
-
- FD_ZERO(&rfds);
- FD_ZERO(&wfds);
- FD_ZERO(&efds);
-
- int max_fd;
- CURLMcode mcode = curl_multi_fdset(curl.multi, &rfds, &wfds,
- &efds, &max_fd);
- if (mcode != CURLM_OK) {
- g_warning("curl_multi_fdset() failed: %s\n",
- curl_multi_strerror(mcode));
- return;
- }
-
- GSList *fds = curl.fds;
- curl.fds = NULL;
-
- while (fds != NULL) {
- GPollFD *poll_fd = fds->data;
- gushort events = input_curl_fd_events(poll_fd->fd, &rfds,
- &wfds, &efds);
-
- assert(poll_fd->events != 0);
-
- fds = g_slist_remove(fds, poll_fd);
-
- if (events != poll_fd->events)
- g_source_remove_poll(curl.source, poll_fd);
-
- if (events != 0) {
- if (events != poll_fd->events) {
- poll_fd->events = events;
- g_source_add_poll(curl.source, poll_fd);
- }
-
- curl.fds = g_slist_prepend(curl.fds, poll_fd);
- } else {
- g_free(poll_fd);
- }
- }
-
- for (int fd = 0; fd <= max_fd; ++fd) {
- gushort events = input_curl_fd_events(fd, &rfds, &wfds, &efds);
- if (events != 0) {
- GPollFD *poll_fd = g_new(GPollFD, 1);
- poll_fd->fd = fd;
- poll_fd->events = events;
- g_source_add_poll(curl.source, poll_fd);
- curl.fds = g_slist_prepend(curl.fds, poll_fd);
- }
- }
-}
-
-/**
- * Runs in the I/O thread. No lock needed.
- */
-static bool
-input_curl_easy_add(struct input_curl *c, GError **error_r)
-{
- assert(io_thread_inside());
- assert(c != NULL);
- assert(c->easy != NULL);
- assert(input_curl_find_request(c->easy) == NULL);
-
- curl.requests = g_slist_prepend(curl.requests, c);
-
- CURLMcode mcode = curl_multi_add_handle(curl.multi, c->easy);
- if (mcode != CURLM_OK) {
- g_set_error(error_r, curl_quark(), mcode,
- "curl_multi_add_handle() failed: %s",
- curl_multi_strerror(mcode));
- return false;
- }
-
- curl_update_fds();
-
- return true;
-}
-
-struct easy_add_params {
- struct input_curl *c;
- GError **error_r;
-};
-
-static gpointer
-input_curl_easy_add_callback(gpointer data)
-{
- const struct easy_add_params *params = data;
-
- bool success = input_curl_easy_add(params->c, params->error_r);
- return GUINT_TO_POINTER(success);
-}
-
-/**
- * Call input_curl_easy_add() in the I/O thread. May be called from
- * any thread. Caller must not hold a mutex.
- */
-static bool
-input_curl_easy_add_indirect(struct input_curl *c, GError **error_r)
-{
- assert(c != NULL);
- assert(c->easy != NULL);
-
- struct easy_add_params params = {
- .c = c,
- .error_r = error_r,
- };
-
- gpointer result =
- io_thread_call(input_curl_easy_add_callback, &params);
- return GPOINTER_TO_UINT(result);
-}
-
-/**
- * Frees the current "libcurl easy" handle, and everything associated
- * with it.
- *
- * Runs in the I/O thread.
- */
-static void
-input_curl_easy_free(struct input_curl *c)
-{
- assert(io_thread_inside());
- assert(c != NULL);
-
- if (c->easy == NULL)
- return;
-
- curl.requests = g_slist_remove(curl.requests, c);
-
- curl_multi_remove_handle(curl.multi, c->easy);
- curl_easy_cleanup(c->easy);
- c->easy = NULL;
-
- curl_slist_free_all(c->request_headers);
- c->request_headers = NULL;
-
- g_free(c->range);
- c->range = NULL;
-}
-
-static gpointer
-input_curl_easy_free_callback(gpointer data)
-{
- struct input_curl *c = data;
-
- input_curl_easy_free(c);
- curl_update_fds();
-
- return NULL;
-}
-
-/**
- * Frees the current "libcurl easy" handle, and everything associated
- * with it.
- *
- * The mutex must not be locked.
- */
-static void
-input_curl_easy_free_indirect(struct input_curl *c)
-{
- io_thread_call(input_curl_easy_free_callback, c);
- assert(c->easy == NULL);
-}
-
-/**
- * Abort and free all HTTP requests.
- *
- * Runs in the I/O thread. The caller must not hold locks.
- */
-static void
-input_curl_abort_all_requests(GError *error)
-{
- assert(io_thread_inside());
- assert(error != NULL);
-
- while (curl.requests != NULL) {
- struct input_curl *c = curl.requests->data;
- assert(c->postponed_error == NULL);
-
- input_curl_easy_free(c);
-
- g_mutex_lock(c->base.mutex);
- c->postponed_error = g_error_copy(error);
- c->base.ready = true;
- g_cond_broadcast(c->base.cond);
- g_mutex_unlock(c->base.mutex);
- }
-
- g_error_free(error);
-
-}
-
-/**
- * A HTTP request is finished.
- *
- * Runs in the I/O thread. The caller must not hold locks.
- */
-static void
-input_curl_request_done(struct input_curl *c, CURLcode result, long status)
-{
- assert(io_thread_inside());
- assert(c != NULL);
- assert(c->easy == NULL);
- assert(c->postponed_error == NULL);
-
- g_mutex_lock(c->base.mutex);
-
- if (result != CURLE_OK) {
- c->postponed_error = g_error_new(curl_quark(), result,
- "curl failed: %s",
- c->error);
- } else if (status < 200 || status >= 300) {
- c->postponed_error = g_error_new(curl_quark(), 0,
- "got HTTP status %ld",
- status);
- }
-
- c->base.ready = true;
- g_cond_broadcast(c->base.cond);
- g_mutex_unlock(c->base.mutex);
-}
-
-static void
-input_curl_handle_done(CURL *easy_handle, CURLcode result)
-{
- struct input_curl *c = input_curl_find_request(easy_handle);
- assert(c != NULL);
-
- long status = 0;
- curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status);
-
- input_curl_easy_free(c);
- input_curl_request_done(c, result, status);
-}
-
-/**
- * Check for finished HTTP responses.
- *
- * Runs in the I/O thread. The caller must not hold locks.
- */
-static void
-input_curl_info_read(void)
-{
- assert(io_thread_inside());
-
- CURLMsg *msg;
- int msgs_in_queue;
-
- while ((msg = curl_multi_info_read(curl.multi,
- &msgs_in_queue)) != NULL) {
- if (msg->msg == CURLMSG_DONE)
- input_curl_handle_done(msg->easy_handle, msg->data.result);
- }
-}
-
-/**
- * Give control to CURL.
- *
- * Runs in the I/O thread. The caller must not hold locks.
- */
-static bool
-input_curl_perform(void)
-{
- assert(io_thread_inside());
-
- CURLMcode mcode;
-
- do {
- int running_handles;
- mcode = curl_multi_perform(curl.multi, &running_handles);
- } while (mcode == CURLM_CALL_MULTI_PERFORM);
-
- if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) {
- GError *error = g_error_new(curl_quark(), mcode,
- "curl_multi_perform() failed: %s",
- curl_multi_strerror(mcode));
- input_curl_abort_all_requests(error);
- return false;
- }
-
- return true;
-}
-
-/*
- * GSource methods
- *
- */
-
-/**
- * The GSource prepare() method implementation.
- */
-static gboolean
-input_curl_source_prepare(G_GNUC_UNUSED GSource *source, gint *timeout_r)
-{
- curl_update_fds();
-
-#if LIBCURL_VERSION_NUM >= 0x070f04
- curl.timeout = false;
-
- long timeout2;
- CURLMcode mcode = curl_multi_timeout(curl.multi, &timeout2);
- if (mcode == CURLM_OK) {
- if (timeout2 >= 0)
- curl.absolute_timeout = g_source_get_time(source)
- + timeout2 * 1000;
-
- if (timeout2 >= 0 && timeout2 < 10)
- /* CURL 7.21.1 likes to report "timeout=0",
- which means we're running in a busy loop.
- Quite a bad idea to waste so much CPU.
- Let's use a lower limit of 10ms. */
- timeout2 = 10;
-
- *timeout_r = timeout2;
-
- curl.timeout = timeout2 >= 0;
- } else
- g_warning("curl_multi_timeout() failed: %s\n",
- curl_multi_strerror(mcode));
-#else
- (void)timeout_r;
-#endif
-
- return false;
-}
-
-/**
- * The GSource check() method implementation.
- */
-static gboolean
-input_curl_source_check(G_GNUC_UNUSED GSource *source)
-{
-#if LIBCURL_VERSION_NUM >= 0x070f04
- if (curl.timeout) {
- /* when a timeout has expired, we need to call
- curl_multi_perform(), even if there was no file
- descriptor event */
-
- if (g_source_get_time(source) >= curl.absolute_timeout)
- return true;
- }
-#endif
-
- for (GSList *i = curl.fds; i != NULL; i = i->next) {
- GPollFD *poll_fd = i->data;
- if (poll_fd->revents != 0)
- return true;
- }
-
- return false;
-}
-
-/**
- * The GSource dispatch() method implementation. The callback isn't
- * used, because we're handling all events directly.
- */
-static gboolean
-input_curl_source_dispatch(G_GNUC_UNUSED GSource *source,
- G_GNUC_UNUSED GSourceFunc callback,
- G_GNUC_UNUSED gpointer user_data)
-{
- if (input_curl_perform())
- input_curl_info_read();
-
- return true;
-}
-
-/**
- * The vtable for our GSource implementation. Unfortunately, we
- * cannot declare it "const", because g_source_new() takes a non-const
- * pointer, for whatever reason.
- */
-static GSourceFuncs curl_source_funcs = {
- .prepare = input_curl_source_prepare,
- .check = input_curl_source_check,
- .dispatch = input_curl_source_dispatch,
-};
-
-/*
- * input_plugin methods
- *
- */
-
-static bool
-input_curl_init(const struct config_param *param,
- G_GNUC_UNUSED GError **error_r)
-{
- CURLcode code = curl_global_init(CURL_GLOBAL_ALL);
- if (code != CURLE_OK) {
- g_set_error(error_r, curl_quark(), code,
- "curl_global_init() failed: %s\n",
- curl_easy_strerror(code));
- return false;
- }
-
- http_200_aliases = curl_slist_append(http_200_aliases, "ICY 200 OK");
-
- proxy = config_get_block_string(param, "proxy", NULL);
- proxy_port = config_get_block_unsigned(param, "proxy_port", 0);
- proxy_user = config_get_block_string(param, "proxy_user", NULL);
- proxy_password = config_get_block_string(param, "proxy_password",
- NULL);
-
- if (proxy == NULL) {
- /* deprecated proxy configuration */
- proxy = config_get_string(CONF_HTTP_PROXY_HOST, NULL);
- proxy_port = config_get_positive(CONF_HTTP_PROXY_PORT, 0);
- proxy_user = config_get_string(CONF_HTTP_PROXY_USER, NULL);
- proxy_password = config_get_string(CONF_HTTP_PROXY_PASSWORD,
- "");
- }
-
- curl.multi = curl_multi_init();
- if (curl.multi == NULL) {
- g_set_error(error_r, curl_quark(), 0,
- "curl_multi_init() failed");
- return false;
- }
-
- curl.source = g_source_new(&curl_source_funcs, sizeof(*curl.source));
- curl.source_id = g_source_attach(curl.source, io_thread_context());
-
- return true;
-}
-
-static gpointer
-curl_destroy_sources(G_GNUC_UNUSED gpointer data)
-{
- g_source_destroy(curl.source);
-
- return NULL;
-}
-
-static void
-input_curl_finish(void)
-{
- assert(curl.requests == NULL);
-
- io_thread_call(curl_destroy_sources, NULL);
-
- curl_multi_cleanup(curl.multi);
-
- curl_slist_free_all(http_200_aliases);
-
- curl_global_cleanup();
-}
-
-#if LIBCURL_VERSION_NUM >= 0x071200
-
-/**
- * Determine the total sizes of all buffers, including portions that
- * have already been consumed.
- *
- * The caller must lock the mutex.
- */
-G_GNUC_PURE
-static size_t
-curl_total_buffer_size(const struct input_curl *c)
-{
- size_t total = 0;
-
- for (GList *i = g_queue_peek_head_link(c->buffers);
- i != NULL; i = g_list_next(i)) {
- struct buffer *buffer = i->data;
- total += buffer->size;
- }
-
- return total;
-}
-
-#endif
-
-static void
-buffer_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct buffer *buffer = data;
-
- assert(buffer->consumed <= buffer->size);
-
- g_free(buffer);
-}
-
-static void
-input_curl_flush_buffers(struct input_curl *c)
-{
- g_queue_foreach(c->buffers, buffer_free_callback, NULL);
- g_queue_clear(c->buffers);
-}
-
-/**
- * Frees this stream, including the input_stream struct.
- */
-static void
-input_curl_free(struct input_curl *c)
-{
- if (c->tag != NULL)
- tag_free(c->tag);
- g_free(c->meta_name);
-
- input_curl_easy_free_indirect(c);
- input_curl_flush_buffers(c);
-
- g_queue_free(c->buffers);
-
- if (c->postponed_error != NULL)
- g_error_free(c->postponed_error);
-
- g_free(c->url);
- input_stream_deinit(&c->base);
- g_free(c);
-}
-
-static bool
-input_curl_check(struct input_stream *is, GError **error_r)
-{
- struct input_curl *c = (struct input_curl *)is;
-
- bool success = c->postponed_error == NULL;
- if (!success) {
- g_propagate_error(error_r, c->postponed_error);
- c->postponed_error = NULL;
- }
-
- return success;
-}
-
-static struct tag *
-input_curl_tag(struct input_stream *is)
-{
- struct input_curl *c = (struct input_curl *)is;
- struct tag *tag = c->tag;
-
- c->tag = NULL;
- return tag;
-}
-
-static bool
-fill_buffer(struct input_curl *c, GError **error_r)
-{
- while (c->easy != NULL && g_queue_is_empty(c->buffers))
- g_cond_wait(c->base.cond, c->base.mutex);
-
- if (c->postponed_error != NULL) {
- g_propagate_error(error_r, c->postponed_error);
- c->postponed_error = NULL;
- return false;
- }
-
- return !g_queue_is_empty(c->buffers);
-}
-
-/**
- * Mark a part of the buffer object as consumed.
- */
-static struct buffer *
-consume_buffer(struct buffer *buffer, size_t length)
-{
- assert(buffer != NULL);
- assert(buffer->consumed < buffer->size);
-
- buffer->consumed += length;
- if (buffer->consumed < buffer->size)
- return buffer;
-
- assert(buffer->consumed == buffer->size);
-
- g_free(buffer);
-
- return NULL;
-}
-
-static size_t
-read_from_buffer(struct icy_metadata *icy_metadata, GQueue *buffers,
- void *dest0, size_t length)
-{
- struct buffer *buffer = g_queue_pop_head(buffers);
- uint8_t *dest = dest0;
- size_t nbytes = 0;
-
- assert(buffer->size > 0);
- assert(buffer->consumed < buffer->size);
-
- if (length > buffer->size - buffer->consumed)
- length = buffer->size - buffer->consumed;
-
- while (true) {
- size_t chunk;
-
- chunk = icy_data(icy_metadata, length);
- if (chunk > 0) {
- memcpy(dest, buffer->data + buffer->consumed,
- chunk);
- buffer = consume_buffer(buffer, chunk);
-
- nbytes += chunk;
- dest += chunk;
- length -= chunk;
-
- if (length == 0)
- break;
-
- assert(buffer != NULL);
- }
-
- chunk = icy_meta(icy_metadata, buffer->data + buffer->consumed,
- length);
- if (chunk > 0) {
- buffer = consume_buffer(buffer, chunk);
-
- length -= chunk;
-
- if (length == 0)
- break;
-
- assert(buffer != NULL);
- }
- }
-
- if (buffer != NULL)
- g_queue_push_head(buffers, buffer);
-
- return nbytes;
-}
-
-static void
-copy_icy_tag(struct input_curl *c)
-{
- struct tag *tag = icy_tag(&c->icy_metadata);
-
- if (tag == NULL)
- return;
-
- if (c->tag != NULL)
- tag_free(c->tag);
-
- if (c->meta_name != NULL && !tag_has_type(tag, TAG_NAME))
- tag_add_item(tag, TAG_NAME, c->meta_name);
-
- c->tag = tag;
-}
-
-static bool
-input_curl_available(struct input_stream *is)
-{
- struct input_curl *c = (struct input_curl *)is;
-
- return c->postponed_error != NULL || c->easy == NULL ||
- !g_queue_is_empty(c->buffers);
-}
-
-static size_t
-input_curl_read(struct input_stream *is, void *ptr, size_t size,
- GError **error_r)
-{
- struct input_curl *c = (struct input_curl *)is;
- bool success;
- size_t nbytes = 0;
- char *dest = ptr;
-
- do {
- /* fill the buffer */
-
- success = fill_buffer(c, error_r);
- if (!success)
- return 0;
-
- /* send buffer contents */
-
- while (size > 0 && !g_queue_is_empty(c->buffers)) {
- size_t copy = read_from_buffer(&c->icy_metadata, c->buffers,
- dest + nbytes, size);
-
- nbytes += copy;
- size -= copy;
- }
- } while (nbytes == 0);
-
- if (icy_defined(&c->icy_metadata))
- copy_icy_tag(c);
-
- is->offset += (goffset)nbytes;
-
-#if LIBCURL_VERSION_NUM >= 0x071200
- if (c->paused && curl_total_buffer_size(c) < CURL_RESUME_AT) {
- g_mutex_unlock(c->base.mutex);
- io_thread_call(input_curl_resume, c);
- g_mutex_lock(c->base.mutex);
- }
-#endif
-
- return nbytes;
-}
-
-static void
-input_curl_close(struct input_stream *is)
-{
- struct input_curl *c = (struct input_curl *)is;
-
- input_curl_free(c);
-}
-
-static bool
-input_curl_eof(G_GNUC_UNUSED struct input_stream *is)
-{
- struct input_curl *c = (struct input_curl *)is;
-
- return c->easy == NULL && g_queue_is_empty(c->buffers);
-}
-
-/** called by curl when new data is available */
-static size_t
-input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
-{
- struct input_curl *c = (struct input_curl *)stream;
- const char *header = ptr, *end, *value;
- char name[64];
-
- size *= nmemb;
- end = header + size;
-
- value = memchr(header, ':', size);
- if (value == NULL || (size_t)(value - header) >= sizeof(name))
- return size;
-
- memcpy(name, header, value - header);
- name[value - header] = 0;
-
- /* skip the colon */
-
- ++value;
-
- /* strip the value */
-
- while (value < end && g_ascii_isspace(*value))
- ++value;
-
- while (end > value && g_ascii_isspace(end[-1]))
- --end;
-
- if (g_ascii_strcasecmp(name, "accept-ranges") == 0) {
- /* a stream with icy-metadata is not seekable */
- if (!icy_defined(&c->icy_metadata))
- c->base.seekable = true;
- } else if (g_ascii_strcasecmp(name, "content-length") == 0) {
- char buffer[64];
-
- if ((size_t)(end - header) >= sizeof(buffer))
- return size;
-
- memcpy(buffer, value, end - value);
- buffer[end - value] = 0;
-
- c->base.size = c->base.offset + g_ascii_strtoull(buffer, NULL, 10);
- } else if (g_ascii_strcasecmp(name, "content-type") == 0) {
- g_free(c->base.mime);
- c->base.mime = g_strndup(value, end - value);
- } else if (g_ascii_strcasecmp(name, "icy-name") == 0 ||
- g_ascii_strcasecmp(name, "ice-name") == 0 ||
- g_ascii_strcasecmp(name, "x-audiocast-name") == 0) {
- g_free(c->meta_name);
- c->meta_name = g_strndup(value, end - value);
-
- if (c->tag != NULL)
- tag_free(c->tag);
-
- c->tag = tag_new();
- tag_add_item(c->tag, TAG_NAME, c->meta_name);
- } else if (g_ascii_strcasecmp(name, "icy-metaint") == 0) {
- char buffer[64];
- size_t icy_metaint;
-
- if ((size_t)(end - header) >= sizeof(buffer) ||
- icy_defined(&c->icy_metadata))
- return size;
-
- memcpy(buffer, value, end - value);
- buffer[end - value] = 0;
-
- icy_metaint = g_ascii_strtoull(buffer, NULL, 10);
- g_debug("icy-metaint=%zu", icy_metaint);
-
- if (icy_metaint > 0) {
- icy_start(&c->icy_metadata, icy_metaint);
-
- /* a stream with icy-metadata is not
- seekable */
- c->base.seekable = false;
- }
- }
-
- return size;
-}
-
-/** called by curl when new data is available */
-static size_t
-input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream)
-{
- struct input_curl *c = (struct input_curl *)stream;
- struct buffer *buffer;
-
- size *= nmemb;
- if (size == 0)
- return 0;
-
- g_mutex_lock(c->base.mutex);
-
-#if LIBCURL_VERSION_NUM >= 0x071200
- if (curl_total_buffer_size(c) + size >= CURL_MAX_BUFFERED) {
- c->paused = true;
- g_mutex_unlock(c->base.mutex);
- return CURL_WRITEFUNC_PAUSE;
- }
-#endif
-
- buffer = g_malloc(sizeof(*buffer) - sizeof(buffer->data) + size);
- buffer->size = size;
- buffer->consumed = 0;
- memcpy(buffer->data, ptr, size);
-
- g_queue_push_tail(c->buffers, buffer);
- c->base.ready = true;
-
- g_cond_broadcast(c->base.cond);
- g_mutex_unlock(c->base.mutex);
-
- return size;
-}
-
-static bool
-input_curl_easy_init(struct input_curl *c, GError **error_r)
-{
- CURLcode code;
-
- c->easy = curl_easy_init();
- if (c->easy == NULL) {
- g_set_error(error_r, curl_quark(), 0,
- "curl_easy_init() failed");
- return false;
- }
-
- curl_easy_setopt(c->easy, CURLOPT_USERAGENT,
- "Music Player Daemon " VERSION);
- curl_easy_setopt(c->easy, CURLOPT_HEADERFUNCTION,
- input_curl_headerfunction);
- curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, c);
- curl_easy_setopt(c->easy, CURLOPT_WRITEFUNCTION,
- input_curl_writefunction);
- curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, c);
- curl_easy_setopt(c->easy, CURLOPT_HTTP200ALIASES, http_200_aliases);
- curl_easy_setopt(c->easy, CURLOPT_FOLLOWLOCATION, 1);
- curl_easy_setopt(c->easy, CURLOPT_NETRC, 1);
- curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5);
- curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, true);
- curl_easy_setopt(c->easy, CURLOPT_ERRORBUFFER, c->error);
- curl_easy_setopt(c->easy, CURLOPT_NOPROGRESS, 1l);
- curl_easy_setopt(c->easy, CURLOPT_NOSIGNAL, 1l);
- curl_easy_setopt(c->easy, CURLOPT_CONNECTTIMEOUT, 10l);
-
- if (proxy != NULL)
- curl_easy_setopt(c->easy, CURLOPT_PROXY, proxy);
-
- if (proxy_port > 0)
- curl_easy_setopt(c->easy, CURLOPT_PROXYPORT, (long)proxy_port);
-
- if (proxy_user != NULL && proxy_password != NULL) {
- char *proxy_auth_str =
- g_strconcat(proxy_user, ":", proxy_password, NULL);
- curl_easy_setopt(c->easy, CURLOPT_PROXYUSERPWD, proxy_auth_str);
- g_free(proxy_auth_str);
- }
-
- code = curl_easy_setopt(c->easy, CURLOPT_URL, c->url);
- if (code != CURLE_OK) {
- g_set_error(error_r, curl_quark(), code,
- "curl_easy_setopt() failed: %s",
- curl_easy_strerror(code));
- return false;
- }
-
- c->request_headers = NULL;
- c->request_headers = curl_slist_append(c->request_headers,
- "Icy-Metadata: 1");
- curl_easy_setopt(c->easy, CURLOPT_HTTPHEADER, c->request_headers);
-
- return true;
-}
-
-static bool
-input_curl_seek(struct input_stream *is, goffset offset, int whence,
- GError **error_r)
-{
- struct input_curl *c = (struct input_curl *)is;
- bool ret;
-
- assert(is->ready);
-
- if (whence == SEEK_SET && offset == is->offset)
- /* no-op */
- return true;
-
- if (!is->seekable)
- return false;
-
- /* calculate the absolute offset */
-
- switch (whence) {
- case SEEK_SET:
- break;
-
- case SEEK_CUR:
- offset += is->offset;
- break;
-
- case SEEK_END:
- if (is->size < 0)
- /* stream size is not known */
- return false;
-
- offset += is->size;
- break;
-
- default:
- return false;
- }
-
- if (offset < 0)
- return false;
-
- /* check if we can fast-forward the buffer */
-
- while (offset > is->offset && !g_queue_is_empty(c->buffers)) {
- struct buffer *buffer;
- size_t length;
-
- buffer = (struct buffer *)g_queue_pop_head(c->buffers);
-
- length = buffer->size - buffer->consumed;
- if (offset - is->offset < (goffset)length)
- length = offset - is->offset;
-
- buffer = consume_buffer(buffer, length);
- if (buffer != NULL)
- g_queue_push_head(c->buffers, buffer);
-
- is->offset += length;
- }
-
- if (offset == is->offset)
- return true;
-
- /* close the old connection and open a new one */
-
- g_mutex_unlock(c->base.mutex);
-
- input_curl_easy_free_indirect(c);
- input_curl_flush_buffers(c);
-
- is->offset = offset;
- if (is->offset == is->size) {
- /* seek to EOF: simulate empty result; avoid
- triggering a "416 Requested Range Not Satisfiable"
- response */
- return true;
- }
-
- ret = input_curl_easy_init(c, error_r);
- if (!ret)
- return false;
-
- /* send the "Range" header */
-
- if (is->offset > 0) {
- c->range = g_strdup_printf("%lld-", (long long)is->offset);
- curl_easy_setopt(c->easy, CURLOPT_RANGE, c->range);
- }
-
- c->base.ready = false;
-
- if (!input_curl_easy_add_indirect(c, error_r))
- return false;
-
- g_mutex_lock(c->base.mutex);
-
- while (!c->base.ready)
- g_cond_wait(c->base.cond, c->base.mutex);
-
- if (c->postponed_error != NULL) {
- g_propagate_error(error_r, c->postponed_error);
- c->postponed_error = NULL;
- return false;
- }
-
- return true;
-}
-
-static struct input_stream *
-input_curl_open(const char *url, GMutex *mutex, GCond *cond,
- GError **error_r)
-{
- assert(mutex != NULL);
- assert(cond != NULL);
-
- struct input_curl *c;
-
- if (strncmp(url, "http://", 7) != 0)
- return NULL;
-
- c = g_new0(struct input_curl, 1);
- input_stream_init(&c->base, &input_plugin_curl, url,
- mutex, cond);
-
- c->url = g_strdup(url);
- c->buffers = g_queue_new();
-
- icy_clear(&c->icy_metadata);
- c->tag = NULL;
-
- c->postponed_error = NULL;
-
-#if LIBCURL_VERSION_NUM >= 0x071200
- c->paused = false;
-#endif
-
- if (!input_curl_easy_init(c, error_r)) {
- input_curl_free(c);
- return NULL;
- }
-
- if (!input_curl_easy_add_indirect(c, error_r)) {
- input_curl_free(c);
- return NULL;
- }
-
- return &c->base;
-}
-
-const struct input_plugin input_plugin_curl = {
- .name = "curl",
- .init = input_curl_init,
- .finish = input_curl_finish,
-
- .open = input_curl_open,
- .close = input_curl_close,
- .check = input_curl_check,
- .tag = input_curl_tag,
- .available = input_curl_available,
- .read = input_curl_read,
- .eof = input_curl_eof,
- .seek = input_curl_seek,
-};
diff --git a/src/input/curl_input_plugin.h b/src/input/curl_input_plugin.h
deleted file mode 100644
index c6e71bf40..000000000
--- a/src/input/curl_input_plugin.h
+++ /dev/null
@@ -1,27 +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_INPUT_CURL_H
-#define MPD_INPUT_CURL_H
-
-struct input_stream;
-
-extern const struct input_plugin input_plugin_curl;
-
-#endif
diff --git a/src/input/despotify_input_plugin.c b/src/input/despotify_input_plugin.c
deleted file mode 100644
index 200a0afd6..000000000
--- a/src/input/despotify_input_plugin.c
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Copyright (C) 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 "input/despotify_input_plugin.h"
-#include "input_internal.h"
-#include "input_plugin.h"
-#include "tag.h"
-#include "despotify_utils.h"
-
-#include <glib.h>
-
-#include <unistd.h>
-#include <string.h>
-#include <errno.h>
-#include <despotify.h>
-
-#include <stdio.h>
-
-struct input_despotify {
- struct input_stream base;
-
- struct despotify_session *session;
- struct ds_track *track;
- struct tag *tag;
- struct ds_pcm_data pcm;
- size_t len_available;
- bool eof;
-};
-
-
-static void
-refill_buffer(struct input_despotify *ctx)
-{
- /* Wait until there is data */
- while (1) {
- int rc = despotify_get_pcm(ctx->session, &ctx->pcm);
-
- if (rc == 0 && ctx->pcm.len) {
- ctx->len_available = ctx->pcm.len;
- break;
- }
- if (ctx->eof == true)
- break;
-
- if (rc < 0) {
- g_debug("despotify_get_pcm error\n");
- ctx->eof = true;
- break;
- }
-
- /* Wait a while until next iteration */
- usleep(50 * 1000);
- }
-}
-
-static void callback(G_GNUC_UNUSED struct despotify_session* ds,
- int sig, G_GNUC_UNUSED void* data, void* callback_data)
-{
- struct input_despotify *ctx = (struct input_despotify *)callback_data;
-
- switch (sig) {
- case DESPOTIFY_NEW_TRACK:
- break;
-
- case DESPOTIFY_TIME_TELL:
- break;
-
- case DESPOTIFY_TRACK_PLAY_ERROR:
- g_debug("Track play error\n");
- ctx->eof = true;
- ctx->len_available = 0;
- break;
-
- case DESPOTIFY_END_OF_PLAYLIST:
- ctx->eof = true;
- g_debug("End of playlist: %d\n", ctx->eof);
- break;
- }
-}
-
-
-static struct input_stream *
-input_despotify_open(const char *url,
- GMutex *mutex, GCond *cond,
- G_GNUC_UNUSED GError **error_r)
-{
- struct input_despotify *ctx;
- struct despotify_session *session;
- struct ds_link *ds_link;
- struct ds_track *track;
-
- if (!g_str_has_prefix(url, "spt://"))
- return NULL;
-
- session = mpd_despotify_get_session();
- if (!session)
- return NULL;
-
- ds_link = despotify_link_from_uri(url + 6);
- if (!ds_link) {
- g_debug("Can't find %s\n", url);
- return NULL;
- }
- if (ds_link->type != LINK_TYPE_TRACK) {
- despotify_free_link(ds_link);
- return NULL;
- }
-
- ctx = g_new(struct input_despotify, 1);
- memset(ctx, 0, sizeof(*ctx));
-
- track = despotify_link_get_track(session, ds_link);
- despotify_free_link(ds_link);
- if (!track) {
- g_free(ctx);
- return NULL;
- }
-
- input_stream_init(&ctx->base, &input_plugin_despotify, url,
- mutex, cond);
- ctx->session = session;
- ctx->track = track;
- ctx->tag = mpd_despotify_tag_from_track(track);
- ctx->eof = false;
- /* Despotify outputs pcm data */
- ctx->base.mime = g_strdup("audio/x-mpd-cdda-pcm");
- ctx->base.ready = true;
-
- if (!mpd_despotify_register_callback(callback, ctx)) {
- despotify_free_link(ds_link);
-
- return NULL;
- }
-
- if (despotify_play(ctx->session, ctx->track, false) == false) {
- despotify_free_track(ctx->track);
- g_free(ctx);
- return NULL;
- }
-
- return &ctx->base;
-}
-
-static size_t
-input_despotify_read(struct input_stream *is, void *ptr, size_t size,
- G_GNUC_UNUSED GError **error_r)
-{
- struct input_despotify *ctx = (struct input_despotify *)is;
- size_t to_cpy = size;
-
- if (ctx->len_available == 0)
- refill_buffer(ctx);
-
- if (ctx->len_available < size)
- to_cpy = ctx->len_available;
- memcpy(ptr, ctx->pcm.buf, to_cpy);
- ctx->len_available -= to_cpy;
-
- is->offset += to_cpy;
-
- return to_cpy;
-}
-
-static void
-input_despotify_close(struct input_stream *is)
-{
- struct input_despotify *ctx = (struct input_despotify *)is;
-
- if (ctx->tag != NULL)
- tag_free(ctx->tag);
-
- mpd_despotify_unregister_callback(callback);
- despotify_free_track(ctx->track);
- input_stream_deinit(&ctx->base);
- g_free(ctx);
-}
-
-static bool
-input_despotify_eof(struct input_stream *is)
-{
- struct input_despotify *ctx = (struct input_despotify *)is;
-
- return ctx->eof;
-}
-
-static bool
-input_despotify_seek(G_GNUC_UNUSED struct input_stream *is,
- G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence,
- G_GNUC_UNUSED GError **error_r)
-{
- return false;
-}
-
-static struct tag *
-input_despotify_tag(struct input_stream *is)
-{
- struct input_despotify *ctx = (struct input_despotify *)is;
- struct tag *tag = ctx->tag;
-
- ctx->tag = NULL;
-
- return tag;
-}
-
-const struct input_plugin input_plugin_despotify = {
- .name = "spt",
- .open = input_despotify_open,
- .close = input_despotify_close,
- .read = input_despotify_read,
- .eof = input_despotify_eof,
- .seek = input_despotify_seek,
- .tag = input_despotify_tag,
-};
diff --git a/src/input/despotify_input_plugin.h b/src/input/despotify_input_plugin.h
deleted file mode 100644
index 4c070d882..000000000
--- a/src/input/despotify_input_plugin.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 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 INPUT_DESPOTIFY_H
-#define INPUT_DESPOTIFY_H
-
-extern const struct input_plugin input_plugin_despotify;
-
-#endif
diff --git a/src/input/ffmpeg_input_plugin.c b/src/input/ffmpeg_input_plugin.c
deleted file mode 100644
index 6d339a067..000000000
--- a/src/input/ffmpeg_input_plugin.c
+++ /dev/null
@@ -1,204 +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 "input/ffmpeg_input_plugin.h"
-#include "input_internal.h"
-#include "input_plugin.h"
-
-#include <libavutil/avutil.h>
-#include <libavformat/avio.h>
-#include <libavformat/avformat.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "input_ffmpeg"
-
-struct input_ffmpeg {
- struct input_stream base;
-
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0)
- AVIOContext *h;
-#else
- URLContext *h;
-#endif
-
- bool eof;
-};
-
-static inline GQuark
-ffmpeg_quark(void)
-{
- return g_quark_from_static_string("ffmpeg");
-}
-
-static inline bool
-input_ffmpeg_supported(void)
-{
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0)
- void *opaque = NULL;
- return avio_enum_protocols(&opaque, 0) != NULL;
-#else
- return av_protocol_next(NULL) != NULL;
-#endif
-}
-
-static bool
-input_ffmpeg_init(G_GNUC_UNUSED const struct config_param *param,
- G_GNUC_UNUSED GError **error_r)
-{
- av_register_all();
-
- /* disable this plugin if there's no registered protocol */
- if (!input_ffmpeg_supported()) {
- g_set_error(error_r, ffmpeg_quark(), 0,
- "No protocol");
- return false;
- }
-
- return true;
-}
-
-static struct input_stream *
-input_ffmpeg_open(const char *uri,
- GMutex *mutex, GCond *cond,
- GError **error_r)
-{
- struct input_ffmpeg *i;
-
- if (!g_str_has_prefix(uri, "gopher://") &&
- !g_str_has_prefix(uri, "rtp://") &&
- !g_str_has_prefix(uri, "rtsp://") &&
- !g_str_has_prefix(uri, "rtmp://") &&
- !g_str_has_prefix(uri, "rtmpt://") &&
- !g_str_has_prefix(uri, "rtmps://"))
- return NULL;
-
- i = g_new(struct input_ffmpeg, 1);
- input_stream_init(&i->base, &input_plugin_ffmpeg, uri,
- mutex, cond);
-
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,1,0)
- int ret = avio_open(&i->h, uri, AVIO_FLAG_READ);
-#elif LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0)
- int ret = avio_open(&i->h, uri, AVIO_RDONLY);
-#else
- int ret = url_open(&i->h, uri, URL_RDONLY);
-#endif
- if (ret != 0) {
- g_free(i);
- g_set_error(error_r, ffmpeg_quark(), ret,
- "libavformat failed to open the URI");
- return NULL;
- }
-
- i->eof = false;
-
- i->base.ready = true;
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0)
- i->base.seekable = (i->h->seekable & AVIO_SEEKABLE_NORMAL) != 0;
- i->base.size = avio_size(i->h);
-#else
- i->base.seekable = !i->h->is_streamed;
- i->base.size = url_filesize(i->h);
-#endif
-
- /* hack to make MPD select the "ffmpeg" decoder plugin - since
- avio.h doesn't tell us the MIME type of the resource, we
- can't select a decoder plugin, but the "ffmpeg" plugin is
- quite good at auto-detection */
- i->base.mime = g_strdup("audio/x-mpd-ffmpeg");
-
- return &i->base;
-}
-
-static size_t
-input_ffmpeg_read(struct input_stream *is, void *ptr, size_t size,
- GError **error_r)
-{
- struct input_ffmpeg *i = (struct input_ffmpeg *)is;
-
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0)
- int ret = avio_read(i->h, ptr, size);
-#else
- int ret = url_read(i->h, ptr, size);
-#endif
- if (ret <= 0) {
- if (ret < 0)
- g_set_error(error_r, ffmpeg_quark(), 0,
- "url_read() failed");
-
- i->eof = true;
- return false;
- }
-
- is->offset += ret;
- return (size_t)ret;
-}
-
-static void
-input_ffmpeg_close(struct input_stream *is)
-{
- struct input_ffmpeg *i = (struct input_ffmpeg *)is;
-
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0)
- avio_close(i->h);
-#else
- url_close(i->h);
-#endif
- input_stream_deinit(&i->base);
- g_free(i);
-}
-
-static bool
-input_ffmpeg_eof(struct input_stream *is)
-{
- struct input_ffmpeg *i = (struct input_ffmpeg *)is;
-
- return i->eof;
-}
-
-static bool
-input_ffmpeg_seek(struct input_stream *is, goffset offset, int whence,
- G_GNUC_UNUSED GError **error_r)
-{
- struct input_ffmpeg *i = (struct input_ffmpeg *)is;
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0)
- int64_t ret = avio_seek(i->h, offset, whence);
-#else
- int64_t ret = url_seek(i->h, offset, whence);
-#endif
-
- if (ret >= 0) {
- i->eof = false;
- return true;
- } else {
- g_set_error(error_r, ffmpeg_quark(), 0, "url_seek() failed");
- return false;
- }
-}
-
-const struct input_plugin input_plugin_ffmpeg = {
- .name = "ffmpeg",
- .init = input_ffmpeg_init,
- .open = input_ffmpeg_open,
- .close = input_ffmpeg_close,
- .read = input_ffmpeg_read,
- .eof = input_ffmpeg_eof,
- .seek = input_ffmpeg_seek,
-};
diff --git a/src/input/ffmpeg_input_plugin.h b/src/input/ffmpeg_input_plugin.h
deleted file mode 100644
index 393836ca5..000000000
--- a/src/input/ffmpeg_input_plugin.h
+++ /dev/null
@@ -1,28 +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_FFMPEG_INPUT_PLUGIN_H
-#define MPD_FFMPEG_INPUT_PLUGIN_H
-
-/**
- * An input plugin based on libavformat's "avio" library.
- */
-extern const struct input_plugin input_plugin_ffmpeg;
-
-#endif
diff --git a/src/input/file_input_plugin.c b/src/input/file_input_plugin.c
deleted file mode 100644
index 5ee3f200b..000000000
--- a/src/input/file_input_plugin.c
+++ /dev/null
@@ -1,160 +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" /* must be first for large file support */
-#include "input/file_input_plugin.h"
-#include "input_internal.h"
-#include "input_plugin.h"
-#include "fd_util.h"
-#include "open.h"
-
-#include <sys/stat.h>
-#include <unistd.h>
-#include <errno.h>
-#include <string.h>
-#include <glib.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "input_file"
-
-struct file_input_stream {
- struct input_stream base;
-
- int fd;
-};
-
-static inline GQuark
-file_quark(void)
-{
- return g_quark_from_static_string("file");
-}
-
-static struct input_stream *
-input_file_open(const char *filename,
- GMutex *mutex, GCond *cond,
- GError **error_r)
-{
- int fd, ret;
- struct stat st;
- struct file_input_stream *fis;
-
- if (!g_path_is_absolute(filename))
- return NULL;
-
- fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0);
- if (fd < 0) {
- if (errno != ENOENT && errno != ENOTDIR)
- g_set_error(error_r, file_quark(), errno,
- "Failed to open \"%s\": %s",
- filename, g_strerror(errno));
- return NULL;
- }
-
- ret = fstat(fd, &st);
- if (ret < 0) {
- g_set_error(error_r, file_quark(), errno,
- "Failed to stat \"%s\": %s",
- filename, g_strerror(errno));
- close(fd);
- return NULL;
- }
-
- if (!S_ISREG(st.st_mode)) {
- g_set_error(error_r, file_quark(), 0,
- "Not a regular file: %s", filename);
- close(fd);
- return NULL;
- }
-
-#ifdef POSIX_FADV_SEQUENTIAL
- posix_fadvise(fd, (off_t)0, st.st_size, POSIX_FADV_SEQUENTIAL);
-#endif
-
- fis = g_new(struct file_input_stream, 1);
- input_stream_init(&fis->base, &input_plugin_file, filename,
- mutex, cond);
-
- fis->base.size = st.st_size;
- fis->base.seekable = true;
- fis->base.ready = true;
-
- fis->fd = fd;
-
- return &fis->base;
-}
-
-static bool
-input_file_seek(struct input_stream *is, goffset offset, int whence,
- GError **error_r)
-{
- struct file_input_stream *fis = (struct file_input_stream *)is;
-
- offset = (goffset)lseek(fis->fd, (off_t)offset, whence);
- if (offset < 0) {
- g_set_error(error_r, file_quark(), errno,
- "Failed to seek: %s", g_strerror(errno));
- return false;
- }
-
- is->offset = offset;
- return true;
-}
-
-static size_t
-input_file_read(struct input_stream *is, void *ptr, size_t size,
- GError **error_r)
-{
- struct file_input_stream *fis = (struct file_input_stream *)is;
- ssize_t nbytes;
-
- nbytes = read(fis->fd, ptr, size);
- if (nbytes < 0) {
- g_set_error(error_r, file_quark(), errno,
- "Failed to read: %s", g_strerror(errno));
- return 0;
- }
-
- is->offset += nbytes;
- return (size_t)nbytes;
-}
-
-static void
-input_file_close(struct input_stream *is)
-{
- struct file_input_stream *fis = (struct file_input_stream *)is;
-
- close(fis->fd);
- input_stream_deinit(&fis->base);
- g_free(fis);
-}
-
-static bool
-input_file_eof(struct input_stream *is)
-{
- return is->offset >= is->size;
-}
-
-const struct input_plugin input_plugin_file = {
- .name = "file",
- .open = input_file_open,
- .close = input_file_close,
- .read = input_file_read,
- .eof = input_file_eof,
- .seek = input_file_seek,
-};
diff --git a/src/input/file_input_plugin.h b/src/input/file_input_plugin.h
deleted file mode 100644
index f24769d57..000000000
--- a/src/input/file_input_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_INPUT_FILE_H
-#define MPD_INPUT_FILE_H
-
-extern const struct input_plugin input_plugin_file;
-
-#endif
diff --git a/src/input/mms_input_plugin.c b/src/input/mms_input_plugin.c
deleted file mode 100644
index cff15125b..000000000
--- a/src/input/mms_input_plugin.c
+++ /dev/null
@@ -1,140 +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 "input/mms_input_plugin.h"
-#include "input_internal.h"
-#include "input_plugin.h"
-
-#include <glib.h>
-#include <libmms/mmsx.h>
-
-#include <string.h>
-#include <errno.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "input_mms"
-
-struct input_mms {
- struct input_stream base;
-
- mmsx_t *mms;
-
- bool eof;
-};
-
-static inline GQuark
-mms_quark(void)
-{
- return g_quark_from_static_string("mms");
-}
-
-static struct input_stream *
-input_mms_open(const char *url,
- GMutex *mutex, GCond *cond,
- GError **error_r)
-{
- struct input_mms *m;
-
- if (!g_str_has_prefix(url, "mms://") &&
- !g_str_has_prefix(url, "mmsh://") &&
- !g_str_has_prefix(url, "mmst://") &&
- !g_str_has_prefix(url, "mmsu://"))
- return NULL;
-
- m = g_new(struct input_mms, 1);
- input_stream_init(&m->base, &input_plugin_mms, url,
- mutex, cond);
-
- m->mms = mmsx_connect(NULL, NULL, url, 128 * 1024);
- if (m->mms == NULL) {
- g_free(m);
- g_set_error(error_r, mms_quark(), 0, "mmsx_connect() failed");
- return NULL;
- }
-
- m->eof = false;
-
- /* XX is this correct? at least this selects the ffmpeg
- decoder, which seems to work fine*/
- m->base.mime = g_strdup("audio/x-ms-wma");
-
- m->base.ready = true;
-
- return &m->base;
-}
-
-static size_t
-input_mms_read(struct input_stream *is, void *ptr, size_t size,
- GError **error_r)
-{
- struct input_mms *m = (struct input_mms *)is;
- int ret;
-
- ret = mmsx_read(NULL, m->mms, ptr, size);
- if (ret <= 0) {
- if (ret < 0) {
- g_set_error(error_r, mms_quark(), errno,
- "mmsx_read() failed: %s",
- g_strerror(errno));
- }
-
- m->eof = true;
- return false;
- }
-
- is->offset += ret;
-
- return (size_t)ret;
-}
-
-static void
-input_mms_close(struct input_stream *is)
-{
- struct input_mms *m = (struct input_mms *)is;
-
- mmsx_close(m->mms);
- input_stream_deinit(&m->base);
- g_free(m);
-}
-
-static bool
-input_mms_eof(struct input_stream *is)
-{
- struct input_mms *m = (struct input_mms *)is;
-
- return m->eof;
-}
-
-static bool
-input_mms_seek(G_GNUC_UNUSED struct input_stream *is,
- G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence,
- G_GNUC_UNUSED GError **error_r)
-{
- return false;
-}
-
-const struct input_plugin input_plugin_mms = {
- .name = "mms",
- .open = input_mms_open,
- .close = input_mms_close,
- .read = input_mms_read,
- .eof = input_mms_eof,
- .seek = input_mms_seek,
-};
diff --git a/src/input/rewind_input_plugin.c b/src/input/rewind_input_plugin.c
deleted file mode 100644
index cf06fc57b..000000000
--- a/src/input/rewind_input_plugin.c
+++ /dev/null
@@ -1,256 +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 "input/rewind_input_plugin.h"
-#include "input_internal.h"
-#include "input_plugin.h"
-#include "tag.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <stdio.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "input_rewind"
-
-struct input_rewind {
- struct input_stream base;
-
- struct input_stream *input;
-
- /**
- * The read position within the buffer. Undefined as long as
- * reading_from_buffer() returns false.
- */
- size_t head;
-
- /**
- * The write/append position within the buffer.
- */
- size_t tail;
-
- /**
- * The size of this buffer is the maximum number of bytes
- * which can be rewinded cheaply without passing the "seek"
- * call to CURL.
- *
- * The origin of this buffer is always the beginning of the
- * stream (offset 0).
- */
- char buffer[64 * 1024];
-};
-
-/**
- * Are we currently reading from the buffer, and does the buffer
- * contain more data for the next read operation?
- */
-static bool
-reading_from_buffer(const struct input_rewind *r)
-{
- return r->tail > 0 && r->base.offset < r->input->offset;
-}
-
-/**
- * Copy public attributes from the underlying input stream to the
- * "rewind" input stream. This function is called when a method of
- * the underlying stream has returned, which may have modified these
- * attributes.
- */
-static void
-copy_attributes(struct input_rewind *r)
-{
- struct input_stream *dest = &r->base;
- const struct input_stream *src = r->input;
-
- assert(dest != src);
- assert(src->mime == NULL || dest->mime != src->mime);
-
- bool dest_ready = dest->ready;
-
- dest->ready = src->ready;
- dest->seekable = src->seekable;
- dest->size = src->size;
- dest->offset = src->offset;
-
- if (!dest_ready && src->ready) {
- g_free(dest->mime);
- dest->mime = g_strdup(src->mime);
- }
-}
-
-static void
-input_rewind_close(struct input_stream *is)
-{
- struct input_rewind *r = (struct input_rewind *)is;
-
- input_stream_close(r->input);
-
- input_stream_deinit(&r->base);
- g_free(r);
-}
-
-static bool
-input_rewind_check(struct input_stream *is, GError **error_r)
-{
- struct input_rewind *r = (struct input_rewind *)is;
-
- return input_stream_check(r->input, error_r);
-}
-
-static void
-input_rewind_update(struct input_stream *is)
-{
- struct input_rewind *r = (struct input_rewind *)is;
-
- if (!reading_from_buffer(r))
- copy_attributes(r);
-}
-
-static struct tag *
-input_rewind_tag(struct input_stream *is)
-{
- struct input_rewind *r = (struct input_rewind *)is;
-
- return input_stream_tag(r->input);
-}
-
-static bool
-input_rewind_available(struct input_stream *is)
-{
- struct input_rewind *r = (struct input_rewind *)is;
-
- return input_stream_available(r->input);
-}
-
-static size_t
-input_rewind_read(struct input_stream *is, void *ptr, size_t size,
- GError **error_r)
-{
- struct input_rewind *r = (struct input_rewind *)is;
-
- if (reading_from_buffer(r)) {
- /* buffered read */
-
- assert(r->head == (size_t)is->offset);
- assert(r->tail == (size_t)r->input->offset);
-
- if (size > r->tail - r->head)
- size = r->tail - r->head;
-
- memcpy(ptr, r->buffer + r->head, size);
- r->head += size;
- is->offset += size;
-
- return size;
- } else {
- /* pass method call to underlying stream */
-
- size_t nbytes = input_stream_read(r->input, ptr, size, error_r);
-
- if (r->input->offset > (goffset)sizeof(r->buffer))
- /* disable buffering */
- r->tail = 0;
- else if (r->tail == (size_t)is->offset) {
- /* append to buffer */
-
- memcpy(r->buffer + r->tail, ptr, nbytes);
- r->tail += nbytes;
-
- assert(r->tail == (size_t)r->input->offset);
- }
-
- copy_attributes(r);
-
- return nbytes;
- }
-}
-
-static bool
-input_rewind_eof(struct input_stream *is)
-{
- struct input_rewind *r = (struct input_rewind *)is;
-
- return !reading_from_buffer(r) && input_stream_eof(r->input);
-}
-
-static bool
-input_rewind_seek(struct input_stream *is, goffset offset, int whence,
- GError **error_r)
-{
- struct input_rewind *r = (struct input_rewind *)is;
-
- assert(is->ready);
-
- if (whence == SEEK_SET && r->tail > 0 && offset <= (goffset)r->tail) {
- /* buffered seek */
-
- assert(!reading_from_buffer(r) ||
- r->head == (size_t)is->offset);
- assert(r->tail == (size_t)r->input->offset);
-
- r->head = (size_t)offset;
- is->offset = offset;
-
- return true;
- } else {
- bool success = input_stream_seek(r->input, offset, whence,
- error_r);
- copy_attributes(r);
-
- /* disable the buffer, because r->input has left the
- buffered range now */
- r->tail = 0;
-
- return success;
- }
-}
-
-static const struct input_plugin rewind_input_plugin = {
- .close = input_rewind_close,
- .check = input_rewind_check,
- .update = input_rewind_update,
- .tag = input_rewind_tag,
- .available = input_rewind_available,
- .read = input_rewind_read,
- .eof = input_rewind_eof,
- .seek = input_rewind_seek,
-};
-
-struct input_stream *
-input_rewind_open(struct input_stream *is)
-{
- struct input_rewind *c;
-
- assert(is != NULL);
- assert(is->offset == 0);
-
- if (is->seekable)
- /* seekable resources don't need this plugin */
- return is;
-
- c = g_new(struct input_rewind, 1);
- input_stream_init(&c->base, &rewind_input_plugin, is->uri,
- is->mutex, is->cond);
- c->tail = 0;
- c->input = is;
-
- return &c->base;
-}
diff --git a/src/input/rewind_input_plugin.h b/src/input/rewind_input_plugin.h
deleted file mode 100644
index 83abe257a..000000000
--- a/src/input/rewind_input_plugin.h
+++ /dev/null
@@ -1,37 +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
- *
- * A wrapper for an input_stream object which allows cheap buffered
- * rewinding. This is useful while detecting the stream codec (let
- * each decoder plugin peek a portion from the stream).
- */
-
-#ifndef MPD_INPUT_REWIND_H
-#define MPD_INPUT_REWIND_H
-
-#include "check.h"
-
-struct input_stream;
-
-struct input_stream *
-input_rewind_open(struct input_stream *is);
-
-#endif
diff --git a/src/input/soup_input_plugin.c b/src/input/soup_input_plugin.c
deleted file mode 100644
index fc903b48c..000000000
--- a/src/input/soup_input_plugin.c
+++ /dev/null
@@ -1,473 +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 "input/soup_input_plugin.h"
-#include "input_internal.h"
-#include "input_plugin.h"
-#include "io_thread.h"
-#include "conf.h"
-
-#include <libsoup/soup-uri.h>
-#include <libsoup/soup-session-async.h>
-
-#include <assert.h>
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "input_soup"
-
-/**
- * Do not buffer more than this number of bytes. It should be a
- * reasonable limit that doesn't make low-end machines suffer too
- * much, but doesn't cause stuttering on high-latency lines.
- */
-static const size_t SOUP_MAX_BUFFERED = 512 * 1024;
-
-/**
- * Resume the stream at this number of bytes after it has been paused.
- */
-static const size_t SOUP_RESUME_AT = 384 * 1024;
-
-static SoupURI *soup_proxy;
-static SoupSession *soup_session;
-
-struct input_soup {
- struct input_stream base;
-
- SoupMessage *msg;
-
- GQueue *buffers;
-
- size_t current_consumed;
-
- size_t total_buffered;
-
- bool alive, pause, eof;
-
- /**
- * Set when the session callback has been invoked, when it is
- * safe to free this object.
- */
- bool completed;
-
- GError *postponed_error;
-};
-
-static inline GQuark
-soup_quark(void)
-{
- return g_quark_from_static_string("soup");
-}
-
-static bool
-input_soup_init(const struct config_param *param, GError **error_r)
-{
- assert(soup_proxy == NULL);
- assert(soup_session == NULL);
-
- g_type_init();
-
- const char *proxy = config_get_block_string(param, "proxy", NULL);
-
- if (proxy != NULL) {
- soup_proxy = soup_uri_new(proxy);
- if (soup_proxy == NULL) {
- g_set_error(error_r, soup_quark(), 0,
- "failed to parse proxy setting");
- return false;
- }
- }
-
- soup_session =
- soup_session_async_new_with_options(SOUP_SESSION_PROXY_URI,
- soup_proxy,
- SOUP_SESSION_ASYNC_CONTEXT,
- io_thread_context(),
- NULL);
-
- return true;
-}
-
-static void
-input_soup_finish(void)
-{
- assert(soup_session != NULL);
-
- soup_session_abort(soup_session);
- g_object_unref(G_OBJECT(soup_session));
-
- if (soup_proxy != NULL)
- soup_uri_free(soup_proxy);
-}
-
-/**
- * Copy the error from the SoupMessage object to
- * input_soup::postponed_error.
- *
- * @return true if there was no error
- */
-static bool
-input_soup_copy_error(struct input_soup *s, const SoupMessage *msg)
-{
- if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code))
- return true;
-
- if (msg->status_code == SOUP_STATUS_CANCELLED)
- /* failure, but don't generate a GError, because this
- status was caused by _close() */
- return false;
-
- if (s->postponed_error != NULL)
- /* there's already a GError, don't overwrite it */
- return false;
-
- if (SOUP_STATUS_IS_TRANSPORT_ERROR(msg->status_code))
- s->postponed_error =
- g_error_new(soup_quark(), msg->status_code,
- "HTTP client error: %s",
- msg->reason_phrase);
- else
- s->postponed_error =
- g_error_new(soup_quark(), msg->status_code,
- "got HTTP status: %d %s",
- msg->status_code, msg->reason_phrase);
-
- return false;
-}
-
-static void
-input_soup_session_callback(G_GNUC_UNUSED SoupSession *session,
- SoupMessage *msg, gpointer user_data)
-{
- struct input_soup *s = user_data;
-
- assert(msg == s->msg);
- assert(!s->completed);
-
- g_mutex_lock(s->base.mutex);
-
- if (!s->base.ready)
- input_soup_copy_error(s, msg);
-
- s->base.ready = true;
- s->alive = false;
- s->completed = true;
-
- g_cond_broadcast(s->base.cond);
- g_mutex_unlock(s->base.mutex);
-}
-
-static void
-input_soup_got_headers(SoupMessage *msg, gpointer user_data)
-{
- struct input_soup *s = user_data;
-
- g_mutex_lock(s->base.mutex);
-
- if (!input_soup_copy_error(s, msg)) {
- g_mutex_unlock(s->base.mutex);
-
- soup_session_cancel_message(soup_session, msg,
- SOUP_STATUS_CANCELLED);
- return;
- }
-
- s->base.ready = true;
- g_cond_broadcast(s->base.cond);
- g_mutex_unlock(s->base.mutex);
-
- soup_message_body_set_accumulate(msg->response_body, false);
-}
-
-static void
-input_soup_got_chunk(SoupMessage *msg, SoupBuffer *chunk, gpointer user_data)
-{
- struct input_soup *s = user_data;
-
- assert(msg == s->msg);
-
- g_mutex_lock(s->base.mutex);
-
- g_queue_push_tail(s->buffers, soup_buffer_copy(chunk));
- s->total_buffered += chunk->length;
-
- if (s->total_buffered >= SOUP_MAX_BUFFERED && !s->pause) {
- s->pause = true;
- soup_session_pause_message(soup_session, msg);
- }
-
- g_cond_broadcast(s->base.cond);
- g_mutex_unlock(s->base.mutex);
-}
-
-static void
-input_soup_got_body(G_GNUC_UNUSED SoupMessage *msg, gpointer user_data)
-{
- struct input_soup *s = user_data;
-
- assert(msg == s->msg);
-
- g_mutex_lock(s->base.mutex);
-
- s->base.ready = true;
- s->eof = true;
- s->alive = false;
-
- g_cond_broadcast(s->base.cond);
- g_mutex_unlock(s->base.mutex);
-}
-
-static bool
-input_soup_wait_data(struct input_soup *s)
-{
- while (true) {
- if (s->eof)
- return true;
-
- if (!s->alive)
- return false;
-
- if (!g_queue_is_empty(s->buffers))
- return true;
-
- assert(s->current_consumed == 0);
-
- g_cond_wait(s->base.cond, s->base.mutex);
- }
-}
-
-static gpointer
-input_soup_queue(gpointer data)
-{
- struct input_soup *s = data;
-
- soup_session_queue_message(soup_session, s->msg,
- input_soup_session_callback, s);
-
- return NULL;
-}
-
-static struct input_stream *
-input_soup_open(const char *uri,
- GMutex *mutex, GCond *cond,
- G_GNUC_UNUSED GError **error_r)
-{
- if (strncmp(uri, "http://", 7) != 0)
- return NULL;
-
- struct input_soup *s = g_new(struct input_soup, 1);
- input_stream_init(&s->base, &input_plugin_soup, uri,
- mutex, cond);
-
- s->buffers = g_queue_new();
- s->current_consumed = 0;
- s->total_buffered = 0;
-
-#if GCC_CHECK_VERSION(4,6)
-#pragma GCC diagnostic push
- /* the libsoup macro SOUP_METHOD_GET discards the "const"
- attribute of the g_intern_static_string() return value;
- don't make the gcc warning fatal: */
-#pragma GCC diagnostic ignored "-Wcast-qual"
-#endif
-
- s->msg = soup_message_new(SOUP_METHOD_GET, uri);
-
-#if GCC_CHECK_VERSION(4,6)
-#pragma GCC diagnostic pop
-#endif
-
- soup_message_set_flags(s->msg, SOUP_MESSAGE_NO_REDIRECT);
-
- soup_message_headers_append(s->msg->request_headers, "User-Agent",
- "Music Player Daemon " VERSION);
-
- g_signal_connect(s->msg, "got-headers",
- G_CALLBACK(input_soup_got_headers), s);
- g_signal_connect(s->msg, "got-chunk",
- G_CALLBACK(input_soup_got_chunk), s);
- g_signal_connect(s->msg, "got-body",
- G_CALLBACK(input_soup_got_body), s);
-
- s->alive = true;
- s->pause = false;
- s->eof = false;
- s->completed = false;
- s->postponed_error = NULL;
-
- io_thread_call(input_soup_queue, s);
-
- return &s->base;
-}
-
-static gpointer
-input_soup_cancel(gpointer data)
-{
- struct input_soup *s = data;
-
- if (!s->completed)
- soup_session_cancel_message(soup_session, s->msg,
- SOUP_STATUS_CANCELLED);
-
- return NULL;
-}
-
-static void
-input_soup_close(struct input_stream *is)
-{
- struct input_soup *s = (struct input_soup *)is;
-
- g_mutex_lock(s->base.mutex);
-
- if (!s->completed) {
- /* the messages's session callback hasn't been invoked
- yet; cancel it and wait for completion */
-
- g_mutex_unlock(s->base.mutex);
-
- io_thread_call(input_soup_cancel, s);
-
- g_mutex_lock(s->base.mutex);
- while (!s->completed)
- g_cond_wait(s->base.cond, s->base.mutex);
- }
-
- g_mutex_unlock(s->base.mutex);
-
- SoupBuffer *buffer;
- while ((buffer = g_queue_pop_head(s->buffers)) != NULL)
- soup_buffer_free(buffer);
- g_queue_free(s->buffers);
-
- if (s->postponed_error != NULL)
- g_error_free(s->postponed_error);
-
- input_stream_deinit(&s->base);
- g_free(s);
-}
-
-static bool
-input_soup_check(struct input_stream *is, GError **error_r)
-{
- struct input_soup *s = (struct input_soup *)is;
-
- bool success = s->postponed_error == NULL;
- if (!success) {
- g_propagate_error(error_r, s->postponed_error);
- s->postponed_error = NULL;
- }
-
- return success;
-}
-
-static bool
-input_soup_available(struct input_stream *is)
-{
- struct input_soup *s = (struct input_soup *)is;
-
- return s->eof || !s->alive || !g_queue_is_empty(s->buffers);
-}
-
-static size_t
-input_soup_read(struct input_stream *is, void *ptr, size_t size,
- G_GNUC_UNUSED GError **error_r)
-{
- struct input_soup *s = (struct input_soup *)is;
-
- if (!input_soup_wait_data(s)) {
- assert(!s->alive);
-
- if (s->postponed_error != NULL) {
- g_propagate_error(error_r, s->postponed_error);
- s->postponed_error = NULL;
- } else
- g_set_error_literal(error_r, soup_quark(), 0,
- "HTTP failure");
- return 0;
- }
-
- char *p0 = ptr, *p = p0, *p_end = p0 + size;
-
- while (p < p_end) {
- SoupBuffer *buffer = g_queue_pop_head(s->buffers);
- if (buffer == NULL) {
- assert(s->current_consumed == 0);
- break;
- }
-
- assert(s->current_consumed < buffer->length);
- assert(s->total_buffered >= buffer->length);
-
- const char *q = buffer->data;
- q += s->current_consumed;
-
- size_t remaining = buffer->length - s->current_consumed;
- size_t nbytes = p_end - p;
- if (nbytes > remaining)
- nbytes = remaining;
-
- memcpy(p, q, nbytes);
- p += nbytes;
-
- s->current_consumed += remaining;
- if (s->current_consumed >= buffer->length) {
- /* done with this buffer */
- s->total_buffered -= buffer->length;
- soup_buffer_free(buffer);
- s->current_consumed = 0;
- } else {
- /* partial read */
- assert(p == p_end);
-
- g_queue_push_head(s->buffers, buffer);
- }
- }
-
- if (s->pause && s->total_buffered < SOUP_RESUME_AT) {
- s->pause = false;
- soup_session_unpause_message(soup_session, s->msg);
- }
-
- size_t nbytes = p - p0;
- s->base.offset += nbytes;
-
- return nbytes;
-}
-
-static bool
-input_soup_eof(G_GNUC_UNUSED struct input_stream *is)
-{
- struct input_soup *s = (struct input_soup *)is;
-
- return !s->alive && g_queue_is_empty(s->buffers);
-}
-
-const struct input_plugin input_plugin_soup = {
- .name = "soup",
- .init = input_soup_init,
- .finish = input_soup_finish,
-
- .open = input_soup_open,
- .close = input_soup_close,
- .check = input_soup_check,
- .available = input_soup_available,
- .read = input_soup_read,
- .eof = input_soup_eof,
-};
diff --git a/src/input/soup_input_plugin.h b/src/input/soup_input_plugin.h
deleted file mode 100644
index 689b2d971..000000000
--- a/src/input/soup_input_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_INPUT_SOUP_H
-#define MPD_INPUT_SOUP_H
-
-extern const struct input_plugin input_plugin_soup;
-
-#endif
diff --git a/src/input_init.c b/src/input_init.c
deleted file mode 100644
index 771d648d1..000000000
--- a/src/input_init.c
+++ /dev/null
@@ -1,104 +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 "input_init.h"
-#include "input_plugin.h"
-#include "input_registry.h"
-#include "conf.h"
-
-#include <assert.h>
-#include <string.h>
-
-static inline GQuark
-input_quark(void)
-{
- return g_quark_from_static_string("input");
-}
-
-/**
- * Find the "input" configuration block for the specified plugin.
- *
- * @param plugin_name the name of the input plugin
- * @return the configuration block, or NULL if none was configured
- */
-static const struct config_param *
-input_plugin_config(const char *plugin_name, GError **error_r)
-{
- const struct config_param *param = NULL;
-
- while ((param = config_get_next_param(CONF_INPUT, param)) != NULL) {
- const char *name =
- config_get_block_string(param, "plugin", NULL);
- if (name == NULL) {
- g_set_error(error_r, input_quark(), 0,
- "input configuration without 'plugin' name in line %d",
- param->line);
- return NULL;
- }
-
- if (strcmp(name, plugin_name) == 0)
- return param;
- }
-
- return NULL;
-}
-
-bool
-input_stream_global_init(GError **error_r)
-{
- GError *error = NULL;
-
- for (unsigned i = 0; input_plugins[i] != NULL; ++i) {
- const struct input_plugin *plugin = input_plugins[i];
-
- assert(plugin->name != NULL);
- assert(*plugin->name != 0);
- assert(plugin->open != NULL);
-
- const struct config_param *param =
- input_plugin_config(plugin->name, &error);
- if (param == NULL && error != NULL) {
- g_propagate_error(error_r, error);
- return false;
- }
-
- if (!config_get_block_bool(param, "enabled", true))
- /* the plugin is disabled in mpd.conf */
- continue;
-
- if (plugin->init == NULL || plugin->init(param, &error))
- input_plugins_enabled[i] = true;
- else {
- g_propagate_prefixed_error(error_r, error,
- "Failed to initialize input plugin '%s': ",
- plugin->name);
- return false;
- }
- }
-
- return true;
-}
-
-void input_stream_global_finish(void)
-{
- input_plugins_for_each_enabled(plugin)
- if (plugin->finish != NULL)
- plugin->finish();
-}
diff --git a/src/input_init.h b/src/input_init.h
deleted file mode 100644
index ad92cda08..000000000
--- a/src/input_init.h
+++ /dev/null
@@ -1,42 +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_INPUT_INIT_H
-#define MPD_INPUT_INIT_H
-
-#include "check.h"
-
-#include <glib.h>
-#include <stdbool.h>
-
-/**
- * Initializes this library and all input_stream implementations.
- *
- * @param error_r location to store the error occurring, or NULL to
- * ignore errors
- */
-bool
-input_stream_global_init(GError **error_r);
-
-/**
- * Deinitializes this library and all input_stream implementations.
- */
-void input_stream_global_finish(void);
-
-#endif
diff --git a/src/input_internal.c b/src/input_internal.c
deleted file mode 100644
index 92a71856e..000000000
--- a/src/input_internal.c
+++ /dev/null
@@ -1,73 +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 "input_internal.h"
-#include "input_stream.h"
-
-#include <assert.h>
-
-void
-input_stream_init(struct input_stream *is, const struct input_plugin *plugin,
- const char *uri, GMutex *mutex, GCond *cond)
-{
- assert(is != NULL);
- assert(plugin != NULL);
- assert(uri != NULL);
-
- is->plugin = plugin;
- is->uri = g_strdup(uri);
- is->mutex = mutex;
- is->cond = cond;
- is->ready = false;
- is->seekable = false;
- is->size = -1;
- is->offset = 0;
- is->mime = NULL;
-}
-
-void
-input_stream_deinit(struct input_stream *is)
-{
- assert(is != NULL);
- assert(is->plugin != NULL);
-
- g_free(is->uri);
- g_free(is->mime);
-}
-
-void
-input_stream_signal_client(struct input_stream *is)
-{
- if (is->cond != NULL)
- g_cond_broadcast(is->cond);
-}
-
-void
-input_stream_set_ready(struct input_stream *is)
-{
- g_mutex_lock(is->mutex);
-
- if (!is->ready) {
- is->ready = true;
- input_stream_signal_client(is);
- }
-
- g_mutex_unlock(is->mutex);
-}
diff --git a/src/input_internal.h b/src/input_internal.h
deleted file mode 100644
index d95142e46..000000000
--- a/src/input_internal.h
+++ /dev/null
@@ -1,43 +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_INPUT_INTERNAL_H
-#define MPD_INPUT_INTERNAL_H
-
-#include "check.h"
-
-#include <glib.h>
-
-struct input_stream;
-struct input_plugin;
-
-void
-input_stream_init(struct input_stream *is, const struct input_plugin *plugin,
- const char *uri, GMutex *mutex, GCond *cond);
-
-void
-input_stream_deinit(struct input_stream *is);
-
-void
-input_stream_signal_client(struct input_stream *is);
-
-void
-input_stream_set_ready(struct input_stream *is);
-
-#endif
diff --git a/src/input_plugin.h b/src/input_plugin.h
deleted file mode 100644
index 6b0c77c85..000000000
--- a/src/input_plugin.h
+++ /dev/null
@@ -1,89 +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_INPUT_PLUGIN_H
-#define MPD_INPUT_PLUGIN_H
-
-#include "input_stream.h"
-
-#include <stddef.h>
-#include <stdbool.h>
-#include <sys/types.h>
-
-struct config_param;
-struct input_stream;
-
-struct input_plugin {
- const char *name;
-
- /**
- * Global initialization. This method is called when MPD starts.
- *
- * @param error_r location to store the error occurring, or
- * NULL to ignore errors
- * @return true on success, false if the plugin should be
- * disabled
- */
- bool (*init)(const struct config_param *param, GError **error_r);
-
- /**
- * Global deinitialization. Called once before MPD shuts
- * down (only if init() has returned true).
- */
- void (*finish)(void);
-
- struct input_stream *(*open)(const char *uri,
- GMutex *mutex, GCond *cond,
- GError **error_r);
- void (*close)(struct input_stream *is);
-
- /**
- * Check for errors that may have occurred in the I/O thread.
- * May be unimplemented for synchronous plugins.
- *
- * @return false on error
- */
- bool (*check)(struct input_stream *is, GError **error_r);
-
- /**
- * Update the public attributes. Call before access. Can be
- * NULL if the plugin always keeps its attributes up to date.
- */
- void (*update)(struct input_stream *is);
-
- struct tag *(*tag)(struct input_stream *is);
-
- /**
- * Returns true if the next read operation will not block:
- * either data is available, or end-of-stream has been
- * reached, or an error has occurred.
- *
- * If this method is unimplemented, then it is assumed that
- * reading will never block.
- */
- bool (*available)(struct input_stream *is);
-
- size_t (*read)(struct input_stream *is, void *ptr, size_t size,
- GError **error_r);
- bool (*eof)(struct input_stream *is);
- bool (*seek)(struct input_stream *is, goffset offset, int whence,
- GError **error_r);
-};
-
-#endif
diff --git a/src/input_registry.c b/src/input_registry.c
deleted file mode 100644
index 5987d5da2..000000000
--- a/src/input_registry.c
+++ /dev/null
@@ -1,80 +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 "input_registry.h"
-#include "input/file_input_plugin.h"
-
-#ifdef ENABLE_ARCHIVE
-#include "input/archive_input_plugin.h"
-#endif
-
-#ifdef ENABLE_CURL
-#include "input/curl_input_plugin.h"
-#endif
-
-#ifdef ENABLE_SOUP
-#include "input/soup_input_plugin.h"
-#endif
-
-#ifdef HAVE_FFMPEG
-#include "input/ffmpeg_input_plugin.h"
-#endif
-
-#ifdef ENABLE_MMS
-#include "input/mms_input_plugin.h"
-#endif
-
-#ifdef ENABLE_CDIO_PARANOIA
-#include "input/cdio_paranoia_input_plugin.h"
-#endif
-
-#ifdef ENABLE_DESPOTIFY
-#include "input/despotify_input_plugin.h"
-#endif
-
-#include <glib.h>
-
-const struct input_plugin *const input_plugins[] = {
- &input_plugin_file,
-#ifdef ENABLE_ARCHIVE
- &input_plugin_archive,
-#endif
-#ifdef ENABLE_CURL
- &input_plugin_curl,
-#endif
-#ifdef ENABLE_SOUP
- &input_plugin_soup,
-#endif
-#ifdef HAVE_FFMPEG
- &input_plugin_ffmpeg,
-#endif
-#ifdef ENABLE_MMS
- &input_plugin_mms,
-#endif
-#ifdef ENABLE_CDIO_PARANOIA
- &input_plugin_cdio_paranoia,
-#endif
-#ifdef ENABLE_DESPOTIFY
- &input_plugin_despotify,
-#endif
- NULL
-};
-
-bool input_plugins_enabled[G_N_ELEMENTS(input_plugins) - 1];
diff --git a/src/input_registry.h b/src/input_registry.h
deleted file mode 100644
index 4f5fff8da..000000000
--- a/src/input_registry.h
+++ /dev/null
@@ -1,45 +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_INPUT_REGISTRY_H
-#define MPD_INPUT_REGISTRY_H
-
-#include "check.h"
-
-#include <stdbool.h>
-
-/**
- * NULL terminated list of all input plugins which were enabled at
- * compile time.
- */
-extern const struct input_plugin *const input_plugins[];
-
-extern bool input_plugins_enabled[];
-
-#define input_plugins_for_each(plugin) \
- for (const struct input_plugin *plugin, \
- *const*input_plugin_iterator = &input_plugins[0]; \
- (plugin = *input_plugin_iterator) != NULL; \
- ++input_plugin_iterator)
-
-#define input_plugins_for_each_enabled(plugin) \
- input_plugins_for_each(plugin) \
- if (input_plugins_enabled[input_plugin_iterator - input_plugins])
-
-#endif
diff --git a/src/input_stream.c b/src/input_stream.c
deleted file mode 100644
index e445dca6c..000000000
--- a/src/input_stream.c
+++ /dev/null
@@ -1,243 +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 "input_stream.h"
-#include "input_registry.h"
-#include "input_plugin.h"
-#include "input/rewind_input_plugin.h"
-
-#include <glib.h>
-#include <assert.h>
-
-static inline GQuark
-input_quark(void)
-{
- return g_quark_from_static_string("input");
-}
-
-struct input_stream *
-input_stream_open(const char *url,
- GMutex *mutex, GCond *cond,
- GError **error_r)
-{
- GError *error = NULL;
-
- assert(mutex != NULL);
- assert(error_r == NULL || *error_r == NULL);
-
- input_plugins_for_each_enabled(plugin) {
- struct input_stream *is;
-
- is = plugin->open(url, mutex, cond, &error);
- if (is != NULL) {
- assert(is->plugin != NULL);
- assert(is->plugin->close != NULL);
- assert(is->plugin->read != NULL);
- assert(is->plugin->eof != NULL);
- assert(!is->seekable || is->plugin->seek != NULL);
-
- is = input_rewind_open(is);
-
- return is;
- } else if (error != NULL) {
- g_propagate_error(error_r, error);
- return NULL;
- }
- }
-
- g_set_error(error_r, input_quark(), 0, "Unrecognized URI");
- return NULL;
-}
-
-bool
-input_stream_check(struct input_stream *is, GError **error_r)
-{
- assert(is != NULL);
- assert(is->plugin != NULL);
-
- return is->plugin->check == NULL ||
- is->plugin->check(is, error_r);
-}
-
-void
-input_stream_update(struct input_stream *is)
-{
- assert(is != NULL);
- assert(is->plugin != NULL);
-
- if (is->plugin->update != NULL)
- is->plugin->update(is);
-}
-
-void
-input_stream_wait_ready(struct input_stream *is)
-{
- assert(is != NULL);
- assert(is->mutex != NULL);
- assert(is->cond != NULL);
-
- while (true) {
- input_stream_update(is);
- if (is->ready)
- break;
-
- g_cond_wait(is->cond, is->mutex);
- }
-}
-
-void
-input_stream_lock_wait_ready(struct input_stream *is)
-{
- assert(is != NULL);
- assert(is->mutex != NULL);
- assert(is->cond != NULL);
-
- g_mutex_lock(is->mutex);
- input_stream_wait_ready(is);
- g_mutex_unlock(is->mutex);
-}
-
-bool
-input_stream_seek(struct input_stream *is, goffset offset, int whence,
- GError **error_r)
-{
- assert(is != NULL);
- assert(is->plugin != NULL);
-
- if (is->plugin->seek == NULL)
- return false;
-
- return is->plugin->seek(is, offset, whence, error_r);
-}
-
-bool
-input_stream_lock_seek(struct input_stream *is, goffset offset, int whence,
- GError **error_r)
-{
- assert(is != NULL);
- assert(is->plugin != NULL);
-
- if (is->plugin->seek == NULL)
- return false;
-
- if (is->mutex == NULL)
- /* no locking */
- return input_stream_seek(is, offset, whence, error_r);
-
- g_mutex_lock(is->mutex);
- bool success = input_stream_seek(is, offset, whence, error_r);
- g_mutex_unlock(is->mutex);
- return success;
-}
-
-struct tag *
-input_stream_tag(struct input_stream *is)
-{
- assert(is != NULL);
- assert(is->plugin != NULL);
-
- return is->plugin->tag != NULL
- ? is->plugin->tag(is)
- : NULL;
-}
-
-struct tag *
-input_stream_lock_tag(struct input_stream *is)
-{
- assert(is != NULL);
- assert(is->plugin != NULL);
-
- if (is->plugin->tag == NULL)
- return false;
-
- if (is->mutex == NULL)
- /* no locking */
- return input_stream_tag(is);
-
- g_mutex_lock(is->mutex);
- struct tag *tag = input_stream_tag(is);
- g_mutex_unlock(is->mutex);
- return tag;
-}
-
-bool
-input_stream_available(struct input_stream *is)
-{
- assert(is != NULL);
- assert(is->plugin != NULL);
-
- return is->plugin->available != NULL
- ? is->plugin->available(is)
- : true;
-}
-
-size_t
-input_stream_read(struct input_stream *is, void *ptr, size_t size,
- GError **error_r)
-{
- assert(ptr != NULL);
- assert(size > 0);
-
- return is->plugin->read(is, ptr, size, error_r);
-}
-
-size_t
-input_stream_lock_read(struct input_stream *is, void *ptr, size_t size,
- GError **error_r)
-{
- assert(ptr != NULL);
- assert(size > 0);
-
- if (is->mutex == NULL)
- /* no locking */
- return input_stream_read(is, ptr, size, error_r);
-
- g_mutex_lock(is->mutex);
- size_t nbytes = input_stream_read(is, ptr, size, error_r);
- g_mutex_unlock(is->mutex);
- return nbytes;
-}
-
-void input_stream_close(struct input_stream *is)
-{
- is->plugin->close(is);
-}
-
-bool input_stream_eof(struct input_stream *is)
-{
- return is->plugin->eof(is);
-}
-
-bool
-input_stream_lock_eof(struct input_stream *is)
-{
- assert(is != NULL);
- assert(is->plugin != NULL);
-
- if (is->mutex == NULL)
- /* no locking */
- return input_stream_eof(is);
-
- g_mutex_lock(is->mutex);
- bool eof = input_stream_eof(is);
- g_mutex_unlock(is->mutex);
- return eof;
-}
-
diff --git a/src/input_stream.h b/src/input_stream.h
index 10ad97161..24dda1eee 100644
--- a/src/input_stream.h
+++ b/src/input_stream.h
@@ -29,64 +29,13 @@
#include <stdbool.h>
#include <sys/types.h>
-struct input_stream {
- /**
- * the plugin which implements this input stream
- */
- const struct input_plugin *plugin;
+struct input_stream;
- /**
- * The absolute URI which was used to open this stream. May
- * be NULL if this is unknown.
- */
- char *uri;
+#ifdef __cplusplus
+extern "C" {
- /**
- * A mutex that protects the mutable attributes of this object
- * and its implementation. It must be locked before calling
- * any of the public methods.
- *
- * This object is allocated by the client, and the client is
- * responsible for freeing it.
- */
- GMutex *mutex;
-
- /**
- * A cond that gets signalled when the state of this object
- * changes from the I/O thread. The client of this object may
- * wait on it. Optional, may be NULL.
- *
- * This object is allocated by the client, and the client is
- * responsible for freeing it.
- */
- GCond *cond;
-
- /**
- * indicates whether the stream is ready for reading and
- * whether the other attributes in this struct are valid
- */
- bool ready;
-
- /**
- * if true, then the stream is fully seekable
- */
- bool seekable;
-
- /**
- * the size of the resource, or -1 if unknown
- */
- goffset size;
-
- /**
- * the current offset within the stream
- */
- goffset offset;
-
- /**
- * the MIME content type of the resource, or NULL if unknown
- */
- char *mime;
-};
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
/**
* Opens a new input stream. You may not access it until the "ready"
@@ -99,13 +48,15 @@ struct input_stream {
* notifications
* @return an #input_stream object on success, NULL on error
*/
-gcc_nonnull(1, 2)
+gcc_nonnull(1)
G_GNUC_MALLOC
struct input_stream *
input_stream_open(const char *uri,
- GMutex *mutex, GCond *cond,
+ Mutex &mutex, Cond &cond,
GError **error_r);
+#endif
+
/**
* Close the input stream and free resources.
*
@@ -115,20 +66,6 @@ gcc_nonnull(1)
void
input_stream_close(struct input_stream *is);
-gcc_nonnull(1)
-static inline void
-input_stream_lock(struct input_stream *is)
-{
- g_mutex_lock(is->mutex);
-}
-
-gcc_nonnull(1)
-static inline void
-input_stream_unlock(struct input_stream *is)
-{
- g_mutex_unlock(is->mutex);
-}
-
/**
* Check for errors that may have occurred in the I/O thread.
*
@@ -163,6 +100,33 @@ gcc_nonnull(1)
void
input_stream_lock_wait_ready(struct input_stream *is);
+gcc_nonnull_all gcc_pure
+const char *
+input_stream_get_mime_type(const struct input_stream *is);
+
+gcc_nonnull_all
+void
+input_stream_override_mime_type(struct input_stream *is, const char *mime);
+
+gcc_nonnull_all gcc_pure
+goffset
+input_stream_get_size(const struct input_stream *is);
+
+gcc_nonnull_all gcc_pure
+goffset
+input_stream_get_offset(const struct input_stream *is);
+
+gcc_nonnull_all gcc_pure
+bool
+input_stream_is_seekable(const struct input_stream *is);
+
+/**
+ * Determines whether seeking is cheap. This is true for local files.
+ */
+gcc_pure gcc_nonnull(1)
+bool
+input_stream_cheap_seeking(const struct input_stream *is);
+
/**
* Seeks to the specified position in the stream. This will most
* likely fail if the "seekable" flag is false.
@@ -264,4 +228,8 @@ size_t
input_stream_lock_read(struct input_stream *is, void *ptr, size_t size,
GError **error_r);
+#ifdef __cplusplus
+}
+#endif
+
#endif