aboutsummaryrefslogtreecommitdiffstats
path: root/src/input
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/input/ArchiveInputPlugin.cxx99
-rw-r--r--src/input/ArchiveInputPlugin.hxx25
-rw-r--r--src/input/AsyncInputStream.cxx257
-rw-r--r--src/input/AsyncInputStream.hxx162
-rw-r--r--src/input/CdioParanoiaInputPlugin.cxx406
-rw-r--r--src/input/CdioParanoiaInputPlugin.hxx28
-rw-r--r--src/input/CurlInputPlugin.cxx1166
-rw-r--r--src/input/CurlInputPlugin.hxx25
-rw-r--r--src/input/Domain.cxx24
-rw-r--r--src/input/Domain.hxx27
-rw-r--r--src/input/FfmpegInputPlugin.cxx179
-rw-r--r--src/input/FfmpegInputPlugin.hxx28
-rw-r--r--src/input/FileInputPlugin.cxx159
-rw-r--r--src/input/FileInputPlugin.hxx25
-rw-r--r--src/input/IcyInputStream.cxx99
-rw-r--r--src/input/IcyInputStream.hxx68
-rw-r--r--src/input/Init.cxx87
-rw-r--r--src/input/Init.hxx36
-rw-r--r--src/input/InputPlugin.hxx85
-rw-r--r--src/input/InputStream.cxx141
-rw-r--r--src/input/InputStream.hxx369
-rw-r--r--src/input/LocalOpen.cxx59
-rw-r--r--src/input/LocalOpen.hxx38
-rw-r--r--src/input/MmsInputPlugin.cxx130
-rw-r--r--src/input/MmsInputPlugin.hxx25
-rw-r--r--src/input/Offset.hxx32
-rw-r--r--src/input/Open.cxx78
-rw-r--r--src/input/ProxyInputStream.cxx100
-rw-r--r--src/input/ProxyInputStream.hxx64
-rw-r--r--src/input/Registry.cxx86
-rw-r--r--src/input/Registry.hxx43
-rw-r--r--src/input/RewindInputPlugin.cxx252
-rw-r--r--src/input/RewindInputPlugin.hxx37
-rw-r--r--src/input/TextInputStream.cxx84
-rw-r--r--src/input/TextInputStream.hxx52
-rw-r--r--src/input/ThreadInputStream.cxx173
-rw-r--r--src/input/ThreadInputStream.hxx144
-rw-r--r--src/input/plugins/AlsaInputPlugin.cxx385
-rw-r--r--src/input/plugins/AlsaInputPlugin.hxx28
-rw-r--r--src/input/plugins/ArchiveInputPlugin.cxx89
-rw-r--r--src/input/plugins/ArchiveInputPlugin.hxx34
-rw-r--r--src/input/plugins/CdioParanoiaInputPlugin.cxx375
-rw-r--r--src/input/plugins/CdioParanoiaInputPlugin.hxx28
-rw-r--r--src/input/plugins/CurlInputPlugin.cxx876
-rw-r--r--src/input/plugins/CurlInputPlugin.hxx25
-rw-r--r--src/input/plugins/FfmpegInputPlugin.cxx154
-rw-r--r--src/input/plugins/FfmpegInputPlugin.hxx28
-rw-r--r--src/input/plugins/FileInputPlugin.cxx138
-rw-r--r--src/input/plugins/FileInputPlugin.hxx36
-rw-r--r--src/input/plugins/MmsInputPlugin.cxx117
-rw-r--r--src/input/plugins/MmsInputPlugin.hxx25
-rw-r--r--src/input/plugins/NfsInputPlugin.cxx268
-rw-r--r--src/input/plugins/NfsInputPlugin.hxx25
-rw-r--r--src/input/plugins/RewindInputPlugin.cxx156
-rw-r--r--src/input/plugins/RewindInputPlugin.hxx37
-rw-r--r--src/input/plugins/SmbclientInputPlugin.cxx158
-rw-r--r--src/input/plugins/SmbclientInputPlugin.hxx25
57 files changed, 5315 insertions, 2584 deletions
diff --git a/src/input/ArchiveInputPlugin.cxx b/src/input/ArchiveInputPlugin.cxx
deleted file mode 100644
index 5288f2b3b..000000000
--- a/src/input/ArchiveInputPlugin.cxx
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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 "ArchiveDomain.hxx"
-#include "ArchiveLookup.hxx"
-#include "ArchiveList.hxx"
-#include "ArchivePlugin.hxx"
-#include "ArchiveFile.hxx"
-#include "InputPlugin.hxx"
-#include "util/Error.hxx"
-#include "fs/Traits.hxx"
-#include "Log.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 InputStream *
-input_archive_open(const char *pathname,
- Mutex &mutex, Cond &cond,
- Error &error)
-{
- const struct archive_plugin *arplug;
- InputStream *is;
-
- if (!PathTraits::IsAbsoluteFS(pathname))
- return nullptr;
-
- char *pname = g_strdup(pathname);
- // archive_lookup will modify pname when true is returned
- const char *archive, *filename, *suffix;
- if (!archive_lookup(pname, &archive, &filename, &suffix)) {
- FormatDebug(archive_domain,
- "not an archive, lookup %s failed", pname);
- g_free(pname);
- return nullptr;
- }
-
- //check which archive plugin to use (by ext)
- arplug = archive_plugin_from_suffix(suffix);
- if (!arplug) {
- FormatWarning(archive_domain,
- "can't handle archive %s", archive);
- g_free(pname);
- return nullptr;
- }
-
- auto file = archive_file_open(arplug, archive, error);
- if (file == nullptr) {
- g_free(pname);
- return nullptr;
- }
-
- //setup fileops
- is = file->OpenStream(filename, mutex, cond, error);
- g_free(pname);
- file->Close();
-
- return is;
-}
-
-const InputPlugin 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
deleted file mode 100644
index 9ac70b2fc..000000000
--- a/src/input/ArchiveInputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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 InputPlugin input_plugin_archive;
-
-#endif
diff --git a/src/input/AsyncInputStream.cxx b/src/input/AsyncInputStream.cxx
new file mode 100644
index 000000000..5795ecead
--- /dev/null
+++ b/src/input/AsyncInputStream.cxx
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2003-2014 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 "AsyncInputStream.hxx"
+#include "Domain.hxx"
+#include "tag/Tag.hxx"
+#include "event/Call.hxx"
+#include "thread/Cond.hxx"
+#include "IOThread.hxx"
+#include "util/HugeAllocator.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+AsyncInputStream::AsyncInputStream(const char *_url,
+ Mutex &_mutex, Cond &_cond,
+ void *_buffer, size_t _buffer_size,
+ size_t _resume_at)
+ :InputStream(_url, _mutex, _cond), DeferredMonitor(io_thread_get()),
+ buffer((uint8_t *)_buffer, _buffer_size),
+ resume_at(_resume_at),
+ open(true),
+ paused(false),
+ seek_state(SeekState::NONE),
+ tag(nullptr) {}
+
+AsyncInputStream::~AsyncInputStream()
+{
+ delete tag;
+
+ buffer.Clear();
+ HugeFree(buffer.Write().data, buffer.GetCapacity());
+}
+
+void
+AsyncInputStream::SetTag(Tag *_tag)
+{
+ delete tag;
+ tag = _tag;
+}
+
+void
+AsyncInputStream::Pause()
+{
+ assert(io_thread_inside());
+
+ paused = true;
+}
+
+void
+AsyncInputStream::PostponeError(Error &&error)
+{
+ assert(io_thread_inside());
+
+ seek_state = SeekState::NONE;
+ postponed_error = std::move(error);
+ cond.broadcast();
+}
+
+inline void
+AsyncInputStream::Resume()
+{
+ assert(io_thread_inside());
+
+ if (paused) {
+ paused = false;
+ DoResume();
+ }
+}
+
+bool
+AsyncInputStream::Check(Error &error)
+{
+ bool success = !postponed_error.IsDefined();
+ if (!success) {
+ error = std::move(postponed_error);
+ postponed_error.Clear();
+ }
+
+ return success;
+}
+
+bool
+AsyncInputStream::IsEOF()
+{
+ return (KnownSize() && offset >= size) ||
+ (!open && buffer.IsEmpty());
+}
+
+bool
+AsyncInputStream::Seek(offset_type new_offset, Error &error)
+{
+ assert(IsReady());
+ assert(seek_state == SeekState::NONE);
+
+ if (new_offset == offset)
+ /* no-op */
+ return true;
+
+ if (!IsSeekable()) {
+ error.Set(input_domain, "Not seekable");
+ return false;
+ }
+
+ /* check if we can fast-forward the buffer */
+
+ while (new_offset > offset) {
+ auto r = buffer.Read();
+ if (r.IsEmpty())
+ break;
+
+ const size_t nbytes =
+ new_offset - offset < (offset_type)r.size
+ ? new_offset - offset
+ : r.size;
+
+ buffer.Consume(nbytes);
+ offset += nbytes;
+ }
+
+ if (new_offset == offset)
+ return true;
+
+ /* no: ask the implementation to seek */
+
+ seek_offset = new_offset;
+ seek_state = SeekState::SCHEDULED;
+
+ DeferredMonitor::Schedule();
+
+ while (seek_state != SeekState::NONE)
+ cond.wait(mutex);
+
+ if (!Check(error))
+ return false;
+
+ return true;
+}
+
+void
+AsyncInputStream::SeekDone()
+{
+ assert(io_thread_inside());
+ assert(IsSeekPending());
+
+ /* we may have reached end-of-file previously, and the
+ connection may have been closed already; however after
+ seeking successfully, the connection must be alive again */
+ open = true;
+
+ seek_state = SeekState::NONE;
+ cond.broadcast();
+}
+
+Tag *
+AsyncInputStream::ReadTag()
+{
+ Tag *result = tag;
+ tag = nullptr;
+ return result;
+}
+
+bool
+AsyncInputStream::IsAvailable()
+{
+ return postponed_error.IsDefined() ||
+ IsEOF() ||
+ !buffer.IsEmpty();
+}
+
+size_t
+AsyncInputStream::Read(void *ptr, size_t read_size, Error &error)
+{
+ assert(!io_thread_inside());
+
+ /* wait for data */
+ CircularBuffer<uint8_t>::Range r;
+ while (true) {
+ if (!Check(error))
+ return 0;
+
+ r = buffer.Read();
+ if (!r.IsEmpty() || IsEOF())
+ break;
+
+ cond.wait(mutex);
+ }
+
+ const size_t nbytes = std::min(read_size, r.size);
+ memcpy(ptr, r.data, nbytes);
+ buffer.Consume(nbytes);
+
+ offset += (offset_type)nbytes;
+
+ if (paused && buffer.GetSize() < resume_at)
+ DeferredMonitor::Schedule();
+
+ return nbytes;
+}
+
+void
+AsyncInputStream::AppendToBuffer(const void *data, size_t append_size)
+{
+ auto w = buffer.Write();
+ assert(!w.IsEmpty());
+
+ size_t nbytes = std::min(w.size, append_size);
+ memcpy(w.data, data, nbytes);
+ buffer.Append(nbytes);
+
+ const size_t remaining = append_size - nbytes;
+ if (remaining > 0) {
+ w = buffer.Write();
+ assert(!w.IsEmpty());
+ assert(w.size >= remaining);
+
+ memcpy(w.data, (const uint8_t *)data + nbytes, remaining);
+ buffer.Append(remaining);
+ }
+
+ if (!IsReady())
+ SetReady();
+ else
+ cond.broadcast();
+}
+
+void
+AsyncInputStream::RunDeferred()
+{
+ const ScopeLock protect(mutex);
+
+ Resume();
+
+ if (seek_state == SeekState::SCHEDULED) {
+ seek_state = SeekState::PENDING;
+ buffer.Clear();
+ paused = false;
+ DoSeek(seek_offset);
+ }
+}
diff --git a/src/input/AsyncInputStream.hxx b/src/input/AsyncInputStream.hxx
new file mode 100644
index 000000000..d1f0c3b9d
--- /dev/null
+++ b/src/input/AsyncInputStream.hxx
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2003-2014 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_ASYNC_INPUT_STREAM_HXX
+#define MPD_ASYNC_INPUT_STREAM_HXX
+
+#include "InputStream.hxx"
+#include "event/DeferredMonitor.hxx"
+#include "util/CircularBuffer.hxx"
+#include "util/Error.hxx"
+
+/**
+ * Helper class for moving asynchronous (non-blocking) InputStream
+ * implementations to the I/O thread. Data is being read into a ring
+ * buffer, and that buffer is then consumed by another thread using
+ * the regular #InputStream API.
+ */
+class AsyncInputStream : public InputStream, private DeferredMonitor {
+ enum class SeekState : uint8_t {
+ NONE, SCHEDULED, PENDING
+ };
+
+ CircularBuffer<uint8_t> buffer;
+ const size_t resume_at;
+
+ bool open;
+
+ /**
+ * 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;
+
+ SeekState seek_state;
+
+ /**
+ * The #Tag object ready to be requested via
+ * InputStream::ReadTag().
+ */
+ Tag *tag;
+
+ offset_type seek_offset;
+
+protected:
+ Error postponed_error;
+
+public:
+ AsyncInputStream(const char *_url,
+ Mutex &_mutex, Cond &_cond,
+ void *_buffer, size_t _buffer_size,
+ size_t _resume_at);
+
+ virtual ~AsyncInputStream();
+
+ /* virtual methods from InputStream */
+ bool Check(Error &error) final;
+ bool IsEOF() final;
+ bool Seek(offset_type new_offset, Error &error) final;
+ Tag *ReadTag() final;
+ bool IsAvailable() final;
+ size_t Read(void *ptr, size_t read_size, Error &error) final;
+
+protected:
+ /**
+ * Pass an tag from the I/O thread to the client thread.
+ */
+ void SetTag(Tag *_tag);
+
+ void ClearTag() {
+ SetTag(nullptr);
+ }
+
+ void Pause();
+
+ bool IsPaused() const {
+ return paused;
+ }
+
+ /**
+ * Declare that the underlying stream was closed. We will
+ * continue feeding Read() calls from the buffer until it runs
+ * empty.
+ */
+ void SetClosed() {
+ open = false;
+ }
+
+ /**
+ * Pass an error from the I/O thread to the client thread.
+ */
+ void PostponeError(Error &&error);
+
+ bool IsBufferEmpty() const {
+ return buffer.IsEmpty();
+ }
+
+ bool IsBufferFull() const {
+ return buffer.IsFull();
+ }
+
+ /**
+ * Determine how many bytes can be added to the buffer.
+ */
+ gcc_pure
+ size_t GetBufferSpace() const {
+ return buffer.GetSpace();
+ }
+
+ /**
+ * Append data to the buffer. The size must fit into the
+ * buffer; see GetBufferSpace().
+ */
+ void AppendToBuffer(const void *data, size_t append_size);
+
+ /**
+ * Implement code here that will resume the stream after it
+ * has been paused due to full input buffer.
+ */
+ virtual void DoResume() = 0;
+
+ /**
+ * The actual Seek() implementation. This virtual method will
+ * be called from within the I/O thread. When the operation
+ * is finished, call SeekDone() to notify the caller.
+ */
+ virtual void DoSeek(offset_type new_offset) = 0;
+
+ bool IsSeekPending() const {
+ return seek_state == SeekState::PENDING;
+ }
+
+ /**
+ * Call this after seeking has finished. It will notify the
+ * client thread.
+ */
+ void SeekDone();
+
+private:
+ void Resume();
+
+ /* virtual methods from DeferredMonitor */
+ void RunDeferred() final;
+};
+
+#endif
diff --git a/src/input/CdioParanoiaInputPlugin.cxx b/src/input/CdioParanoiaInputPlugin.cxx
deleted file mode 100644
index b3ac57413..000000000
--- a/src/input/CdioParanoiaInputPlugin.cxx
+++ /dev/null
@@ -1,406 +0,0 @@
-/*
- * 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 "InputStream.hxx"
-#include "InputPlugin.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "system/ByteOrder.hxx"
-#include "fs/AllocatedPath.hxx"
-#include "Log.hxx"
-#include "ConfigData.hxx"
-#include "ConfigError.hxx"
-
-#include <stdio.h>
-#include <stdint.h>
-#include <stddef.h>
-#include <string.h>
-#include <stdlib.h>
-#include <glib.h>
-#include <assert.h>
-
-#ifdef HAVE_CDIO_PARANOIA_PARANOIA_H
-#include <cdio/paranoia/paranoia.h>
-#else
-#include <cdio/paranoia.h>
-#endif
-
-#include <cdio/cd_types.h>
-
-struct CdioParanoiaInputStream {
- InputStream 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 constexpr Domain cdio_domain("cdio");
-
-static bool default_reverse_endian;
-
-static bool
-input_cdio_init(const config_param &param, Error &error)
-{
- const char *value = param.GetBlockValue("default_byte_order");
- if (value != nullptr) {
- if (strcmp(value, "little_endian") == 0)
- default_reverse_endian = IsBigEndian();
- else if (strcmp(value, "big_endian") == 0)
- default_reverse_endian = IsLittleEndian();
- else {
- error.Format(config_domain, 0,
- "Unrecognized 'default_byte_order' setting: %s",
- value);
- return false;
- }
- }
-
- return true;
-}
-
-static void
-input_cdio_close(InputStream *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, Error &error)
-{
- 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) {
- error.Set(cdio_domain, "Malformed track number");
- return false;
- }
-
- if (endptr == track)
- /* play the whole CD */
- dest->track = -1;
-
- return true;
-}
-
-static AllocatedPath
-cdio_detect_device(void)
-{
- char **devices = cdio_get_devices_with_cap(nullptr, CDIO_FS_AUDIO,
- false);
- if (devices == nullptr)
- return AllocatedPath::Null();
-
- AllocatedPath path = AllocatedPath::FromFS(devices[0]);
- cdio_free_device_list(devices);
- return path;
-}
-
-static InputStream *
-input_cdio_open(const char *uri,
- Mutex &mutex, Cond &cond,
- Error &error)
-{
- struct cdio_uri parsed_uri;
- if (!parse_cdio_uri(&parsed_uri, uri, error))
- return nullptr;
-
- CdioParanoiaInputStream *i =
- new CdioParanoiaInputStream(uri, mutex, cond,
- parsed_uri.track);
-
- /* get list of CD's supporting CD-DA */
- const AllocatedPath device = parsed_uri.device[0] != 0
- ? AllocatedPath::FromFS(parsed_uri.device)
- : cdio_detect_device();
- if (device.IsNull()) {
- error.Set(cdio_domain,
- "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.c_str(), DRIVER_UNKNOWN);
-
- i->drv = cdio_cddap_identify_cdio(i->cdio, 1, nullptr);
-
- if ( !i->drv ) {
- error.Set(cdio_domain, "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) ) {
- error.Set(cdio_domain, "Unable to open disc.");
- delete i;
- return nullptr;
- }
-
- bool reverse_endian;
- switch (data_bigendianp(i->drv)) {
- case -1:
- LogDebug(cdio_domain, "drive returns unknown audio data");
- reverse_endian = default_reverse_endian;
- break;
-
- case 0:
- LogDebug(cdio_domain, "drive returns audio data Little Endian");
- reverse_endian = IsBigEndian();
- break;
-
- case 1:
- LogDebug(cdio_domain, "drive returns audio data Big Endian");
- reverse_endian = IsLittleEndian();
- break;
-
- default:
- error.Format(cdio_domain, "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 = reverse_endian
- ? "audio/x-mpd-cdda-pcm-reverse"
- : "audio/x-mpd-cdda-pcm";
-
- return &i->base;
-}
-
-static bool
-input_cdio_seek(InputStream *is,
- InputPlugin::offset_type offset, int whence, Error &error)
-{
- 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) {
- error.Format(cdio_domain, "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(InputStream *is, void *ptr, size_t length,
- Error &error)
-{
- 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) {
- FormatError(cdio_domain,
- "paranoia_read: %s", s_err);
- free(s_err);
- }
- s_mess = cdda_messages(cis->drv);
- if (s_mess) {
- free(s_mess);
- }
- if (!rbuf) {
- error.Set(cdio_domain,
- "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(InputStream *is)
-{
- CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is;
-
- return (cis->lsn_from + cis->lsn_relofs > cis->lsn_to);
-}
-
-const InputPlugin input_plugin_cdio_paranoia = {
- "cdio_paranoia",
- input_cdio_init,
- 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
deleted file mode 100644
index 847802a48..000000000
--- a/src/input/CdioParanoiaInputPlugin.hxx
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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 InputPlugin input_plugin_cdio_paranoia;
-
-#endif
diff --git a/src/input/CurlInputPlugin.cxx b/src/input/CurlInputPlugin.cxx
deleted file mode 100644
index 031ebfea6..000000000
--- a/src/input/CurlInputPlugin.cxx
+++ /dev/null
@@ -1,1166 +0,0 @@
-/*
- * 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 "InputStream.hxx"
-#include "InputPlugin.hxx"
-#include "ConfigGlobal.hxx"
-#include "ConfigData.hxx"
-#include "tag/Tag.hxx"
-#include "IcyMetaDataParser.hxx"
-#include "event/SocketMonitor.hxx"
-#include "event/TimeoutMonitor.hxx"
-#include "event/Call.hxx"
-#include "IOThread.hxx"
-#include "util/ASCII.hxx"
-#include "util/CharUtil.hxx"
-#include "util/NumberParser.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.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 <curl/curl.h>
-#include <glib.h>
-
-#if LIBCURL_VERSION_NUM < 0x071200
-#error libcurl is too old
-#endif
-
-/**
- * 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 {
- InputStream base;
-
- /* some buffers which were passed to libcurl, which we have
- too free */
- char range[32];
- 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 */
- std::string meta_name;
-
- /** the tag object ready to be requested via
- InputStream::ReadTag() */
- Tag *tag;
-
- Error postponed_error;
-
- input_curl(const char *url, Mutex &mutex, Cond &cond)
- :base(input_plugin_curl, url, mutex, cond),
- request_headers(nullptr),
- paused(false),
- tag(nullptr) {}
-
- ~input_curl();
-
- input_curl(const input_curl &) = delete;
- input_curl &operator=(const input_curl &) = delete;
-};
-
-class CurlMulti;
-
-/**
- * Monitor for one socket created by CURL.
- */
-class CurlSocket final : SocketMonitor {
- CurlMulti &multi;
-
-public:
- CurlSocket(CurlMulti &_multi, EventLoop &_loop, int _fd)
- :SocketMonitor(_fd, _loop), multi(_multi) {}
-
- ~CurlSocket() {
- /* TODO: sometimes, CURL uses CURL_POLL_REMOVE after
- closing the socket, and sometimes, it uses
- CURL_POLL_REMOVE just to move the (still open)
- connection to the pool; in the first case,
- Abandon() would be most appropriate, but it breaks
- the second case - is that a CURL bug? is there a
- better solution? */
-
- Steal();
- }
-
- /**
- * Callback function for CURLMOPT_SOCKETFUNCTION.
- */
- static int SocketFunction(CURL *easy,
- curl_socket_t s, int action,
- void *userp, void *socketp);
-
- virtual bool OnSocketReady(unsigned flags) override;
-
-private:
- static constexpr int FlagsToCurlCSelect(unsigned flags) {
- return (flags & (READ | HANGUP) ? CURL_CSELECT_IN : 0) |
- (flags & WRITE ? CURL_CSELECT_OUT : 0) |
- (flags & ERROR ? CURL_CSELECT_ERR : 0);
- }
-
- gcc_const
- static unsigned CurlPollToFlags(int action) {
- switch (action) {
- case CURL_POLL_NONE:
- return 0;
-
- case CURL_POLL_IN:
- return READ;
-
- case CURL_POLL_OUT:
- return WRITE;
-
- case CURL_POLL_INOUT:
- return READ|WRITE;
- }
-
- assert(false);
- gcc_unreachable();
- }
-};
-
-/**
- * Manager for the global CURLM object.
- */
-class CurlMulti final : private TimeoutMonitor {
- CURLM *const multi;
-
-public:
- CurlMulti(EventLoop &_loop, CURLM *_multi);
-
- ~CurlMulti() {
- curl_multi_cleanup(multi);
- }
-
- bool Add(input_curl *c, Error &error);
- void Remove(input_curl *c);
-
- /**
- * Check for finished HTTP responses.
- *
- * Runs in the I/O thread. The caller must not hold locks.
- */
- void ReadInfo();
-
- void Assign(curl_socket_t fd, CurlSocket &cs) {
- curl_multi_assign(multi, fd, &cs);
- }
-
- void SocketAction(curl_socket_t fd, int ev_bitmask);
-
- void InvalidateSockets() {
- SocketAction(CURL_SOCKET_TIMEOUT, 0);
- }
-
- /**
- * This is a kludge to allow pausing/resuming a stream with
- * libcurl < 7.32.0. Read the curl_easy_pause manpage for
- * more information.
- */
- void ResumeSockets() {
- int running_handles;
- curl_multi_socket_all(multi, &running_handles);
- }
-
-private:
- static int TimerFunction(CURLM *multi, long timeout_ms, void *userp);
-
- virtual void OnTimeout() override;
-};
-
-/**
- * libcurl version number encoded in a 24 bit integer.
- */
-static unsigned curl_version_num;
-
-/** 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 CurlMulti *curl_multi;
-
-static constexpr Domain http_domain("http");
-static constexpr Domain curl_domain("curl");
-static constexpr Domain curlm_domain("curlm");
-
-CurlMulti::CurlMulti(EventLoop &_loop, CURLM *_multi)
- :TimeoutMonitor(_loop), multi(_multi)
-{
- curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION,
- CurlSocket::SocketFunction);
- curl_multi_setopt(multi, CURLMOPT_SOCKETDATA, this);
-
- curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, TimerFunction);
- curl_multi_setopt(multi, CURLMOPT_TIMERDATA, this);
-}
-
-/**
- * Find a request by its CURL "easy" handle.
- *
- * Runs in the I/O thread. No lock needed.
- */
-gcc_pure
-static struct input_curl *
-input_curl_find_request(CURL *easy)
-{
- assert(io_thread_inside());
-
- void *p;
- CURLcode code = curl_easy_getinfo(easy, CURLINFO_PRIVATE, &p);
- if (code != CURLE_OK)
- return nullptr;
-
- return (input_curl *)p;
-}
-
-static void
-input_curl_resume(struct input_curl *c)
-{
- assert(io_thread_inside());
-
- if (c->paused) {
- c->paused = false;
- curl_easy_pause(c->easy, CURLPAUSE_CONT);
-
- if (curl_version_num < 0x072000)
- /* libcurl older than 7.32.0 does not update
- its sockets after curl_easy_pause(); force
- libcurl to do it now */
- curl_multi->ResumeSockets();
-
- curl_multi->InvalidateSockets();
- }
-}
-
-int
-CurlSocket::SocketFunction(gcc_unused CURL *easy,
- curl_socket_t s, int action,
- void *userp, void *socketp) {
- CurlMulti &multi = *(CurlMulti *)userp;
- CurlSocket *cs = (CurlSocket *)socketp;
-
- assert(io_thread_inside());
-
- if (action == CURL_POLL_REMOVE) {
- delete cs;
- return 0;
- }
-
- if (cs == nullptr) {
- cs = new CurlSocket(multi, io_thread_get(), s);
- multi.Assign(s, *cs);
- } else {
-#ifdef USE_EPOLL
- /* when using epoll, we need to unregister the socket
- each time this callback is invoked, because older
- CURL versions may omit the CURL_POLL_REMOVE call
- when the socket has been closed and recreated with
- the same file number (bug found in CURL 7.26, CURL
- 7.33 not affected); in that case, epoll refuses the
- EPOLL_CTL_MOD because it does not know the new
- socket yet */
- cs->Cancel();
-#endif
- }
-
- unsigned flags = CurlPollToFlags(action);
- if (flags != 0)
- cs->Schedule(flags);
- return 0;
-}
-
-bool
-CurlSocket::OnSocketReady(unsigned flags)
-{
- assert(io_thread_inside());
-
- multi.SocketAction(Get(), FlagsToCurlCSelect(flags));
- return true;
-}
-
-/**
- * Runs in the I/O thread. No lock needed.
- */
-inline bool
-CurlMulti::Add(struct input_curl *c, Error &error)
-{
- assert(io_thread_inside());
- assert(c != nullptr);
- assert(c->easy != nullptr);
-
- CURLMcode mcode = curl_multi_add_handle(multi, c->easy);
- if (mcode != CURLM_OK) {
- error.Format(curlm_domain, mcode,
- "curl_multi_add_handle() failed: %s",
- curl_multi_strerror(mcode));
- return false;
- }
-
- InvalidateSockets();
- return true;
-}
-
-/**
- * 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, Error &error)
-{
- assert(c != nullptr);
- assert(c->easy != nullptr);
-
- bool result;
- BlockingCall(io_thread_get(), [c, &error, &result](){
- result = curl_multi->Add(c, error);
- });
- return result;
-}
-
-inline void
-CurlMulti::Remove(input_curl *c)
-{
- curl_multi_remove_handle(multi, c->easy);
-}
-
-/**
- * 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 != nullptr);
-
- if (c->easy == nullptr)
- return;
-
- curl_multi->Remove(c);
-
- curl_easy_cleanup(c->easy);
- c->easy = nullptr;
-
- curl_slist_free_all(c->request_headers);
- c->request_headers = nullptr;
-}
-
-/**
- * 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)
-{
- BlockingCall(io_thread_get(), [c](){
- input_curl_easy_free(c);
- curl_multi->InvalidateSockets();
- });
-
- assert(c->easy == nullptr);
-}
-
-/**
- * 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 != nullptr);
- assert(c->easy == nullptr);
- assert(!c->postponed_error.IsDefined());
-
- const ScopeLock protect(c->base.mutex);
-
- if (result != CURLE_OK) {
- c->postponed_error.Format(curl_domain, result,
- "curl failed: %s", c->error);
- } else if (status < 200 || status >= 300) {
- c->postponed_error.Format(http_domain, status,
- "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 != nullptr);
-
- long status = 0;
- curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status);
-
- input_curl_easy_free(c);
- input_curl_request_done(c, result, status);
-}
-
-void
-CurlMulti::SocketAction(curl_socket_t fd, int ev_bitmask)
-{
- int running_handles;
- CURLMcode mcode = curl_multi_socket_action(multi, fd, ev_bitmask,
- &running_handles);
- if (mcode != CURLM_OK)
- FormatError(curlm_domain,
- "curl_multi_socket_action() failed: %s",
- curl_multi_strerror(mcode));
-
- ReadInfo();
-}
-
-/**
- * Check for finished HTTP responses.
- *
- * Runs in the I/O thread. The caller must not hold locks.
- */
-inline void
-CurlMulti::ReadInfo()
-{
- assert(io_thread_inside());
-
- CURLMsg *msg;
- int msgs_in_queue;
-
- while ((msg = curl_multi_info_read(multi,
- &msgs_in_queue)) != nullptr) {
- if (msg->msg == CURLMSG_DONE)
- input_curl_handle_done(msg->easy_handle, msg->data.result);
- }
-}
-
-int
-CurlMulti::TimerFunction(gcc_unused CURLM *_multi, long timeout_ms, void *userp)
-{
- CurlMulti &multi = *(CurlMulti *)userp;
- assert(_multi == multi.multi);
-
- if (timeout_ms < 0) {
- multi.Cancel();
- return 0;
- }
-
- if (timeout_ms >= 0 && timeout_ms < 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. */
- timeout_ms = 10;
-
- multi.Schedule(timeout_ms);
- return 0;
-}
-
-void
-CurlMulti::OnTimeout()
-{
- SocketAction(CURL_SOCKET_TIMEOUT, 0);
-}
-
-/*
- * InputPlugin methods
- *
- */
-
-static bool
-input_curl_init(const config_param &param, Error &error)
-{
- CURLcode code = curl_global_init(CURL_GLOBAL_ALL);
- if (code != CURLE_OK) {
- error.Format(curl_domain, code,
- "curl_global_init() failed: %s",
- curl_easy_strerror(code));
- return false;
- }
-
- const auto version_info = curl_version_info(CURLVERSION_FIRST);
- if (version_info != nullptr) {
- FormatDebug(curl_domain, "version %s", version_info->version);
- if (version_info->features & CURL_VERSION_SSL)
- FormatDebug(curl_domain, "with %s",
- version_info->ssl_version);
-
- curl_version_num = version_info->version_num;
- }
-
- http_200_aliases = curl_slist_append(http_200_aliases, "ICY 200 OK");
-
- proxy = param.GetBlockValue("proxy");
- proxy_port = param.GetBlockValue("proxy_port", 0u);
- proxy_user = param.GetBlockValue("proxy_user");
- proxy_password = param.GetBlockValue("proxy_password");
-
- if (proxy == nullptr) {
- /* deprecated proxy configuration */
- proxy = config_get_string(CONF_HTTP_PROXY_HOST, nullptr);
- proxy_port = config_get_positive(CONF_HTTP_PROXY_PORT, 0);
- proxy_user = config_get_string(CONF_HTTP_PROXY_USER, nullptr);
- proxy_password = config_get_string(CONF_HTTP_PROXY_PASSWORD,
- "");
- }
-
- CURLM *multi = curl_multi_init();
- if (multi == nullptr) {
- error.Set(curl_domain, 0, "curl_multi_init() failed");
- return false;
- }
-
- curl_multi = new CurlMulti(io_thread_get(), multi);
- return true;
-}
-
-static void
-input_curl_finish(void)
-{
- BlockingCall(io_thread_get(), [](){
- delete 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.
- */
-gcc_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()
-{
- delete tag;
-
- input_curl_easy_free_indirect(this);
-}
-
-static bool
-input_curl_check(InputStream *is, Error &error)
-{
- struct input_curl *c = (struct input_curl *)is;
-
- bool success = !c->postponed_error.IsDefined();
- if (!success) {
- error = std::move(c->postponed_error);
- c->postponed_error.Clear();
- }
-
- return success;
-}
-
-static Tag *
-input_curl_tag(InputStream *is)
-{
- struct input_curl *c = (struct input_curl *)is;
- Tag *tag = c->tag;
-
- c->tag = nullptr;
- return tag;
-}
-
-static bool
-fill_buffer(struct input_curl *c, Error &error)
-{
- while (c->easy != nullptr && c->buffers.empty())
- c->base.cond.wait(c->base.mutex);
-
- if (c->postponed_error.IsDefined()) {
- error = std::move(c->postponed_error);
- c->postponed_error.Clear();
- 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)
-{
- Tag *tag = c->icy.ReadTag();
-
- if (tag == nullptr)
- return;
-
- delete c->tag;
-
- if (!c->meta_name.empty() && !tag->HasType(TAG_NAME))
- tag->AddItem(TAG_NAME, c->meta_name.c_str());
-
- c->tag = tag;
-}
-
-static bool
-input_curl_available(InputStream *is)
-{
- struct input_curl *c = (struct input_curl *)is;
-
- return c->postponed_error.IsDefined() || c->easy == nullptr ||
- !c->buffers.empty();
-}
-
-static size_t
-input_curl_read(InputStream *is, void *ptr, size_t size,
- Error &error)
-{
- 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);
- 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 += (InputPlugin::offset_type)nbytes;
-
- if (c->paused && curl_total_buffer_size(c) < CURL_RESUME_AT) {
- c->base.mutex.unlock();
-
- BlockingCall(io_thread_get(), [c](){
- input_curl_resume(c);
- });
-
- c->base.mutex.lock();
- }
-
- return nbytes;
-}
-
-static void
-input_curl_close(InputStream *is)
-{
- struct input_curl *c = (struct input_curl *)is;
-
- delete c;
-}
-
-static bool
-input_curl_eof(gcc_unused InputStream *is)
-{
- struct input_curl *c = (struct input_curl *)is;
-
- return c->easy == nullptr && 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 == nullptr || (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 && IsWhitespaceOrNull(*value))
- ++value;
-
- while (end > value && IsWhitespaceOrNull(end[-1]))
- --end;
-
- if (StringEqualsCaseASCII(name, "accept-ranges")) {
- /* a stream with icy-metadata is not seekable */
- if (!c->icy.IsDefined())
- c->base.seekable = true;
- } else if (StringEqualsCaseASCII(name, "content-length")) {
- 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 + ParseUint64(buffer);
- } else if (StringEqualsCaseASCII(name, "content-type")) {
- c->base.mime.assign(value, end);
- } else if (StringEqualsCaseASCII(name, "icy-name") ||
- StringEqualsCaseASCII(name, "ice-name") ||
- StringEqualsCaseASCII(name, "x-audiocast-name")) {
- c->meta_name.assign(value, end);
-
- delete c->tag;
-
- c->tag = new Tag();
- c->tag->AddItem(TAG_NAME, c->meta_name.c_str());
- } else if (StringEqualsCaseASCII(name, "icy-metaint")) {
- 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 = ParseUint64(buffer);
- FormatDebug(curl_domain, "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, Error &error)
-{
- CURLcode code;
-
- c->easy = curl_easy_init();
- if (c->easy == nullptr) {
- error.Set(curl_domain, "curl_easy_init() failed");
- return false;
- }
-
- curl_easy_setopt(c->easy, CURLOPT_PRIVATE, (void *)c);
- 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, 1l);
- curl_easy_setopt(c->easy, CURLOPT_NETRC, 1l);
- curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5l);
- curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, 1l);
- 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 != nullptr)
- 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 != nullptr && proxy_password != nullptr) {
- char proxy_auth_str[1024];
- snprintf(proxy_auth_str, sizeof(proxy_auth_str),
- "%s:%s",
- proxy_user, proxy_password);
- curl_easy_setopt(c->easy, CURLOPT_PROXYUSERPWD, proxy_auth_str);
- }
-
- code = curl_easy_setopt(c->easy, CURLOPT_URL, c->base.uri.c_str());
- if (code != CURLE_OK) {
- error.Format(curl_domain, code,
- "curl_easy_setopt() failed: %s",
- curl_easy_strerror(code));
- return false;
- }
-
- c->request_headers = nullptr;
- 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(InputStream *is, InputPlugin::offset_type offset,
- int whence,
- Error &error)
-{
- 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 < (InputPlugin::offset_type)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);
- if (!ret)
- return false;
-
- /* send the "Range" header */
-
- if (is->offset > 0) {
- sprintf(c->range, "%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))
- return false;
-
- c->base.mutex.lock();
-
- while (!c->base.ready)
- c->base.cond.wait(c->base.mutex);
-
- if (c->postponed_error.IsDefined()) {
- error = std::move(c->postponed_error);
- c->postponed_error.Clear();
- return false;
- }
-
- return true;
-}
-
-static InputStream *
-input_curl_open(const char *url, Mutex &mutex, Cond &cond,
- Error &error)
-{
- if (memcmp(url, "http://", 7) != 0 &&
- memcmp(url, "https://", 8) != 0)
- return nullptr;
-
- struct input_curl *c = new input_curl(url, mutex, cond);
-
- if (!input_curl_easy_init(c, error)) {
- delete c;
- return nullptr;
- }
-
- if (!input_curl_easy_add_indirect(c, error)) {
- delete c;
- return nullptr;
- }
-
- return &c->base;
-}
-
-const struct InputPlugin 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
deleted file mode 100644
index 30e917257..000000000
--- a/src/input/CurlInputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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
-
-extern const struct InputPlugin input_plugin_curl;
-
-#endif
diff --git a/src/input/Domain.cxx b/src/input/Domain.cxx
new file mode 100644
index 000000000..26ae298a4
--- /dev/null
+++ b/src/input/Domain.cxx
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2003-2014 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 "Domain.hxx"
+#include "util/Domain.hxx"
+
+const Domain input_domain("input");
diff --git a/src/input/Domain.hxx b/src/input/Domain.hxx
new file mode 100644
index 000000000..16fa5e0f1
--- /dev/null
+++ b/src/input/Domain.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2014 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_DOMAIN_HXX
+#define MPD_INPUT_DOMAIN_HXX
+
+class Domain;
+
+extern const Domain input_domain;
+
+#endif
diff --git a/src/input/FfmpegInputPlugin.cxx b/src/input/FfmpegInputPlugin.cxx
deleted file mode 100644
index 8f9cd0b86..000000000
--- a/src/input/FfmpegInputPlugin.cxx
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * 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 "InputStream.hxx"
-#include "InputPlugin.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-
-extern "C" {
-#include <libavutil/avutil.h>
-#include <libavformat/avio.h>
-#include <libavformat/avformat.h>
-}
-
-#include <glib.h>
-
-struct FfmpegInputStream {
- InputStream 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 = "audio/x-mpd-ffmpeg";
- }
-
- ~FfmpegInputStream() {
- avio_close(h);
- }
-};
-
-static constexpr Domain ffmpeg_domain("ffmpeg");
-
-static inline bool
-input_ffmpeg_supported(void)
-{
- void *opaque = nullptr;
- return avio_enum_protocols(&opaque, 0) != nullptr;
-}
-
-static bool
-input_ffmpeg_init(gcc_unused const config_param &param,
- Error &error)
-{
- av_register_all();
-
- /* disable this plugin if there's no registered protocol */
- if (!input_ffmpeg_supported()) {
- error.Set(ffmpeg_domain, "No protocol");
- return false;
- }
-
- return true;
-}
-
-static InputStream *
-input_ffmpeg_open(const char *uri,
- Mutex &mutex, Cond &cond,
- Error &error)
-{
- 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) {
- error.Set(ffmpeg_domain, 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(InputStream *is, void *ptr, size_t size,
- Error &error)
-{
- FfmpegInputStream *i = (FfmpegInputStream *)is;
-
- int ret = avio_read(i->h, (unsigned char *)ptr, size);
- if (ret <= 0) {
- if (ret < 0)
- error.Set(ffmpeg_domain, "avio_read() failed");
-
- i->eof = true;
- return false;
- }
-
- is->offset += ret;
- return (size_t)ret;
-}
-
-static void
-input_ffmpeg_close(InputStream *is)
-{
- FfmpegInputStream *i = (FfmpegInputStream *)is;
-
- delete i;
-}
-
-static bool
-input_ffmpeg_eof(InputStream *is)
-{
- FfmpegInputStream *i = (FfmpegInputStream *)is;
-
- return i->eof;
-}
-
-static bool
-input_ffmpeg_seek(InputStream *is, InputPlugin::offset_type offset,
- int whence,
- Error &error)
-{
- FfmpegInputStream *i = (FfmpegInputStream *)is;
- int64_t ret = avio_seek(i->h, offset, whence);
-
- if (ret >= 0) {
- i->eof = false;
- return true;
- } else {
- error.Set(ffmpeg_domain, "avio_seek() failed");
- return false;
- }
-}
-
-const InputPlugin 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
deleted file mode 100644
index 9bc2eeaea..000000000
--- a/src/input/FfmpegInputPlugin.hxx
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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 InputPlugin input_plugin_ffmpeg;
-
-#endif
diff --git a/src/input/FileInputPlugin.cxx b/src/input/FileInputPlugin.cxx
deleted file mode 100644
index 26e40d609..000000000
--- a/src/input/FileInputPlugin.cxx
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * 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 "InputStream.hxx"
-#include "InputPlugin.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "fs/Traits.hxx"
-#include "system/fd_util.h"
-#include "open.h"
-
-#include <sys/stat.h>
-#include <unistd.h>
-#include <errno.h>
-#include <string.h>
-#include <glib.h>
-
-static constexpr Domain file_domain("file");
-
-struct FileInputStream {
- InputStream 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 InputStream *
-input_file_open(const char *filename,
- Mutex &mutex, Cond &cond,
- Error &error)
-{
- int fd, ret;
- struct stat st;
-
- if (!PathTraits::IsAbsoluteFS(filename))
- return nullptr;
-
- fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0);
- if (fd < 0) {
- if (errno != ENOENT && errno != ENOTDIR)
- error.FormatErrno("Failed to open \"%s\"",
- filename);
- return nullptr;
- }
-
- ret = fstat(fd, &st);
- if (ret < 0) {
- error.FormatErrno("Failed to stat \"%s\"", filename);
- close(fd);
- return nullptr;
- }
-
- if (!S_ISREG(st.st_mode)) {
- error.Format(file_domain, "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(InputStream *is, InputPlugin::offset_type offset,
- int whence,
- Error &error)
-{
- FileInputStream *fis = (FileInputStream *)is;
-
- offset = (InputPlugin::offset_type)lseek(fis->fd, (off_t)offset, whence);
- if (offset < 0) {
- error.SetErrno("Failed to seek");
- return false;
- }
-
- is->offset = offset;
- return true;
-}
-
-static size_t
-input_file_read(InputStream *is, void *ptr, size_t size,
- Error &error)
-{
- FileInputStream *fis = (FileInputStream *)is;
- ssize_t nbytes;
-
- nbytes = read(fis->fd, ptr, size);
- if (nbytes < 0) {
- error.SetErrno("Failed to read");
- return 0;
- }
-
- is->offset += nbytes;
- return (size_t)nbytes;
-}
-
-static void
-input_file_close(InputStream *is)
-{
- FileInputStream *fis = (FileInputStream *)is;
-
- delete fis;
-}
-
-static bool
-input_file_eof(InputStream *is)
-{
- return is->offset >= is->size;
-}
-
-const InputPlugin 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
deleted file mode 100644
index f76f4dd0e..000000000
--- a/src/input/FileInputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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 InputPlugin input_plugin_file;
-
-#endif
diff --git a/src/input/IcyInputStream.cxx b/src/input/IcyInputStream.cxx
new file mode 100644
index 000000000..fb82cdec6
--- /dev/null
+++ b/src/input/IcyInputStream.cxx
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2003-2014 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 "IcyInputStream.hxx"
+#include "tag/Tag.hxx"
+
+IcyInputStream::IcyInputStream(InputStream *_input)
+ :ProxyInputStream(_input),
+ input_tag(nullptr), icy_tag(nullptr),
+ override_offset(0)
+{
+}
+
+IcyInputStream::~IcyInputStream()
+{
+ delete input_tag;
+ delete icy_tag;
+}
+
+void
+IcyInputStream::Update()
+{
+ ProxyInputStream::Update();
+
+ if (IsEnabled())
+ offset = override_offset;
+}
+
+Tag *
+IcyInputStream::ReadTag()
+{
+ Tag *new_input_tag = ProxyInputStream::ReadTag();
+ if (!IsEnabled())
+ return new_input_tag;
+
+ if (new_input_tag != nullptr) {
+ delete input_tag;
+ input_tag = new_input_tag;
+ }
+
+ Tag *new_icy_tag = parser.ReadTag();
+ if (new_icy_tag != nullptr) {
+ delete icy_tag;
+ icy_tag = new_icy_tag;
+ }
+
+ if (new_input_tag == nullptr && new_icy_tag == nullptr)
+ /* no change */
+ return nullptr;
+
+ if (input_tag == nullptr && icy_tag == nullptr)
+ /* no tag */
+ return nullptr;
+
+ if (input_tag == nullptr)
+ return new Tag(*icy_tag);
+
+ if (icy_tag == nullptr)
+ return new Tag(*input_tag);
+
+ return Tag::Merge(*input_tag, *icy_tag);
+}
+
+size_t
+IcyInputStream::Read(void *ptr, size_t read_size, Error &error)
+{
+ if (!IsEnabled())
+ return ProxyInputStream::Read(ptr, read_size, error);
+
+ while (true) {
+ size_t nbytes = ProxyInputStream::Read(ptr, read_size, error);
+ if (nbytes == 0)
+ return 0;
+
+ size_t result = parser.ParseInPlace(ptr, nbytes);
+ if (result > 0) {
+ override_offset += result;
+ offset = override_offset;
+ return result;
+ }
+ }
+}
diff --git a/src/input/IcyInputStream.hxx b/src/input/IcyInputStream.hxx
new file mode 100644
index 000000000..d8968a741
--- /dev/null
+++ b/src/input/IcyInputStream.hxx
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2003-2014 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_ICY_INPUT_STREAM_HXX
+#define MPD_ICY_INPUT_STREAM_HXX
+
+#include "ProxyInputStream.hxx"
+#include "IcyMetaDataParser.hxx"
+#include "Compiler.h"
+
+struct Tag;
+
+/**
+ * An #InputStream filter that parses Icy metadata.
+ */
+class IcyInputStream final : public ProxyInputStream {
+ IcyMetaDataParser parser;
+
+ /**
+ * The #Tag object ready to be requested via ReadTag().
+ */
+ Tag *input_tag;
+
+ /**
+ * The #Tag object ready to be requested via ReadTag().
+ */
+ Tag *icy_tag;
+
+ offset_type override_offset;
+
+public:
+ IcyInputStream(InputStream *_input);
+ virtual ~IcyInputStream();
+
+ IcyInputStream(const IcyInputStream &) = delete;
+ IcyInputStream &operator=(const IcyInputStream &) = delete;
+
+ void Enable(size_t _data_size) {
+ parser.Start(_data_size);
+ }
+
+ bool IsEnabled() const {
+ return parser.IsDefined();
+ }
+
+ /* virtual methods from InputStream */
+ void Update() override;
+ Tag *ReadTag() override;
+ size_t Read(void *ptr, size_t size, Error &error) override;
+};
+
+#endif
diff --git a/src/input/Init.cxx b/src/input/Init.cxx
new file mode 100644
index 000000000..0ee87c4d8
--- /dev/null
+++ b/src/input/Init.cxx
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2003-2014 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 "Init.hxx"
+#include "Registry.hxx"
+#include "InputPlugin.hxx"
+#include "util/Error.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigOption.hxx"
+#include "config/ConfigData.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+bool
+input_stream_global_init(Error &error)
+{
+ const config_param empty;
+
+ for (unsigned i = 0; input_plugins[i] != nullptr; ++i) {
+ const InputPlugin *plugin = input_plugins[i];
+
+ assert(plugin->name != nullptr);
+ assert(*plugin->name != 0);
+ assert(plugin->open != nullptr);
+
+ const struct config_param *param =
+ config_find_block(CONF_INPUT, "plugin", plugin->name);
+ if (param == nullptr) {
+ param = &empty;
+ } else if (!param->GetBlockValue("enabled", true))
+ /* the plugin is disabled in mpd.conf */
+ continue;
+
+ InputPlugin::InitResult result = plugin->init != nullptr
+ ? plugin->init(*param, error)
+ : InputPlugin::InitResult::SUCCESS;
+
+ switch (result) {
+ case InputPlugin::InitResult::SUCCESS:
+ input_plugins_enabled[i] = true;
+ break;
+
+ case InputPlugin::InitResult::ERROR:
+ error.FormatPrefix("Failed to initialize input plugin '%s': ",
+ plugin->name);
+ return false;
+
+ case InputPlugin::InitResult::UNAVAILABLE:
+ if (error.IsDefined()) {
+ FormatError(error,
+ "Input plugin '%s' is unavailable",
+ plugin->name);
+ error.Clear();
+ }
+
+ break;
+ }
+ }
+
+ return true;
+}
+
+void input_stream_global_finish(void)
+{
+ input_plugins_for_each_enabled(plugin)
+ if (plugin->finish != nullptr)
+ plugin->finish();
+}
diff --git a/src/input/Init.hxx b/src/input/Init.hxx
new file mode 100644
index 000000000..875fdce7c
--- /dev/null
+++ b/src/input/Init.hxx
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2003-2014 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_HXX
+#define MPD_INPUT_INIT_HXX
+
+class Error;
+
+/**
+ * Initializes this library and all input_stream implementations.
+ */
+bool
+input_stream_global_init(Error &error);
+
+/**
+ * Deinitializes this library and all input_stream implementations.
+ */
+void input_stream_global_finish(void);
+
+#endif
diff --git a/src/input/InputPlugin.hxx b/src/input/InputPlugin.hxx
new file mode 100644
index 000000000..c2adb419c
--- /dev/null
+++ b/src/input/InputPlugin.hxx
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2003-2014 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_HXX
+#define MPD_INPUT_PLUGIN_HXX
+
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef WIN32
+#include <windows.h>
+/* damn you, windows.h! */
+#ifdef ERROR
+#undef ERROR
+#endif
+#endif
+
+struct config_param;
+class InputStream;
+class Error;
+struct Tag;
+
+struct InputPlugin {
+ enum class InitResult {
+ /**
+ * A fatal error has occurred (e.g. misconfiguration).
+ * The #Error has been set.
+ */
+ ERROR,
+
+ /**
+ * The plugin was initialized successfully and is
+ * ready to be used.
+ */
+ SUCCESS,
+
+ /**
+ * The plugin is not available and shall be disabled.
+ * The #Error may be set describing the situation (to
+ * be logged).
+ */
+ UNAVAILABLE,
+ };
+
+ const char *name;
+
+ /**
+ * Global initialization. This method is called when MPD starts.
+ *
+ * @return true on success, false if the plugin should be
+ * disabled
+ */
+ InitResult (*init)(const config_param &param, Error &error);
+
+ /**
+ * Global deinitialization. Called once before MPD shuts
+ * down (only if init() has returned true).
+ */
+ void (*finish)(void);
+
+ InputStream *(*open)(const char *uri,
+ Mutex &mutex, Cond &cond,
+ Error &error);
+};
+
+#endif
diff --git a/src/input/InputStream.cxx b/src/input/InputStream.cxx
new file mode 100644
index 000000000..44f726a62
--- /dev/null
+++ b/src/input/InputStream.cxx
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2003-2014 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 "InputStream.hxx"
+#include "thread/Cond.hxx"
+#include "util/StringUtil.hxx"
+
+#include <assert.h>
+
+InputStream::~InputStream()
+{
+}
+
+bool
+InputStream::Check(gcc_unused Error &error)
+{
+ return true;
+}
+
+void
+InputStream::Update()
+{
+}
+
+void
+InputStream::SetReady()
+{
+ assert(!ready);
+
+ ready = true;
+ cond.broadcast();
+}
+
+void
+InputStream::WaitReady()
+{
+ while (true) {
+ Update();
+ if (ready)
+ break;
+
+ cond.wait(mutex);
+ }
+}
+
+void
+InputStream::LockWaitReady()
+{
+ const ScopeLock protect(mutex);
+ WaitReady();
+}
+
+/**
+ * Is seeking on resources behind this URI "expensive"? For example,
+ * seeking in a HTTP file requires opening a new connection with a new
+ * HTTP request.
+ */
+gcc_pure
+static bool
+ExpensiveSeeking(const char *uri)
+{
+ return StringStartsWith(uri, "http://") ||
+ StringStartsWith(uri, "https://");
+}
+
+bool
+InputStream::CheapSeeking() const
+{
+ return IsSeekable() && !ExpensiveSeeking(uri.c_str());
+}
+
+bool
+InputStream::Seek(gcc_unused offset_type new_offset,
+ gcc_unused Error &error)
+{
+ return false;
+}
+
+bool
+InputStream::LockSeek(offset_type _offset, Error &error)
+{
+ const ScopeLock protect(mutex);
+ return Seek(_offset, error);
+}
+
+Tag *
+InputStream::ReadTag()
+{
+ return nullptr;
+}
+
+Tag *
+InputStream::LockReadTag()
+{
+ const ScopeLock protect(mutex);
+ return ReadTag();
+}
+
+bool
+InputStream::IsAvailable()
+{
+ return true;
+}
+
+size_t
+InputStream::LockRead(void *ptr, size_t _size, Error &error)
+{
+#if !CLANG_CHECK_VERSION(3,6)
+ /* disabled on clang due to -Wtautological-pointer-compare */
+ assert(ptr != nullptr);
+#endif
+ assert(_size > 0);
+
+ const ScopeLock protect(mutex);
+ return Read(ptr, _size, error);
+}
+
+bool
+InputStream::LockIsEOF()
+{
+ const ScopeLock protect(mutex);
+ return IsEOF();
+}
+
diff --git a/src/input/InputStream.hxx b/src/input/InputStream.hxx
new file mode 100644
index 000000000..81b903ba2
--- /dev/null
+++ b/src/input/InputStream.hxx
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2003-2014 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_STREAM_HXX
+#define MPD_INPUT_STREAM_HXX
+
+#include "check.h"
+#include "Offset.hxx"
+#include "thread/Mutex.hxx"
+#include "Compiler.h"
+
+#include <string>
+
+#include <assert.h>
+#include <stdint.h>
+
+class Cond;
+class Error;
+struct Tag;
+
+class InputStream {
+public:
+ typedef ::offset_type offset_type;
+
+private:
+ /**
+ * The absolute URI which was used to open this stream.
+ */
+ std::string uri;
+
+public:
+ /**
+ * 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.
+ */
+ Mutex &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 nullptr.
+ *
+ * This object is allocated by the client, and the client is
+ * responsible for freeing it.
+ */
+ Cond &cond;
+
+protected:
+ /**
+ * 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;
+
+ static constexpr offset_type UNKNOWN_SIZE = -1;
+
+ /**
+ * the size of the resource, or #UNKNOWN_SIZE if unknown
+ */
+ offset_type size;
+
+ /**
+ * the current offset within the stream
+ */
+ offset_type offset;
+
+private:
+ /**
+ * the MIME content type of the resource, or empty if unknown.
+ */
+ std::string mime;
+
+public:
+ InputStream(const char *_uri, Mutex &_mutex, Cond &_cond)
+ :uri(_uri),
+ mutex(_mutex), cond(_cond),
+ ready(false), seekable(false),
+ size(UNKNOWN_SIZE), offset(0) {
+ assert(_uri != nullptr);
+ }
+
+ /**
+ * Close the input stream and free resources.
+ *
+ * The caller must not lock the mutex.
+ */
+ virtual ~InputStream();
+
+ /**
+ * Opens a new input stream. You may not access it until the "ready"
+ * flag is set.
+ *
+ * @param mutex a mutex that is used to protect this object; must be
+ * locked before calling any of the public methods
+ * @param cond a cond that gets signalled when the state of
+ * this object changes; may be nullptr if the caller doesn't want to get
+ * notifications
+ * @return an #InputStream object on success, nullptr on error
+ */
+ gcc_nonnull_all
+ gcc_malloc
+ static InputStream *Open(const char *uri, Mutex &mutex, Cond &cond,
+ Error &error);
+
+ /**
+ * Just like Open(), but waits for the stream to become ready.
+ * It is a wrapper for Open(), WaitReady() and Check().
+ */
+ gcc_malloc gcc_nonnull_all
+ static InputStream *OpenReady(const char *uri,
+ Mutex &mutex, Cond &cond,
+ Error &error);
+
+ /**
+ * The absolute URI which was used to open this stream.
+ *
+ * No lock necessary for this method.
+ */
+ const char *GetURI() const {
+ return uri.c_str();
+ }
+
+ void Lock() {
+ mutex.lock();
+ }
+
+ void Unlock() {
+ mutex.unlock();
+ }
+
+ /**
+ * Check for errors that may have occurred in the I/O thread.
+ *
+ * @return false on error
+ */
+ virtual bool Check(Error &error);
+
+ /**
+ * Update the public attributes. Call before accessing attributes
+ * such as "ready" or "offset".
+ */
+ virtual void Update();
+
+ void SetReady();
+
+ /**
+ * Return whether the stream is ready for reading and whether
+ * the other attributes in this struct are valid.
+ *
+ * The caller must lock the mutex.
+ */
+ bool IsReady() const {
+ return ready;
+ }
+
+ void WaitReady();
+
+ /**
+ * Wrapper for WaitReady() which locks and unlocks the mutex;
+ * the caller must not be holding it already.
+ */
+ void LockWaitReady();
+
+ gcc_pure
+ bool HasMimeType() const {
+ assert(ready);
+
+ return !mime.empty();
+ }
+
+ gcc_pure
+ const char *GetMimeType() const {
+ assert(ready);
+
+ return mime.empty() ? nullptr : mime.c_str();
+ }
+
+ void ClearMimeType() {
+ mime.clear();
+ }
+
+ gcc_nonnull_all
+ void SetMimeType(const char *_mime) {
+ assert(!ready);
+
+ mime = _mime;
+ }
+
+ void SetMimeType(std::string &&_mime) {
+ assert(!ready);
+
+ mime = std::move(_mime);
+ }
+
+ gcc_nonnull_all
+ void OverrideMimeType(const char *_mime) {
+ assert(ready);
+
+ mime = _mime;
+ }
+
+ gcc_pure
+ bool KnownSize() const {
+ assert(ready);
+
+ return size != UNKNOWN_SIZE;
+ }
+
+ gcc_pure
+ offset_type GetSize() const {
+ assert(ready);
+ assert(KnownSize());
+
+ return size;
+ }
+
+ void AddOffset(offset_type delta) {
+ assert(ready);
+
+ offset += delta;
+ }
+
+ gcc_pure
+ offset_type GetOffset() const {
+ assert(ready);
+
+ return offset;
+ }
+
+ gcc_pure
+ offset_type GetRest() const {
+ assert(ready);
+ assert(KnownSize());
+
+ return size - offset;
+ }
+
+ gcc_pure
+ bool IsSeekable() const {
+ assert(ready);
+
+ return seekable;
+ }
+
+ /**
+ * Determines whether seeking is cheap. This is true for local files.
+ */
+ gcc_pure
+ bool CheapSeeking() const;
+
+ /**
+ * Seeks to the specified position in the stream. This will most
+ * likely fail if the "seekable" flag is false.
+ *
+ * The caller must lock the mutex.
+ *
+ * @param offset the relative offset
+ */
+ virtual bool Seek(offset_type offset, Error &error);
+
+ /**
+ * Wrapper for Seek() which locks and unlocks the mutex; the
+ * caller must not be holding it already.
+ */
+ bool LockSeek(offset_type offset, Error &error);
+
+ /**
+ * Rewind to the beginning of the stream. This is a wrapper
+ * for Seek(0, error).
+ */
+ bool Rewind(Error &error) {
+ return Seek(0, error);
+ }
+
+ bool LockRewind(Error &error) {
+ return LockSeek(0, error);
+ }
+
+ /**
+ * Returns true if the stream has reached end-of-file.
+ *
+ * The caller must lock the mutex.
+ */
+ gcc_pure
+ virtual bool IsEOF() = 0;
+
+ /**
+ * Wrapper for IsEOF() which locks and unlocks the mutex; the
+ * caller must not be holding it already.
+ */
+ gcc_pure
+ bool LockIsEOF();
+
+ /**
+ * Reads the tag from the stream.
+ *
+ * The caller must lock the mutex.
+ *
+ * @return a tag object which must be freed by the caller, or
+ * nullptr if the tag has not changed since the last call
+ */
+ gcc_malloc
+ virtual Tag *ReadTag();
+
+ /**
+ * Wrapper for ReadTag() which locks and unlocks the mutex;
+ * the caller must not be holding it already.
+ */
+ gcc_malloc
+ Tag *LockReadTag();
+
+ /**
+ * 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.
+ *
+ * The caller must lock the mutex.
+ */
+ gcc_pure
+ virtual bool IsAvailable();
+
+ /**
+ * Reads data from the stream into the caller-supplied buffer.
+ * Returns 0 on error or eof (check with IsEOF()).
+ *
+ * The caller must lock the mutex.
+ *
+ * @param is the InputStream object
+ * @param ptr the buffer to read into
+ * @param size the maximum number of bytes to read
+ * @return the number of bytes read
+ */
+ gcc_nonnull_all
+ virtual size_t Read(void *ptr, size_t size, Error &error) = 0;
+
+ /**
+ * Wrapper for Read() which locks and unlocks the mutex;
+ * the caller must not be holding it already.
+ */
+ gcc_nonnull_all
+ size_t LockRead(void *ptr, size_t size, Error &error);
+};
+
+#endif
diff --git a/src/input/LocalOpen.cxx b/src/input/LocalOpen.cxx
new file mode 100644
index 000000000..ad8eba8ce
--- /dev/null
+++ b/src/input/LocalOpen.cxx
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2003-2014 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 "LocalOpen.hxx"
+#include "InputStream.hxx"
+#include "plugins/FileInputPlugin.hxx"
+
+#ifdef ENABLE_ARCHIVE
+#include "plugins/ArchiveInputPlugin.hxx"
+#endif
+
+#include "fs/Path.hxx"
+#include "util/Error.hxx"
+
+#include <assert.h>
+
+#ifdef ENABLE_ARCHIVE
+#include <errno.h>
+#endif
+
+InputStream *
+OpenLocalInputStream(Path path, Mutex &mutex, Cond &cond, Error &error)
+{
+ assert(!error.IsDefined());
+
+ InputStream *is = OpenFileInputStream(path, mutex, cond, error);
+#ifdef ENABLE_ARCHIVE
+ if (is == nullptr && error.IsDomain(errno_domain) &&
+ error.GetCode() == ENOTDIR) {
+ /* ENOTDIR means this may be a path inside an archive
+ file */
+ Error error2;
+ is = OpenArchiveInputStream(path, mutex, cond, error2);
+ if (is == nullptr && error2.IsDefined())
+ error = std::move(error2);
+ }
+#endif
+
+ assert(is == nullptr || is->IsReady());
+
+ return is;
+}
diff --git a/src/input/LocalOpen.hxx b/src/input/LocalOpen.hxx
new file mode 100644
index 000000000..cf1b2b632
--- /dev/null
+++ b/src/input/LocalOpen.hxx
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2003-2014 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_LOCAL_OPEN_HXX
+#define MPD_INPUT_LOCAL_OPEN_HXX
+
+#include "check.h"
+
+class InputStream;
+class Path;
+class Mutex;
+class Cond;
+class Error;
+
+/**
+ * Open a "local" file. This is a wrapper for the input plugins
+ * "file" and "archive".
+ */
+InputStream *
+OpenLocalInputStream(Path path, Mutex &mutex, Cond &cond, Error &error);
+
+#endif
diff --git a/src/input/MmsInputPlugin.cxx b/src/input/MmsInputPlugin.cxx
deleted file mode 100644
index e97c1eb3f..000000000
--- a/src/input/MmsInputPlugin.cxx
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * 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 "MmsInputPlugin.hxx"
-#include "InputStream.hxx"
-#include "InputPlugin.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-
-#include <glib.h>
-#include <libmms/mmsx.h>
-
-#include <string.h>
-#include <errno.h>
-
-struct MmsInputStream {
- InputStream 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 = "audio/x-ms-wma";
-
- base.ready = true;
- }
-
- ~MmsInputStream() {
- mmsx_close(mms);
- }
-};
-
-static constexpr Domain mms_domain("mms");
-
-static InputStream *
-input_mms_open(const char *url,
- Mutex &mutex, Cond &cond,
- Error &error)
-{
- 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) {
- error.Set(mms_domain, "mmsx_connect() failed");
- return nullptr;
- }
-
- auto m = new MmsInputStream(url, mutex, cond, mms);
- return &m->base;
-}
-
-static size_t
-input_mms_read(InputStream *is, void *ptr, size_t size,
- Error &error)
-{
- MmsInputStream *m = (MmsInputStream *)is;
- int ret;
-
- ret = mmsx_read(nullptr, m->mms, (char *)ptr, size);
- if (ret <= 0) {
- if (ret < 0)
- error.SetErrno("mmsx_read() failed");
-
- m->eof = true;
- return false;
- }
-
- is->offset += ret;
-
- return (size_t)ret;
-}
-
-static void
-input_mms_close(InputStream *is)
-{
- MmsInputStream *m = (MmsInputStream *)is;
-
- delete m;
-}
-
-static bool
-input_mms_eof(InputStream *is)
-{
- MmsInputStream *m = (MmsInputStream *)is;
-
- return m->eof;
-}
-
-const InputPlugin input_plugin_mms = {
- "mms",
- nullptr,
- nullptr,
- input_mms_open,
- input_mms_close,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- input_mms_read,
- input_mms_eof,
- nullptr,
-};
diff --git a/src/input/MmsInputPlugin.hxx b/src/input/MmsInputPlugin.hxx
deleted file mode 100644
index e3d3ba3c8..000000000
--- a/src/input/MmsInputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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 INPUT_MMS_H
-#define INPUT_MMS_H
-
-extern const struct InputPlugin input_plugin_mms;
-
-#endif
diff --git a/src/input/Offset.hxx b/src/input/Offset.hxx
new file mode 100644
index 000000000..552397904
--- /dev/null
+++ b/src/input/Offset.hxx
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2003-2014 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_OFFSET_HXX
+#define MPD_OFFSET_HXX
+
+#include "check.h"
+
+#include <stdint.h>
+
+/**
+ * A type for absolute offsets in a file.
+ */
+typedef uint64_t offset_type;
+
+#endif
diff --git a/src/input/Open.cxx b/src/input/Open.cxx
new file mode 100644
index 000000000..66ccdce74
--- /dev/null
+++ b/src/input/Open.cxx
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2003-2014 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 "InputStream.hxx"
+#include "Registry.hxx"
+#include "InputPlugin.hxx"
+#include "LocalOpen.hxx"
+#include "Domain.hxx"
+#include "plugins/RewindInputPlugin.hxx"
+#include "fs/Traits.hxx"
+#include "fs/Path.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+InputStream *
+InputStream::Open(const char *url,
+ Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ if (PathTraitsFS::IsAbsolute(url))
+ /* TODO: the parameter is UTF-8, not filesystem charset */
+ return OpenLocalInputStream(Path::FromFS(url),
+ mutex, cond, error);
+
+ input_plugins_for_each_enabled(plugin) {
+ InputStream *is;
+
+ is = plugin->open(url, mutex, cond, error);
+ if (is != nullptr) {
+ is = input_rewind_open(is);
+
+ return is;
+ } else if (error.IsDefined())
+ return nullptr;
+ }
+
+ error.Set(input_domain, "Unrecognized URI");
+ return nullptr;
+}
+
+InputStream *
+InputStream::OpenReady(const char *uri,
+ Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ InputStream *is = Open(uri, mutex, cond, error);
+ if (is == nullptr)
+ return nullptr;
+
+ mutex.lock();
+ is->WaitReady();
+ bool success = is->Check(error);
+ mutex.unlock();
+
+ if (!success) {
+ delete is;
+ is = nullptr;
+ }
+
+ return is;
+}
diff --git a/src/input/ProxyInputStream.cxx b/src/input/ProxyInputStream.cxx
new file mode 100644
index 000000000..74a272f6a
--- /dev/null
+++ b/src/input/ProxyInputStream.cxx
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2003-2014 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 "ProxyInputStream.hxx"
+#include "tag/Tag.hxx"
+
+#include <assert.h>
+
+ProxyInputStream::ProxyInputStream(InputStream *_input)
+ :InputStream(_input->GetURI(), _input->mutex, _input->cond),
+ input(*_input) {}
+
+ProxyInputStream::~ProxyInputStream()
+{
+ delete &input;
+}
+
+void
+ProxyInputStream::CopyAttributes()
+{
+ if (input.IsReady()) {
+ if (!IsReady()) {
+ if (input.HasMimeType())
+ SetMimeType(input.GetMimeType());
+
+ size = input.KnownSize()
+ ? input.GetSize()
+ : UNKNOWN_SIZE;
+
+ seekable = input.IsSeekable();
+ SetReady();
+ }
+
+ offset = input.GetOffset();
+ }
+}
+
+bool
+ProxyInputStream::Check(Error &error)
+{
+ return input.Check(error);
+}
+
+void
+ProxyInputStream::Update()
+{
+ input.Update();
+ CopyAttributes();
+}
+
+bool
+ProxyInputStream::Seek(offset_type new_offset, Error &error)
+{
+ bool success = input.Seek(new_offset, error);
+ CopyAttributes();
+ return success;
+}
+
+bool
+ProxyInputStream::IsEOF()
+{
+ return input.IsEOF();
+}
+
+Tag *
+ProxyInputStream::ReadTag()
+{
+ return input.ReadTag();
+}
+
+bool
+ProxyInputStream::IsAvailable()
+{
+ return input.IsAvailable();
+}
+
+size_t
+ProxyInputStream::Read(void *ptr, size_t read_size, Error &error)
+{
+ size_t nbytes = input.Read(ptr, read_size, error);
+ CopyAttributes();
+ return nbytes;
+}
diff --git a/src/input/ProxyInputStream.hxx b/src/input/ProxyInputStream.hxx
new file mode 100644
index 000000000..727ae5917
--- /dev/null
+++ b/src/input/ProxyInputStream.hxx
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2003-2014 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_PROXY_INPUT_STREAM_HXX
+#define MPD_PROXY_INPUT_STREAM_HXX
+
+#include "InputStream.hxx"
+
+struct Tag;
+
+/**
+ * An #InputStream that forwards all methods call to another
+ * #InputStream instance. This can be used as a base class to
+ * override selected methods.
+ */
+class ProxyInputStream : public InputStream {
+protected:
+ InputStream &input;
+
+public:
+ gcc_nonnull_all
+ ProxyInputStream(InputStream *_input);
+
+ virtual ~ProxyInputStream();
+
+ ProxyInputStream(const ProxyInputStream &) = delete;
+ ProxyInputStream &operator=(const ProxyInputStream &) = delete;
+
+ /* virtual methods from InputStream */
+ bool Check(Error &error) override;
+ void Update() override;
+ bool Seek(offset_type new_offset, Error &error) override;
+ bool IsEOF() override;
+ Tag *ReadTag() override;
+ bool IsAvailable() override;
+ size_t Read(void *ptr, size_t read_size, Error &error) override;
+
+protected:
+ /**
+ * 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();
+};
+
+#endif
diff --git a/src/input/Registry.cxx b/src/input/Registry.cxx
new file mode 100644
index 000000000..748c18ca8
--- /dev/null
+++ b/src/input/Registry.cxx
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2003-2014 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 "Registry.hxx"
+#include "util/Macros.hxx"
+#include "plugins/FileInputPlugin.hxx"
+
+#ifdef HAVE_ALSA
+#include "plugins/AlsaInputPlugin.hxx"
+#endif
+
+#ifdef ENABLE_ARCHIVE
+#include "plugins/ArchiveInputPlugin.hxx"
+#endif
+
+#ifdef ENABLE_CURL
+#include "plugins/CurlInputPlugin.hxx"
+#endif
+
+#ifdef HAVE_FFMPEG
+#include "plugins/FfmpegInputPlugin.hxx"
+#endif
+
+#ifdef ENABLE_SMBCLIENT
+#include "plugins/SmbclientInputPlugin.hxx"
+#endif
+
+#ifdef ENABLE_NFS
+#include "plugins/NfsInputPlugin.hxx"
+#endif
+
+#ifdef ENABLE_MMS
+#include "plugins/MmsInputPlugin.hxx"
+#endif
+
+#ifdef ENABLE_CDIO_PARANOIA
+#include "plugins/CdioParanoiaInputPlugin.hxx"
+#endif
+
+const InputPlugin *const input_plugins[] = {
+ &input_plugin_file,
+#ifdef HAVE_ALSA
+ &input_plugin_alsa,
+#endif
+#ifdef ENABLE_ARCHIVE
+ &input_plugin_archive,
+#endif
+#ifdef ENABLE_CURL
+ &input_plugin_curl,
+#endif
+#ifdef HAVE_FFMPEG
+ &input_plugin_ffmpeg,
+#endif
+#ifdef ENABLE_SMBCLIENT
+ &input_plugin_smbclient,
+#endif
+#ifdef ENABLE_NFS
+ &input_plugin_nfs,
+#endif
+#ifdef ENABLE_MMS
+ &input_plugin_mms,
+#endif
+#ifdef ENABLE_CDIO_PARANOIA
+ &input_plugin_cdio_paranoia,
+#endif
+ nullptr
+};
+
+bool input_plugins_enabled[ARRAY_SIZE(input_plugins) - 1];
diff --git a/src/input/Registry.hxx b/src/input/Registry.hxx
new file mode 100644
index 000000000..1b81f8f06
--- /dev/null
+++ b/src/input/Registry.hxx
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2003-2014 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_HXX
+#define MPD_INPUT_REGISTRY_HXX
+
+#include "check.h"
+
+/**
+ * NULL terminated list of all input plugins which were enabled at
+ * compile time.
+ */
+extern const struct InputPlugin *const input_plugins[];
+
+extern bool input_plugins_enabled[];
+
+#define input_plugins_for_each(plugin) \
+ for (const InputPlugin *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/RewindInputPlugin.cxx b/src/input/RewindInputPlugin.cxx
deleted file mode 100644
index e11f56631..000000000
--- a/src/input/RewindInputPlugin.cxx
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * 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 "InputStream.hxx"
-#include "InputPlugin.hxx"
-#include "tag/Tag.hxx"
-
-#include <assert.h>
-#include <string.h>
-#include <stdio.h>
-
-extern const InputPlugin rewind_input_plugin;
-
-struct RewindInputStream {
- InputStream base;
-
- InputStream *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(InputStream *_input)
- :base(rewind_input_plugin, _input->uri.c_str(),
- _input->mutex, _input->cond),
- input(_input), tail(0) {
- }
-
- ~RewindInputStream() {
- input->Close();
- }
-
- /**
- * 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() {
- InputStream *dest = &base;
- const InputStream *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(InputStream *is)
-{
- RewindInputStream *r = (RewindInputStream *)is;
-
- delete r;
-}
-
-static bool
-input_rewind_check(InputStream *is, Error &error)
-{
- RewindInputStream *r = (RewindInputStream *)is;
-
- return r->input->Check(error);
-}
-
-static void
-input_rewind_update(InputStream *is)
-{
- RewindInputStream *r = (RewindInputStream *)is;
-
- if (!r->ReadingFromBuffer())
- r->CopyAttributes();
-}
-
-static Tag *
-input_rewind_tag(InputStream *is)
-{
- RewindInputStream *r = (RewindInputStream *)is;
-
- return r->input->ReadTag();
-}
-
-static bool
-input_rewind_available(InputStream *is)
-{
- RewindInputStream *r = (RewindInputStream *)is;
-
- return r->input->IsAvailable();
-}
-
-static size_t
-input_rewind_read(InputStream *is, void *ptr, size_t size,
- Error &error)
-{
- 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 = r->input->Read(ptr, size, error);
-
- if (r->input->offset > (InputPlugin::offset_type)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(InputStream *is)
-{
- RewindInputStream *r = (RewindInputStream *)is;
-
- return !r->ReadingFromBuffer() && r->input->IsEOF();
-}
-
-static bool
-input_rewind_seek(InputStream *is, InputPlugin::offset_type offset,
- int whence,
- Error &error)
-{
- RewindInputStream *r = (RewindInputStream *)is;
-
- assert(is->ready);
-
- if (whence == SEEK_SET && r->tail > 0 &&
- offset <= (InputPlugin::offset_type)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 = r->input->Seek(offset, whence, error);
- r->CopyAttributes();
-
- /* disable the buffer, because r->input has left the
- buffered range now */
- r->tail = 0;
-
- return success;
- }
-}
-
-const InputPlugin 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,
-};
-
-InputStream *
-input_rewind_open(InputStream *is)
-{
- assert(is != nullptr);
- 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
deleted file mode 100644
index 2d461970a..000000000
--- a/src/input/RewindInputPlugin.hxx
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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 InputStream;
-
-InputStream *
-input_rewind_open(InputStream *is);
-
-#endif
diff --git a/src/input/TextInputStream.cxx b/src/input/TextInputStream.cxx
new file mode 100644
index 000000000..5a8dcc065
--- /dev/null
+++ b/src/input/TextInputStream.cxx
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2003-2014 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 "TextInputStream.hxx"
+#include "InputStream.hxx"
+#include "util/Error.hxx"
+#include "util/TextFile.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+
+char *
+TextInputStream::ReadLine()
+{
+ char *line = ReadBufferedLine(buffer);
+ if (line != nullptr)
+ return line;
+
+ buffer.Shift();
+
+ while (true) {
+ auto dest = buffer.Write();
+ if (dest.size < 2) {
+ /* line too long: terminate the current
+ line */
+
+ assert(!dest.IsEmpty());
+ dest[0] = 0;
+ line = buffer.Read().data;
+ buffer.Clear();
+ return line;
+ }
+
+ /* reserve one byte for the null terminator if the
+ last line is not terminated by a newline
+ character */
+ --dest.size;
+
+ Error error;
+ size_t nbytes = is.LockRead(dest.data, dest.size, error);
+ if (nbytes > 0)
+ buffer.Append(nbytes);
+ else if (error.IsDefined()) {
+ LogError(error);
+ return nullptr;
+ }
+
+ line = ReadBufferedLine(buffer);
+ if (line != nullptr)
+ return line;
+
+ if (nbytes == 0) {
+ /* end of file: see if there's an unterminated
+ line */
+
+ dest = buffer.Write();
+ assert(!dest.IsEmpty());
+ dest[0] = 0;
+
+ auto r = buffer.Read();
+ buffer.Clear();
+ return r.IsEmpty()
+ ? nullptr
+ : r.data;
+ }
+ }
+}
diff --git a/src/input/TextInputStream.hxx b/src/input/TextInputStream.hxx
new file mode 100644
index 000000000..6f39d22cf
--- /dev/null
+++ b/src/input/TextInputStream.hxx
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2003-2014 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_TEXT_INPUT_STREAM_HXX
+#define MPD_TEXT_INPUT_STREAM_HXX
+
+#include "util/StaticFifoBuffer.hxx"
+
+class InputStream;
+
+class TextInputStream {
+ InputStream &is;
+ StaticFifoBuffer<char, 4096> buffer;
+
+public:
+ /**
+ * Wraps an existing #input_stream object into a #TextInputStream,
+ * to read its contents as text lines.
+ *
+ * @param _is an open #input_stream object
+ */
+ explicit TextInputStream(InputStream &_is)
+ :is(_is) {}
+
+ TextInputStream(const TextInputStream &) = delete;
+ TextInputStream& operator=(const TextInputStream &) = delete;
+
+ /**
+ * Reads the next line from the stream with newline character stripped.
+ *
+ * @return a pointer to the line, or nullptr on end-of-file or error
+ */
+ char *ReadLine();
+};
+
+#endif
diff --git a/src/input/ThreadInputStream.cxx b/src/input/ThreadInputStream.cxx
new file mode 100644
index 000000000..235ed2b01
--- /dev/null
+++ b/src/input/ThreadInputStream.cxx
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2003-2014 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 "ThreadInputStream.hxx"
+#include "thread/Name.hxx"
+#include "util/CircularBuffer.hxx"
+#include "util/HugeAllocator.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+ThreadInputStream::~ThreadInputStream()
+{
+ Lock();
+ close = true;
+ wake_cond.signal();
+ Unlock();
+
+ Cancel();
+
+ thread.Join();
+
+ if (buffer != nullptr) {
+ buffer->Clear();
+ HugeFree(buffer->Write().data, buffer_size);
+ delete buffer;
+ }
+}
+
+InputStream *
+ThreadInputStream::Start(Error &error)
+{
+ assert(buffer == nullptr);
+
+ void *p = HugeAllocate(buffer_size);
+ if (p == nullptr) {
+ error.SetErrno();
+ return nullptr;
+ }
+
+ buffer = new CircularBuffer<uint8_t>((uint8_t *)p, buffer_size);
+
+ if (!thread.Start(ThreadFunc, this, error))
+ return nullptr;
+
+ return this;
+}
+
+inline void
+ThreadInputStream::ThreadFunc()
+{
+ FormatThreadName("input:%s", plugin);
+
+ Lock();
+ if (!Open(postponed_error)) {
+ cond.broadcast();
+ Unlock();
+ return;
+ }
+
+ /* we're ready, tell it to our client */
+ SetReady();
+
+ while (!close) {
+ assert(!postponed_error.IsDefined());
+
+ auto w = buffer->Write();
+ if (w.IsEmpty()) {
+ wake_cond.wait(mutex);
+ } else {
+ Unlock();
+
+ Error error;
+ size_t nbytes = ThreadRead(w.data, w.size, error);
+
+ Lock();
+ cond.broadcast();
+
+ if (nbytes == 0) {
+ eof = true;
+ postponed_error = std::move(error);
+ break;
+ }
+
+ buffer->Append(nbytes);
+ }
+ }
+
+ Unlock();
+
+ Close();
+}
+
+void
+ThreadInputStream::ThreadFunc(void *ctx)
+{
+ ThreadInputStream &tis = *(ThreadInputStream *)ctx;
+ tis.ThreadFunc();
+}
+
+bool
+ThreadInputStream::Check(Error &error)
+{
+ assert(!thread.IsInside());
+
+ if (postponed_error.IsDefined()) {
+ error = std::move(postponed_error);
+ return false;
+ }
+
+ return true;
+}
+
+bool
+ThreadInputStream::IsAvailable()
+{
+ assert(!thread.IsInside());
+
+ return !buffer->IsEmpty() || eof || postponed_error.IsDefined();
+}
+
+inline size_t
+ThreadInputStream::Read(void *ptr, size_t read_size, Error &error)
+{
+ assert(!thread.IsInside());
+
+ while (true) {
+ if (postponed_error.IsDefined()) {
+ error = std::move(postponed_error);
+ return 0;
+ }
+
+ auto r = buffer->Read();
+ if (!r.IsEmpty()) {
+ size_t nbytes = std::min(read_size, r.size);
+ memcpy(ptr, r.data, nbytes);
+ buffer->Consume(nbytes);
+ wake_cond.broadcast();
+ offset += nbytes;
+ return nbytes;
+ }
+
+ if (eof)
+ return 0;
+
+ cond.wait(mutex);
+ }
+}
+
+bool
+ThreadInputStream::IsEOF()
+{
+ assert(!thread.IsInside());
+
+ return eof;
+}
diff --git a/src/input/ThreadInputStream.hxx b/src/input/ThreadInputStream.hxx
new file mode 100644
index 000000000..c6ac7669c
--- /dev/null
+++ b/src/input/ThreadInputStream.hxx
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2003-2014 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_THREAD_INPUT_STREAM_HXX
+#define MPD_THREAD_INPUT_STREAM_HXX
+
+#include "check.h"
+#include "InputStream.hxx"
+#include "thread/Thread.hxx"
+#include "thread/Cond.hxx"
+#include "util/Error.hxx"
+
+#include <stdint.h>
+
+template<typename T> class CircularBuffer;
+
+/**
+ * Helper class for moving InputStream implementations with blocking
+ * backend library implementation to a dedicated thread. Data is
+ * being read into a ring buffer, and that buffer is then consumed by
+ * another thread using the regular #InputStream API. This class
+ * manages the thread and the buffer.
+ *
+ * This works only for "streams": unknown length, no seeking, no tags.
+ */
+class ThreadInputStream : public InputStream {
+ const char *const plugin;
+
+ Thread thread;
+
+ /**
+ * Signalled when the thread shall be woken up: when data from
+ * the buffer has been consumed and when the stream shall be
+ * closed.
+ */
+ Cond wake_cond;
+
+ Error postponed_error;
+
+ const size_t buffer_size;
+ CircularBuffer<uint8_t> *buffer;
+
+ /**
+ * Shall the stream be closed?
+ */
+ bool close;
+
+ /**
+ * Has the end of the stream been seen by the thread?
+ */
+ bool eof;
+
+public:
+ ThreadInputStream(const char *_plugin,
+ const char *_uri, Mutex &_mutex, Cond &_cond,
+ size_t _buffer_size)
+ :InputStream(_uri, _mutex, _cond),
+ plugin(_plugin),
+ buffer_size(_buffer_size),
+ buffer(nullptr),
+ close(false), eof(false) {}
+
+ virtual ~ThreadInputStream();
+
+ /**
+ * Initialize the object and start the thread.
+ *
+ * @return false on error
+ */
+ InputStream *Start(Error &error);
+
+ /* virtual methods from InputStream */
+ bool Check(Error &error) override final;
+ bool IsEOF() override final;
+ bool IsAvailable() override final;
+ size_t Read(void *ptr, size_t size, Error &error) override final;
+
+protected:
+ void SetMimeType(const char *_mime) {
+ assert(thread.IsInside());
+
+ InputStream::SetMimeType(_mime);
+ }
+
+ /* to be implemented by the plugin */
+
+ /**
+ * Optional initialization after entering the thread. After
+ * this returns with success, the InputStream::ready flag is
+ * set.
+ *
+ * The #InputStream is locked. Unlock/relock it if you do a
+ * blocking operation.
+ */
+ virtual bool Open(gcc_unused Error &error) {
+ return true;
+ }
+
+ /**
+ * Read from the stream.
+ *
+ * The #InputStream is not locked.
+ *
+ * @return 0 on end-of-file or on error
+ */
+ virtual size_t ThreadRead(void *ptr, size_t size, Error &error) = 0;
+
+ /**
+ * Optional deinitialization before leaving the thread.
+ *
+ * The #InputStream is not locked.
+ */
+ virtual void Close() {}
+
+ /**
+ * Called from the client thread to cancel a Read() inside the
+ * thread.
+ *
+ * The #InputStream is not locked.
+ */
+ virtual void Cancel() {}
+
+private:
+ void ThreadFunc();
+ static void ThreadFunc(void *ctx);
+};
+
+#endif
diff --git a/src/input/plugins/AlsaInputPlugin.cxx b/src/input/plugins/AlsaInputPlugin.cxx
new file mode 100644
index 000000000..f03f745c6
--- /dev/null
+++ b/src/input/plugins/AlsaInputPlugin.cxx
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2003-2014 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.
+ */
+
+/*
+ * ALSA code based on an example by Paul Davis released under GPL here:
+ * http://equalarea.com/paul/alsa-audio.html
+ * and one by Matthias Nagorni, also GPL, here:
+ * http://alsamodular.sourceforge.net/alsa_programming_howto.html
+ */
+
+#include "config.h"
+#include "AlsaInputPlugin.hxx"
+#include "../InputPlugin.hxx"
+#include "../InputStream.hxx"
+#include "util/Domain.hxx"
+#include "util/Error.hxx"
+#include "util/StringUtil.hxx"
+#include "util/ReusableArray.hxx"
+
+#include "Log.hxx"
+#include "event/MultiSocketMonitor.hxx"
+#include "event/DeferredMonitor.hxx"
+#include "event/Call.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "IOThread.hxx"
+
+#include <alsa/asoundlib.h>
+
+#include <atomic>
+
+#include <assert.h>
+#include <string.h>
+
+static constexpr Domain alsa_input_domain("alsa");
+
+static constexpr const char *default_device = "hw:0,0";
+
+// the following defaults are because the PcmDecoderPlugin forces CD format
+static constexpr snd_pcm_format_t default_format = SND_PCM_FORMAT_S16;
+static constexpr int default_channels = 2; // stereo
+static constexpr unsigned int default_rate = 44100; // cd quality
+
+/**
+ * This value should be the same as the read buffer size defined in
+ * PcmDecoderPlugin.cxx:pcm_stream_decode().
+ * We use it to calculate how many audio frames to buffer in the alsa driver
+ * before reading from the device. snd_pcm_readi() blocks until that many
+ * frames are ready.
+ */
+static constexpr size_t read_buffer_size = 4096;
+
+class AlsaInputStream final
+ : public InputStream,
+ MultiSocketMonitor, DeferredMonitor {
+ snd_pcm_t *capture_handle;
+ size_t frame_size;
+ int frames_to_read;
+ bool eof;
+
+ /**
+ * Is somebody waiting for data? This is set by method
+ * Available().
+ */
+ std::atomic_bool waiting;
+
+ ReusableArray<pollfd> pfd_buffer;
+
+public:
+ AlsaInputStream(EventLoop &loop,
+ const char *_uri, Mutex &_mutex, Cond &_cond,
+ snd_pcm_t *_handle, int _frame_size)
+ :InputStream(_uri, _mutex, _cond),
+ MultiSocketMonitor(loop),
+ DeferredMonitor(loop),
+ capture_handle(_handle),
+ frame_size(_frame_size),
+ eof(false)
+ {
+ assert(_uri != nullptr);
+ assert(_handle != nullptr);
+
+ /* this mime type forces use of the PcmDecoderPlugin.
+ Needs to be generalised when/if that decoder is
+ updated to support other audio formats */
+ SetMimeType("audio/x-mpd-cdda-pcm");
+ InputStream::SetReady();
+
+ frames_to_read = read_buffer_size / frame_size;
+
+ snd_pcm_start(capture_handle);
+
+ DeferredMonitor::Schedule();
+ }
+
+ ~AlsaInputStream() {
+ snd_pcm_close(capture_handle);
+ }
+
+ using DeferredMonitor::GetEventLoop;
+
+ static InputStream *Create(const char *uri, Mutex &mutex, Cond &cond,
+ Error &error);
+
+ /* virtual methods from InputStream */
+
+ bool IsEOF() override {
+ return eof;
+ }
+
+ bool IsAvailable() override {
+ if (snd_pcm_avail(capture_handle) > frames_to_read)
+ return true;
+
+ if (!waiting.exchange(true))
+ SafeInvalidateSockets();
+
+ return false;
+ }
+
+ size_t Read(void *ptr, size_t size, Error &error) override;
+
+private:
+ static snd_pcm_t *OpenDevice(const char *device, int rate,
+ snd_pcm_format_t format, int channels,
+ Error &error);
+
+ int Recover(int err);
+
+ void SafeInvalidateSockets() {
+ DeferredMonitor::Schedule();
+ }
+
+ virtual void RunDeferred() override {
+ InvalidateSockets();
+ }
+
+ virtual int PrepareSockets() override;
+ virtual void DispatchSockets() override;
+};
+
+inline InputStream *
+AlsaInputStream::Create(const char *uri, Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ const char *const scheme = "alsa://";
+ if (!StringStartsWith(uri, scheme))
+ return nullptr;
+
+ const char *device = uri + strlen(scheme);
+ if (strlen(device) == 0)
+ device = default_device;
+
+ /* placeholders - eventually user-requested audio format will
+ be passed via the URI. For now we just force the
+ defaults */
+ int rate = default_rate;
+ snd_pcm_format_t format = default_format;
+ int channels = default_channels;
+
+ snd_pcm_t *handle = OpenDevice(device, rate, format, channels,
+ error);
+ if (handle == nullptr)
+ return nullptr;
+
+ int frame_size = snd_pcm_format_width(format) / 8 * channels;
+ return new AlsaInputStream(io_thread_get(),
+ uri, mutex, cond,
+ handle, frame_size);
+}
+
+size_t
+AlsaInputStream::Read(void *ptr, size_t read_size, Error &error)
+{
+ assert(ptr != nullptr);
+
+ int num_frames = read_size / frame_size;
+ int ret;
+ while ((ret = snd_pcm_readi(capture_handle, ptr, num_frames)) < 0) {
+ if (Recover(ret) < 0) {
+ eof = true;
+ error.Format(alsa_input_domain,
+ "PCM error - stream aborted");
+ return 0;
+ }
+ }
+
+ size_t nbytes = ret * frame_size;
+ offset += nbytes;
+ return nbytes;
+}
+
+int
+AlsaInputStream::PrepareSockets()
+{
+ if (!waiting) {
+ ClearSocketList();
+ return -1;
+ }
+
+ int count = snd_pcm_poll_descriptors_count(capture_handle);
+ if (count < 0) {
+ ClearSocketList();
+ return -1;
+ }
+
+ struct pollfd *pfds = pfd_buffer.Get(count);
+
+ count = snd_pcm_poll_descriptors(capture_handle, pfds, count);
+ if (count < 0)
+ count = 0;
+
+ ReplaceSocketList(pfds, count);
+ return -1;
+}
+
+void
+AlsaInputStream::DispatchSockets()
+{
+ waiting = false;
+
+ const ScopeLock protect(mutex);
+ /* wake up the thread that is waiting for more data */
+ cond.broadcast();
+}
+
+inline int
+AlsaInputStream::Recover(int err)
+{
+ switch(err) {
+ case -EPIPE:
+ LogDebug(alsa_input_domain, "Buffer Overrun");
+ // drop through
+ case -ESTRPIPE:
+ case -EINTR:
+ err = snd_pcm_recover(capture_handle, err, 1);
+ break;
+ default:
+ // something broken somewhere, give up
+ err = -1;
+ }
+ return err;
+}
+
+inline snd_pcm_t *
+AlsaInputStream::OpenDevice(const char *device,
+ int rate, snd_pcm_format_t format, int channels,
+ Error &error)
+{
+ snd_pcm_t *capture_handle;
+ int err;
+ if ((err = snd_pcm_open(&capture_handle, device,
+ SND_PCM_STREAM_CAPTURE, 0)) < 0) {
+ error.Format(alsa_input_domain, "Failed to open device: %s (%s)", device, snd_strerror(err));
+ return nullptr;
+ }
+
+ snd_pcm_hw_params_t *hw_params;
+ if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
+ error.Format(alsa_input_domain, "Cannot allocate hardware parameter structure (%s)", snd_strerror(err));
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0) {
+ error.Format(alsa_input_domain, "Cannot initialize hardware parameter structure (%s)", snd_strerror(err));
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
+ error.Format(alsa_input_domain, "Cannot set access type (%s)", snd_strerror (err));
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, format)) < 0) {
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ error.Format(alsa_input_domain, "Cannot set sample format (%s)", snd_strerror (err));
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, channels)) < 0) {
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ error.Format(alsa_input_domain, "Cannot set channels (%s)", snd_strerror (err));
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_hw_params_set_rate(capture_handle, hw_params, rate, 0)) < 0) {
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ error.Format(alsa_input_domain, "Cannot set sample rate (%s)", snd_strerror (err));
+ return nullptr;
+ }
+
+ /* period needs to be big enough so that poll() doesn't fire too often,
+ * but small enough that buffer overruns don't occur if Read() is not
+ * invoked often enough.
+ * the calculation here is empirical; however all measurements were
+ * done using 44100:16:2. When we extend this plugin to support
+ * other audio formats then this may need to be revisited */
+ snd_pcm_uframes_t period = read_buffer_size * 2;
+ int direction = -1;
+ if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params,
+ &period, &direction)) < 0) {
+ error.Format(alsa_input_domain, "Cannot set period size (%s)",
+ snd_strerror(err));
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0) {
+ error.Format(alsa_input_domain, "Cannot set parameters (%s)",
+ snd_strerror(err));
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ snd_pcm_hw_params_free (hw_params);
+
+ snd_pcm_sw_params_t *sw_params;
+
+ snd_pcm_sw_params_malloc(&sw_params);
+ snd_pcm_sw_params_current(capture_handle, sw_params);
+
+ if ((err = snd_pcm_sw_params_set_start_threshold(capture_handle, sw_params,
+ period)) < 0) {
+ error.Format(alsa_input_domain,
+ "unable to set start threshold (%s)", snd_strerror(err));
+ snd_pcm_sw_params_free(sw_params);
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_sw_params(capture_handle, sw_params)) < 0) {
+ error.Format(alsa_input_domain,
+ "unable to install sw params (%s)", snd_strerror(err));
+ snd_pcm_sw_params_free(sw_params);
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ snd_pcm_sw_params_free(sw_params);
+
+ snd_pcm_prepare(capture_handle);
+
+ return capture_handle;
+}
+
+/*######################### Plugin Functions ##############################*/
+
+static InputStream *
+alsa_input_open(const char *uri, Mutex &mutex, Cond &cond, Error &error)
+{
+ return AlsaInputStream::Create(uri, mutex, cond, error);
+}
+
+const struct InputPlugin input_plugin_alsa = {
+ "alsa",
+ nullptr,
+ nullptr,
+ alsa_input_open,
+};
diff --git a/src/input/plugins/AlsaInputPlugin.hxx b/src/input/plugins/AlsaInputPlugin.hxx
new file mode 100644
index 000000000..dddf7dfd7
--- /dev/null
+++ b/src/input/plugins/AlsaInputPlugin.hxx
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2003-2014 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_ALSA_INPUT_PLUGIN_HXX
+#define MPD_ALSA_INPUT_PLUGIN_HXX
+
+#include "../InputPlugin.hxx"
+
+extern const struct InputPlugin input_plugin_alsa;
+
+
+#endif
diff --git a/src/input/plugins/ArchiveInputPlugin.cxx b/src/input/plugins/ArchiveInputPlugin.cxx
new file mode 100644
index 000000000..da3d7ca71
--- /dev/null
+++ b/src/input/plugins/ArchiveInputPlugin.cxx
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2003-2014 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 "archive/ArchiveDomain.hxx"
+#include "archive/ArchiveLookup.hxx"
+#include "archive/ArchiveList.hxx"
+#include "archive/ArchivePlugin.hxx"
+#include "archive/ArchiveFile.hxx"
+#include "../InputPlugin.hxx"
+#include "fs/Traits.hxx"
+#include "fs/Path.hxx"
+#include "util/Alloc.hxx"
+#include "Log.hxx"
+
+#include <stdlib.h>
+
+InputStream *
+OpenArchiveInputStream(Path path, Mutex &mutex, Cond &cond, Error &error)
+{
+ const ArchivePlugin *arplug;
+ InputStream *is;
+
+ char *pname = strdup(path.c_str());
+ // archive_lookup will modify pname when true is returned
+ const char *archive, *filename, *suffix;
+ if (!archive_lookup(pname, &archive, &filename, &suffix)) {
+ FormatDebug(archive_domain,
+ "not an archive, lookup %s failed", pname);
+ free(pname);
+ return nullptr;
+ }
+
+ //check which archive plugin to use (by ext)
+ arplug = archive_plugin_from_suffix(suffix);
+ if (!arplug) {
+ FormatWarning(archive_domain,
+ "can't handle archive %s", archive);
+ free(pname);
+ return nullptr;
+ }
+
+ auto file = archive_file_open(arplug, Path::FromFS(archive), error);
+ if (file == nullptr) {
+ free(pname);
+ return nullptr;
+ }
+
+ //setup fileops
+ is = file->OpenStream(filename, mutex, cond, error);
+ free(pname);
+ file->Close();
+
+ return is;
+}
+
+static InputStream *
+input_archive_open(gcc_unused const char *filename,
+ gcc_unused Mutex &mutex, gcc_unused Cond &cond,
+ gcc_unused Error &error)
+{
+ /* dummy method; use OpenArchiveInputStream() instead */
+
+ return nullptr;
+}
+
+const InputPlugin input_plugin_archive = {
+ "archive",
+ nullptr,
+ nullptr,
+ input_archive_open,
+};
diff --git a/src/input/plugins/ArchiveInputPlugin.hxx b/src/input/plugins/ArchiveInputPlugin.hxx
new file mode 100644
index 000000000..b6158684a
--- /dev/null
+++ b/src/input/plugins/ArchiveInputPlugin.hxx
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2003-2014 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
+
+class InputStream;
+class Path;
+class Mutex;
+class Cond;
+class Error;
+
+extern const struct InputPlugin input_plugin_archive;
+
+InputStream *
+OpenArchiveInputStream(Path path, Mutex &mutex, Cond &cond, Error &error);
+
+#endif
diff --git a/src/input/plugins/CdioParanoiaInputPlugin.cxx b/src/input/plugins/CdioParanoiaInputPlugin.cxx
new file mode 100644
index 000000000..f847b35c1
--- /dev/null
+++ b/src/input/plugins/CdioParanoiaInputPlugin.cxx
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2003-2014 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 "../InputStream.hxx"
+#include "../InputPlugin.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "system/ByteOrder.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "Log.hxx"
+#include "config/ConfigData.hxx"
+#include "config/ConfigError.hxx"
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+#include <glib.h>
+#include <assert.h>
+
+#ifdef HAVE_CDIO_PARANOIA_PARANOIA_H
+#include <cdio/paranoia/paranoia.h>
+#else
+#include <cdio/paranoia.h>
+#endif
+
+#include <cdio/cd_types.h>
+
+class CdioParanoiaInputStream final : public InputStream {
+ cdrom_drive_t *const drv;
+ CdIo_t *const cdio;
+ cdrom_paranoia_t *const para;
+
+ const lsn_t lsn_from, lsn_to;
+ int lsn_relofs;
+
+ char buffer[CDIO_CD_FRAMESIZE_RAW];
+ int buffer_lsn;
+
+ public:
+ CdioParanoiaInputStream(const char *_uri, Mutex &_mutex, Cond &_cond,
+ cdrom_drive_t *_drv, CdIo_t *_cdio,
+ bool reverse_endian,
+ lsn_t _lsn_from, lsn_t _lsn_to)
+ :InputStream(_uri, _mutex, _cond),
+ drv(_drv), cdio(_cdio), para(cdio_paranoia_init(drv)),
+ lsn_from(_lsn_from), lsn_to(_lsn_to),
+ lsn_relofs(0),
+ buffer_lsn(-1)
+ {
+ /* Set reading mode for full paranoia, but allow
+ skipping sectors. */
+ paranoia_modeset(para,
+ PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP);
+
+ /* seek to beginning of the track */
+ cdio_paranoia_seek(para, lsn_from, SEEK_SET);
+
+ seekable = true;
+ size = (lsn_to - lsn_from + 1) * CDIO_CD_FRAMESIZE_RAW;
+
+ /* hack to make MPD select the "pcm" decoder plugin */
+ SetMimeType(reverse_endian
+ ? "audio/x-mpd-cdda-pcm-reverse"
+ : "audio/x-mpd-cdda-pcm");
+ SetReady();
+ }
+
+ ~CdioParanoiaInputStream() {
+ cdio_paranoia_free(para);
+ cdio_cddap_close_no_free_cdio(drv);
+ cdio_destroy(cdio);
+ }
+
+ /* virtual methods from InputStream */
+ bool IsEOF() override;
+ size_t Read(void *ptr, size_t size, Error &error) override;
+ bool Seek(offset_type offset, Error &error) override;
+};
+
+static constexpr Domain cdio_domain("cdio");
+
+static bool default_reverse_endian;
+
+static InputPlugin::InitResult
+input_cdio_init(const config_param &param, Error &error)
+{
+ const char *value = param.GetBlockValue("default_byte_order");
+ if (value != nullptr) {
+ if (strcmp(value, "little_endian") == 0)
+ default_reverse_endian = IsBigEndian();
+ else if (strcmp(value, "big_endian") == 0)
+ default_reverse_endian = IsLittleEndian();
+ else {
+ error.Format(config_domain, 0,
+ "Unrecognized 'default_byte_order' setting: %s",
+ value);
+ return InputPlugin::InitResult::ERROR;
+ }
+ }
+
+ return InputPlugin::InitResult::SUCCESS;
+}
+
+struct cdio_uri {
+ char device[64];
+ int track;
+};
+
+static bool
+parse_cdio_uri(struct cdio_uri *dest, const char *src, Error &error)
+{
+ if (!StringStartsWith(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) {
+ error.Set(cdio_domain, "Malformed track number");
+ return false;
+ }
+
+ if (endptr == track)
+ /* play the whole CD */
+ dest->track = -1;
+
+ return true;
+}
+
+static AllocatedPath
+cdio_detect_device(void)
+{
+ char **devices = cdio_get_devices_with_cap(nullptr, CDIO_FS_AUDIO,
+ false);
+ if (devices == nullptr)
+ return AllocatedPath::Null();
+
+ AllocatedPath path = AllocatedPath::FromFS(devices[0]);
+ cdio_free_device_list(devices);
+ return path;
+}
+
+static InputStream *
+input_cdio_open(const char *uri,
+ Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ struct cdio_uri parsed_uri;
+ if (!parse_cdio_uri(&parsed_uri, uri, error))
+ return nullptr;
+
+ /* get list of CD's supporting CD-DA */
+ const AllocatedPath device = parsed_uri.device[0] != 0
+ ? AllocatedPath::FromFS(parsed_uri.device)
+ : cdio_detect_device();
+ if (device.IsNull()) {
+ error.Set(cdio_domain,
+ "Unable find or access a CD-ROM drive with an audio CD in it.");
+ return nullptr;
+ }
+
+ /* Found such a CD-ROM with a CD-DA loaded. Use the first drive in the list. */
+ const auto cdio = cdio_open(device.c_str(), DRIVER_UNKNOWN);
+ if (cdio == nullptr) {
+ error.Set(cdio_domain, "Failed to open CD drive");
+ return nullptr;
+ }
+
+ const auto drv = cdio_cddap_identify_cdio(cdio, 1, nullptr);
+ if (drv == nullptr) {
+ error.Set(cdio_domain, "Unable to identify audio CD disc.");
+ cdio_destroy(cdio);
+ return nullptr;
+ }
+
+ cdda_verbose_set(drv, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT);
+
+ if (0 != cdio_cddap_open(drv)) {
+ cdio_cddap_close_no_free_cdio(drv);
+ cdio_destroy(cdio);
+ error.Set(cdio_domain, "Unable to open disc.");
+ return nullptr;
+ }
+
+ bool reverse_endian;
+ switch (data_bigendianp(drv)) {
+ case -1:
+ LogDebug(cdio_domain, "drive returns unknown audio data");
+ reverse_endian = default_reverse_endian;
+ break;
+
+ case 0:
+ LogDebug(cdio_domain, "drive returns audio data Little Endian");
+ reverse_endian = IsBigEndian();
+ break;
+
+ case 1:
+ LogDebug(cdio_domain, "drive returns audio data Big Endian");
+ reverse_endian = IsLittleEndian();
+ break;
+
+ default:
+ error.Format(cdio_domain, "Drive returns unknown data type %d",
+ data_bigendianp(drv));
+ cdio_cddap_close_no_free_cdio(drv);
+ cdio_destroy(cdio);
+ return nullptr;
+ }
+
+ lsn_t lsn_from, lsn_to;
+ if (parsed_uri.track >= 0) {
+ lsn_from = cdio_get_track_lsn(cdio, parsed_uri.track);
+ lsn_to = cdio_get_track_last_lsn(cdio, parsed_uri.track);
+ } else {
+ lsn_from = 0;
+ lsn_to = cdio_get_disc_last_lsn(cdio);
+ }
+
+ return new CdioParanoiaInputStream(uri, mutex, cond,
+ drv, cdio, reverse_endian,
+ lsn_from, lsn_to);
+}
+
+bool
+CdioParanoiaInputStream::Seek(offset_type new_offset, Error &error)
+{
+ if (new_offset > size) {
+ error.Format(cdio_domain, "Invalid offset to seek %ld (%ld)",
+ (long int)new_offset, (long int)size);
+ return false;
+ }
+
+ /* simple case */
+ if (new_offset == offset)
+ return true;
+
+ /* calculate current LSN */
+ lsn_relofs = new_offset / CDIO_CD_FRAMESIZE_RAW;
+ offset = new_offset;
+
+ cdio_paranoia_seek(para, lsn_from + lsn_relofs, SEEK_SET);
+
+ return true;
+}
+
+size_t
+CdioParanoiaInputStream::Read(void *ptr, size_t length, Error &error)
+{
+ 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 (lsn_from + lsn_relofs > lsn_to)
+ break;
+
+ //current sector was changed ?
+ if (lsn_relofs != buffer_lsn) {
+ rbuf = cdio_paranoia_read(para, nullptr);
+
+ s_err = cdda_errors(drv);
+ if (s_err) {
+ FormatError(cdio_domain,
+ "paranoia_read: %s", s_err);
+ free(s_err);
+ }
+ s_mess = cdda_messages(drv);
+ if (s_mess) {
+ free(s_mess);
+ }
+ if (!rbuf) {
+ error.Set(cdio_domain,
+ "paranoia read error. Stopping.");
+ return 0;
+ }
+ //store current buffer
+ memcpy(buffer, rbuf, CDIO_CD_FRAMESIZE_RAW);
+ buffer_lsn = lsn_relofs;
+ } else {
+ //use cached sector
+ rbuf = (int16_t *)buffer;
+ }
+
+ //correct offset
+ diff = offset - 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
+ offset += len;
+ lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW;
+ //update length
+ length -= len;
+ }
+
+ return nbytes;
+}
+
+bool
+CdioParanoiaInputStream::IsEOF()
+{
+ return lsn_from + lsn_relofs > lsn_to;
+}
+
+const InputPlugin input_plugin_cdio_paranoia = {
+ "cdio_paranoia",
+ input_cdio_init,
+ nullptr,
+ input_cdio_open,
+};
diff --git a/src/input/plugins/CdioParanoiaInputPlugin.hxx b/src/input/plugins/CdioParanoiaInputPlugin.hxx
new file mode 100644
index 000000000..e2804e8c7
--- /dev/null
+++ b/src/input/plugins/CdioParanoiaInputPlugin.hxx
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2003-2014 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 InputPlugin input_plugin_cdio_paranoia;
+
+#endif
diff --git a/src/input/plugins/CurlInputPlugin.cxx b/src/input/plugins/CurlInputPlugin.cxx
new file mode 100644
index 000000000..abb7e312c
--- /dev/null
+++ b/src/input/plugins/CurlInputPlugin.cxx
@@ -0,0 +1,876 @@
+/*
+ * Copyright (C) 2003-2014 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 "../AsyncInputStream.hxx"
+#include "../IcyInputStream.hxx"
+#include "../InputPlugin.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigData.hxx"
+#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
+#include "event/SocketMonitor.hxx"
+#include "event/TimeoutMonitor.hxx"
+#include "event/Call.hxx"
+#include "IOThread.hxx"
+#include "util/ASCII.hxx"
+#include "util/StringUtil.hxx"
+#include "util/NumberParser.hxx"
+#include "util/CircularBuffer.hxx"
+#include "util/HugeAllocator.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+#include <curl/curl.h>
+
+#if LIBCURL_VERSION_NUM < 0x071200
+#error libcurl is too old
+#endif
+
+/**
+ * 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;
+
+struct CurlInputStream final : public AsyncInputStream {
+ /* some buffers which were passed to libcurl, which we have
+ too free */
+ char range[32];
+ struct curl_slist *request_headers;
+
+ /** the curl handles */
+ CURL *easy;
+
+ /** error message provided by libcurl */
+ char error_buffer[CURL_ERROR_SIZE];
+
+ /** parser for icy-metadata */
+ IcyInputStream *icy;
+
+ CurlInputStream(const char *_url, Mutex &_mutex, Cond &_cond,
+ void *_buffer)
+ :AsyncInputStream(_url, _mutex, _cond,
+ _buffer, CURL_MAX_BUFFERED,
+ CURL_RESUME_AT),
+ request_headers(nullptr),
+ icy(new IcyInputStream(this)) {}
+
+ ~CurlInputStream();
+
+ CurlInputStream(const CurlInputStream &) = delete;
+ CurlInputStream &operator=(const CurlInputStream &) = delete;
+
+ static InputStream *Open(const char *url, Mutex &mutex, Cond &cond,
+ Error &error);
+
+ bool InitEasy(Error &error);
+
+ /**
+ * Frees the current "libcurl easy" handle, and everything
+ * associated with it.
+ *
+ * Runs in the I/O thread.
+ */
+ void FreeEasy();
+
+ /**
+ * Frees the current "libcurl easy" handle, and everything associated
+ * with it.
+ *
+ * The mutex must not be locked.
+ */
+ void FreeEasyIndirect();
+
+ /**
+ * Called when a new response begins. This is used to discard
+ * headers from previous responses (for example authentication
+ * and redirects).
+ */
+ void ResponseBoundary();
+
+ void HeaderReceived(const char *name, std::string &&value);
+
+ size_t DataReceived(const void *ptr, size_t size);
+
+ /**
+ * A HTTP request is finished.
+ *
+ * Runs in the I/O thread. The caller must not hold locks.
+ */
+ void RequestDone(CURLcode result, long status);
+
+ /* virtual methods from AsyncInputStream */
+ virtual void DoResume() override;
+ virtual void DoSeek(offset_type new_offset) override;
+};
+
+class CurlMulti;
+
+/**
+ * Monitor for one socket created by CURL.
+ */
+class CurlSocket final : SocketMonitor {
+ CurlMulti &multi;
+
+public:
+ CurlSocket(CurlMulti &_multi, EventLoop &_loop, int _fd)
+ :SocketMonitor(_fd, _loop), multi(_multi) {}
+
+ ~CurlSocket() {
+ /* TODO: sometimes, CURL uses CURL_POLL_REMOVE after
+ closing the socket, and sometimes, it uses
+ CURL_POLL_REMOVE just to move the (still open)
+ connection to the pool; in the first case,
+ Abandon() would be most appropriate, but it breaks
+ the second case - is that a CURL bug? is there a
+ better solution? */
+ }
+
+ /**
+ * Callback function for CURLMOPT_SOCKETFUNCTION.
+ */
+ static int SocketFunction(CURL *easy,
+ curl_socket_t s, int action,
+ void *userp, void *socketp);
+
+ virtual bool OnSocketReady(unsigned flags) override;
+
+private:
+ static constexpr int FlagsToCurlCSelect(unsigned flags) {
+ return (flags & (READ | HANGUP) ? CURL_CSELECT_IN : 0) |
+ (flags & WRITE ? CURL_CSELECT_OUT : 0) |
+ (flags & ERROR ? CURL_CSELECT_ERR : 0);
+ }
+
+ gcc_const
+ static unsigned CurlPollToFlags(int action) {
+ switch (action) {
+ case CURL_POLL_NONE:
+ return 0;
+
+ case CURL_POLL_IN:
+ return READ;
+
+ case CURL_POLL_OUT:
+ return WRITE;
+
+ case CURL_POLL_INOUT:
+ return READ|WRITE;
+ }
+
+ assert(false);
+ gcc_unreachable();
+ }
+};
+
+/**
+ * Manager for the global CURLM object.
+ */
+class CurlMulti final : private TimeoutMonitor {
+ CURLM *const multi;
+
+public:
+ CurlMulti(EventLoop &_loop, CURLM *_multi);
+
+ ~CurlMulti() {
+ curl_multi_cleanup(multi);
+ }
+
+ bool Add(CurlInputStream *c, Error &error);
+ void Remove(CurlInputStream *c);
+
+ /**
+ * Check for finished HTTP responses.
+ *
+ * Runs in the I/O thread. The caller must not hold locks.
+ */
+ void ReadInfo();
+
+ void Assign(curl_socket_t fd, CurlSocket &cs) {
+ curl_multi_assign(multi, fd, &cs);
+ }
+
+ void SocketAction(curl_socket_t fd, int ev_bitmask);
+
+ void InvalidateSockets() {
+ SocketAction(CURL_SOCKET_TIMEOUT, 0);
+ }
+
+ /**
+ * This is a kludge to allow pausing/resuming a stream with
+ * libcurl < 7.32.0. Read the curl_easy_pause manpage for
+ * more information.
+ */
+ void ResumeSockets() {
+ int running_handles;
+ curl_multi_socket_all(multi, &running_handles);
+ }
+
+private:
+ static int TimerFunction(CURLM *multi, long timeout_ms, void *userp);
+
+ virtual void OnTimeout() override;
+};
+
+/**
+ * libcurl version number encoded in a 24 bit integer.
+ */
+static unsigned curl_version_num;
+
+/** 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 bool verify_peer, verify_host;
+
+static CurlMulti *curl_multi;
+
+static constexpr Domain http_domain("http");
+static constexpr Domain curl_domain("curl");
+static constexpr Domain curlm_domain("curlm");
+
+CurlMulti::CurlMulti(EventLoop &_loop, CURLM *_multi)
+ :TimeoutMonitor(_loop), multi(_multi)
+{
+ curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION,
+ CurlSocket::SocketFunction);
+ curl_multi_setopt(multi, CURLMOPT_SOCKETDATA, this);
+
+ curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, TimerFunction);
+ curl_multi_setopt(multi, CURLMOPT_TIMERDATA, this);
+}
+
+/**
+ * Find a request by its CURL "easy" handle.
+ *
+ * Runs in the I/O thread. No lock needed.
+ */
+gcc_pure
+static CurlInputStream *
+input_curl_find_request(CURL *easy)
+{
+ assert(io_thread_inside());
+
+ void *p;
+ CURLcode code = curl_easy_getinfo(easy, CURLINFO_PRIVATE, &p);
+ if (code != CURLE_OK)
+ return nullptr;
+
+ return (CurlInputStream *)p;
+}
+
+void
+CurlInputStream::DoResume()
+{
+ assert(io_thread_inside());
+
+ mutex.unlock();
+
+ curl_easy_pause(easy, CURLPAUSE_CONT);
+
+ if (curl_version_num < 0x072000)
+ /* libcurl older than 7.32.0 does not update
+ its sockets after curl_easy_pause(); force
+ libcurl to do it now */
+ curl_multi->ResumeSockets();
+
+ curl_multi->InvalidateSockets();
+
+ mutex.lock();
+}
+
+int
+CurlSocket::SocketFunction(gcc_unused CURL *easy,
+ curl_socket_t s, int action,
+ void *userp, void *socketp) {
+ CurlMulti &multi = *(CurlMulti *)userp;
+ CurlSocket *cs = (CurlSocket *)socketp;
+
+ assert(io_thread_inside());
+
+ if (action == CURL_POLL_REMOVE) {
+ delete cs;
+ return 0;
+ }
+
+ if (cs == nullptr) {
+ cs = new CurlSocket(multi, io_thread_get(), s);
+ multi.Assign(s, *cs);
+ } else {
+#ifdef USE_EPOLL
+ /* when using epoll, we need to unregister the socket
+ each time this callback is invoked, because older
+ CURL versions may omit the CURL_POLL_REMOVE call
+ when the socket has been closed and recreated with
+ the same file number (bug found in CURL 7.26, CURL
+ 7.33 not affected); in that case, epoll refuses the
+ EPOLL_CTL_MOD because it does not know the new
+ socket yet */
+ cs->Cancel();
+#endif
+ }
+
+ unsigned flags = CurlPollToFlags(action);
+ if (flags != 0)
+ cs->Schedule(flags);
+ return 0;
+}
+
+bool
+CurlSocket::OnSocketReady(unsigned flags)
+{
+ assert(io_thread_inside());
+
+ multi.SocketAction(Get(), FlagsToCurlCSelect(flags));
+ return true;
+}
+
+/**
+ * Runs in the I/O thread. No lock needed.
+ */
+inline bool
+CurlMulti::Add(CurlInputStream *c, Error &error)
+{
+ assert(io_thread_inside());
+ assert(c != nullptr);
+ assert(c->easy != nullptr);
+
+ CURLMcode mcode = curl_multi_add_handle(multi, c->easy);
+ if (mcode != CURLM_OK) {
+ error.Format(curlm_domain, mcode,
+ "curl_multi_add_handle() failed: %s",
+ curl_multi_strerror(mcode));
+ return false;
+ }
+
+ InvalidateSockets();
+ return true;
+}
+
+/**
+ * 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(CurlInputStream *c, Error &error)
+{
+ assert(c != nullptr);
+ assert(c->easy != nullptr);
+
+ bool result;
+ BlockingCall(io_thread_get(), [c, &error, &result](){
+ result = curl_multi->Add(c, error);
+ });
+ return result;
+}
+
+inline void
+CurlMulti::Remove(CurlInputStream *c)
+{
+ curl_multi_remove_handle(multi, c->easy);
+}
+
+void
+CurlInputStream::FreeEasy()
+{
+ assert(io_thread_inside());
+
+ if (easy == nullptr)
+ return;
+
+ curl_multi->Remove(this);
+
+ curl_easy_cleanup(easy);
+ easy = nullptr;
+
+ curl_slist_free_all(request_headers);
+ request_headers = nullptr;
+}
+
+void
+CurlInputStream::FreeEasyIndirect()
+{
+ BlockingCall(io_thread_get(), [this](){
+ FreeEasy();
+ curl_multi->InvalidateSockets();
+ });
+
+ assert(easy == nullptr);
+}
+
+inline void
+CurlInputStream::RequestDone(CURLcode result, long status)
+{
+ assert(io_thread_inside());
+ assert(!postponed_error.IsDefined());
+
+ FreeEasy();
+ AsyncInputStream::SetClosed();
+
+ const ScopeLock protect(mutex);
+
+ if (result != CURLE_OK) {
+ postponed_error.Format(curl_domain, result,
+ "curl failed: %s", error_buffer);
+ } else if (status < 200 || status >= 300) {
+ postponed_error.Format(http_domain, status,
+ "got HTTP status %ld",
+ status);
+ }
+
+ if (IsSeekPending())
+ SeekDone();
+ else if (!IsReady())
+ SetReady();
+}
+
+static void
+input_curl_handle_done(CURL *easy_handle, CURLcode result)
+{
+ CurlInputStream *c = input_curl_find_request(easy_handle);
+ assert(c != nullptr);
+
+ long status = 0;
+ curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status);
+
+ c->RequestDone(result, status);
+}
+
+void
+CurlMulti::SocketAction(curl_socket_t fd, int ev_bitmask)
+{
+ int running_handles;
+ CURLMcode mcode = curl_multi_socket_action(multi, fd, ev_bitmask,
+ &running_handles);
+ if (mcode != CURLM_OK)
+ FormatError(curlm_domain,
+ "curl_multi_socket_action() failed: %s",
+ curl_multi_strerror(mcode));
+
+ ReadInfo();
+}
+
+/**
+ * Check for finished HTTP responses.
+ *
+ * Runs in the I/O thread. The caller must not hold locks.
+ */
+inline void
+CurlMulti::ReadInfo()
+{
+ assert(io_thread_inside());
+
+ CURLMsg *msg;
+ int msgs_in_queue;
+
+ while ((msg = curl_multi_info_read(multi,
+ &msgs_in_queue)) != nullptr) {
+ if (msg->msg == CURLMSG_DONE)
+ input_curl_handle_done(msg->easy_handle, msg->data.result);
+ }
+}
+
+int
+CurlMulti::TimerFunction(gcc_unused CURLM *_multi, long timeout_ms, void *userp)
+{
+ CurlMulti &multi = *(CurlMulti *)userp;
+ assert(_multi == multi.multi);
+
+ if (timeout_ms < 0) {
+ multi.Cancel();
+ return 0;
+ }
+
+ if (timeout_ms >= 0 && timeout_ms < 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. */
+ timeout_ms = 10;
+
+ multi.Schedule(timeout_ms);
+ return 0;
+}
+
+void
+CurlMulti::OnTimeout()
+{
+ SocketAction(CURL_SOCKET_TIMEOUT, 0);
+}
+
+/*
+ * InputPlugin methods
+ *
+ */
+
+static InputPlugin::InitResult
+input_curl_init(const config_param &param, Error &error)
+{
+ CURLcode code = curl_global_init(CURL_GLOBAL_ALL);
+ if (code != CURLE_OK) {
+ error.Format(curl_domain, code,
+ "curl_global_init() failed: %s",
+ curl_easy_strerror(code));
+ return InputPlugin::InitResult::UNAVAILABLE;
+ }
+
+ const auto version_info = curl_version_info(CURLVERSION_FIRST);
+ if (version_info != nullptr) {
+ FormatDebug(curl_domain, "version %s", version_info->version);
+ if (version_info->features & CURL_VERSION_SSL)
+ FormatDebug(curl_domain, "with %s",
+ version_info->ssl_version);
+
+ curl_version_num = version_info->version_num;
+ }
+
+ http_200_aliases = curl_slist_append(http_200_aliases, "ICY 200 OK");
+
+ proxy = param.GetBlockValue("proxy");
+ proxy_port = param.GetBlockValue("proxy_port", 0u);
+ proxy_user = param.GetBlockValue("proxy_user");
+ proxy_password = param.GetBlockValue("proxy_password");
+
+ if (proxy == nullptr) {
+ /* deprecated proxy configuration */
+ proxy = config_get_string(CONF_HTTP_PROXY_HOST, nullptr);
+ proxy_port = config_get_positive(CONF_HTTP_PROXY_PORT, 0);
+ proxy_user = config_get_string(CONF_HTTP_PROXY_USER, nullptr);
+ proxy_password = config_get_string(CONF_HTTP_PROXY_PASSWORD,
+ "");
+ }
+
+ verify_peer = param.GetBlockValue("verify_peer", true);
+ verify_host = param.GetBlockValue("verify_host", true);
+
+ CURLM *multi = curl_multi_init();
+ if (multi == nullptr) {
+ curl_slist_free_all(http_200_aliases);
+ curl_global_cleanup();
+ error.Set(curl_domain, 0, "curl_multi_init() failed");
+ return InputPlugin::InitResult::UNAVAILABLE;
+ }
+
+ curl_multi = new CurlMulti(io_thread_get(), multi);
+ return InputPlugin::InitResult::SUCCESS;
+}
+
+static void
+input_curl_finish(void)
+{
+ BlockingCall(io_thread_get(), [](){
+ delete curl_multi;
+ });
+
+ curl_slist_free_all(http_200_aliases);
+ http_200_aliases = nullptr;
+
+ curl_global_cleanup();
+}
+
+CurlInputStream::~CurlInputStream()
+{
+ FreeEasyIndirect();
+}
+
+inline void
+CurlInputStream::ResponseBoundary()
+{
+ /* undo all effects of HeaderReceived() because the previous
+ response was not applicable for this stream */
+
+ if (IsSeekPending())
+ /* don't update metadata while seeking */
+ return;
+
+ seekable = false;
+ size = UNKNOWN_SIZE;
+ ClearMimeType();
+ ClearTag();
+
+ // TODO: reset the IcyInputStream?
+}
+
+inline void
+CurlInputStream::HeaderReceived(const char *name, std::string &&value)
+{
+ if (IsSeekPending())
+ /* don't update metadata while seeking */
+ return;
+
+ if (StringEqualsCaseASCII(name, "accept-ranges")) {
+ /* a stream with icy-metadata is not seekable */
+ if (!icy->IsEnabled())
+ seekable = true;
+ } else if (StringEqualsCaseASCII(name, "content-length")) {
+ size = offset + ParseUint64(value.c_str());
+ } else if (StringEqualsCaseASCII(name, "content-type")) {
+ SetMimeType(std::move(value));
+ } else if (StringEqualsCaseASCII(name, "icy-name") ||
+ StringEqualsCaseASCII(name, "ice-name") ||
+ StringEqualsCaseASCII(name, "x-audiocast-name")) {
+ TagBuilder tag_builder;
+ tag_builder.AddItem(TAG_NAME, value.c_str());
+
+ SetTag(tag_builder.CommitNew());
+ } else if (StringEqualsCaseASCII(name, "icy-metaint")) {
+ if (icy->IsEnabled())
+ return;
+
+ size_t icy_metaint = ParseUint64(value.c_str());
+ FormatDebug(curl_domain, "icy-metaint=%zu", icy_metaint);
+
+ if (icy_metaint > 0) {
+ icy->Enable(icy_metaint);
+
+ /* a stream with icy-metadata is not
+ seekable */
+ seekable = false;
+ }
+ }
+}
+
+/** called by curl when new data is available */
+static size_t
+input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
+{
+ CurlInputStream &c = *(CurlInputStream *)stream;
+
+ size *= nmemb;
+
+ const char *header = (const char *)ptr;
+ if (size > 5 && memcmp(header, "HTTP/", 5) == 0) {
+ c.ResponseBoundary();
+ return size;
+ }
+
+ const char *end = header + size;
+
+ char name[64];
+
+ const char *value = (const char *)memchr(header, ':', size);
+ if (value == nullptr || (size_t)(value - header) >= sizeof(name))
+ return size;
+
+ memcpy(name, header, value - header);
+ name[value - header] = 0;
+
+ /* skip the colon */
+
+ ++value;
+
+ /* strip the value */
+
+ value = StripLeft(value, end);
+ end = StripRight(value, end);
+
+ c.HeaderReceived(name, std::string(value, end));
+ return size;
+}
+
+inline size_t
+CurlInputStream::DataReceived(const void *ptr, size_t received_size)
+{
+ assert(received_size > 0);
+
+ const ScopeLock protect(mutex);
+
+ if (IsSeekPending())
+ SeekDone();
+
+ if (received_size > GetBufferSpace()) {
+ AsyncInputStream::Pause();
+ return CURL_WRITEFUNC_PAUSE;
+ }
+
+ AppendToBuffer(ptr, received_size);
+ return received_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)
+{
+ CurlInputStream &c = *(CurlInputStream *)stream;
+
+ size *= nmemb;
+ if (size == 0)
+ return 0;
+
+ return c.DataReceived(ptr, size);
+}
+
+bool
+CurlInputStream::InitEasy(Error &error)
+{
+ easy = curl_easy_init();
+ if (easy == nullptr) {
+ error.Set(curl_domain, "curl_easy_init() failed");
+ return false;
+ }
+
+ curl_easy_setopt(easy, CURLOPT_PRIVATE, (void *)this);
+ curl_easy_setopt(easy, CURLOPT_USERAGENT,
+ "Music Player Daemon " VERSION);
+ curl_easy_setopt(easy, CURLOPT_HEADERFUNCTION,
+ input_curl_headerfunction);
+ curl_easy_setopt(easy, CURLOPT_WRITEHEADER, this);
+ curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION,
+ input_curl_writefunction);
+ curl_easy_setopt(easy, CURLOPT_WRITEDATA, this);
+ curl_easy_setopt(easy, CURLOPT_HTTP200ALIASES, http_200_aliases);
+ curl_easy_setopt(easy, CURLOPT_FOLLOWLOCATION, 1l);
+ curl_easy_setopt(easy, CURLOPT_NETRC, 1l);
+ curl_easy_setopt(easy, CURLOPT_MAXREDIRS, 5l);
+ curl_easy_setopt(easy, CURLOPT_FAILONERROR, 1l);
+ curl_easy_setopt(easy, CURLOPT_ERRORBUFFER, error_buffer);
+ curl_easy_setopt(easy, CURLOPT_NOPROGRESS, 1l);
+ curl_easy_setopt(easy, CURLOPT_NOSIGNAL, 1l);
+ curl_easy_setopt(easy, CURLOPT_CONNECTTIMEOUT, 10l);
+
+ if (proxy != nullptr)
+ curl_easy_setopt(easy, CURLOPT_PROXY, proxy);
+
+ if (proxy_port > 0)
+ curl_easy_setopt(easy, CURLOPT_PROXYPORT, (long)proxy_port);
+
+ if (proxy_user != nullptr && proxy_password != nullptr) {
+ char proxy_auth_str[1024];
+ snprintf(proxy_auth_str, sizeof(proxy_auth_str),
+ "%s:%s",
+ proxy_user, proxy_password);
+ curl_easy_setopt(easy, CURLOPT_PROXYUSERPWD, proxy_auth_str);
+ }
+
+ curl_easy_setopt(easy, CURLOPT_SSL_VERIFYPEER, verify_peer ? 1l : 0l);
+ curl_easy_setopt(easy, CURLOPT_SSL_VERIFYHOST, verify_host ? 2l : 0l);
+
+ CURLcode code = curl_easy_setopt(easy, CURLOPT_URL, GetURI());
+ if (code != CURLE_OK) {
+ error.Format(curl_domain, code,
+ "curl_easy_setopt() failed: %s",
+ curl_easy_strerror(code));
+ return false;
+ }
+
+ request_headers = nullptr;
+ request_headers = curl_slist_append(request_headers,
+ "Icy-Metadata: 1");
+ curl_easy_setopt(easy, CURLOPT_HTTPHEADER, request_headers);
+
+ return true;
+}
+
+void
+CurlInputStream::DoSeek(offset_type new_offset)
+{
+ assert(IsReady());
+
+ /* close the old connection and open a new one */
+
+ mutex.unlock();
+
+ FreeEasyIndirect();
+
+ offset = new_offset;
+ if (offset == size) {
+ /* seek to EOF: simulate empty result; avoid
+ triggering a "416 Requested Range Not Satisfiable"
+ response */
+ mutex.lock();
+ SeekDone();
+ return;
+ }
+
+ Error error;
+ if (!InitEasy(postponed_error)) {
+ mutex.lock();
+ PostponeError(std::move(error));
+ return;
+ }
+
+ /* send the "Range" header */
+
+ if (offset > 0) {
+ sprintf(range, "%lld-", (long long)offset);
+ curl_easy_setopt(easy, CURLOPT_RANGE, range);
+ }
+
+ if (!input_curl_easy_add_indirect(this, error)) {
+ mutex.lock();
+ PostponeError(std::move(error));
+ return;
+ }
+
+ mutex.lock();
+ offset = new_offset;
+}
+
+inline InputStream *
+CurlInputStream::Open(const char *url, Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ void *buffer = HugeAllocate(CURL_MAX_BUFFERED);
+ if (buffer == nullptr) {
+ error.Set(curl_domain, "Out of memory");
+ return nullptr;
+ }
+
+ CurlInputStream *c = new CurlInputStream(url, mutex, cond, buffer);
+
+ if (!c->InitEasy(error) || !input_curl_easy_add_indirect(c, error)) {
+ delete c;
+ return nullptr;
+ }
+
+ return c->icy;
+}
+
+static InputStream *
+input_curl_open(const char *url, Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ if (memcmp(url, "http://", 7) != 0 &&
+ memcmp(url, "https://", 8) != 0)
+ return nullptr;
+
+ return CurlInputStream::Open(url, mutex, cond, error);
+}
+
+const struct InputPlugin input_plugin_curl = {
+ "curl",
+ input_curl_init,
+ input_curl_finish,
+ input_curl_open,
+};
diff --git a/src/input/plugins/CurlInputPlugin.hxx b/src/input/plugins/CurlInputPlugin.hxx
new file mode 100644
index 000000000..4acb18bfc
--- /dev/null
+++ b/src/input/plugins/CurlInputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 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
+
+extern const struct InputPlugin input_plugin_curl;
+
+#endif
diff --git a/src/input/plugins/FfmpegInputPlugin.cxx b/src/input/plugins/FfmpegInputPlugin.cxx
new file mode 100644
index 000000000..669f8d403
--- /dev/null
+++ b/src/input/plugins/FfmpegInputPlugin.cxx
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2003-2014 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 "lib/ffmpeg/Domain.hxx"
+#include "lib/ffmpeg/Error.hxx"
+#include "../InputStream.hxx"
+#include "../InputPlugin.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+
+extern "C" {
+#include <libavformat/avio.h>
+#include <libavformat/avformat.h>
+}
+
+struct FfmpegInputStream final : public InputStream {
+ AVIOContext *h;
+
+ bool eof;
+
+ FfmpegInputStream(const char *_uri, Mutex &_mutex, Cond &_cond,
+ AVIOContext *_h)
+ :InputStream(_uri, _mutex, _cond),
+ h(_h), eof(false) {
+ seekable = (h->seekable & AVIO_SEEKABLE_NORMAL) != 0;
+ 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 */
+ SetMimeType("audio/x-mpd-ffmpeg");
+ SetReady();
+ }
+
+ ~FfmpegInputStream() {
+ avio_close(h);
+ }
+
+ /* virtual methods from InputStream */
+ bool IsEOF() override;
+ size_t Read(void *ptr, size_t size, Error &error) override;
+ bool Seek(offset_type offset, Error &error) override;
+};
+
+static inline bool
+input_ffmpeg_supported(void)
+{
+ void *opaque = nullptr;
+ return avio_enum_protocols(&opaque, 0) != nullptr;
+}
+
+static InputPlugin::InitResult
+input_ffmpeg_init(gcc_unused const config_param &param,
+ Error &error)
+{
+ av_register_all();
+
+ /* disable this plugin if there's no registered protocol */
+ if (!input_ffmpeg_supported()) {
+ error.Set(ffmpeg_domain, "No protocol");
+ return InputPlugin::InitResult::UNAVAILABLE;
+ }
+
+ return InputPlugin::InitResult::SUCCESS;
+}
+
+static InputStream *
+input_ffmpeg_open(const char *uri,
+ Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ if (!StringStartsWith(uri, "gopher://") &&
+ !StringStartsWith(uri, "rtp://") &&
+ !StringStartsWith(uri, "rtsp://") &&
+ !StringStartsWith(uri, "rtmp://") &&
+ !StringStartsWith(uri, "rtmpt://") &&
+ !StringStartsWith(uri, "rtmps://"))
+ return nullptr;
+
+ AVIOContext *h;
+ auto result = avio_open(&h, uri, AVIO_FLAG_READ);
+ if (result != 0) {
+ SetFfmpegError(error, result);
+ return nullptr;
+ }
+
+ return new FfmpegInputStream(uri, mutex, cond, h);
+}
+
+size_t
+FfmpegInputStream::Read(void *ptr, size_t read_size, Error &error)
+{
+ auto result = avio_read(h, (unsigned char *)ptr, read_size);
+ if (result <= 0) {
+ if (result < 0)
+ SetFfmpegError(error, result, "avio_read() failed");
+
+ eof = true;
+ return false;
+ }
+
+ offset += result;
+ return (size_t)result;
+}
+
+bool
+FfmpegInputStream::IsEOF()
+{
+ return eof;
+}
+
+bool
+FfmpegInputStream::Seek(offset_type new_offset, Error &error)
+{
+ auto result = avio_seek(h, new_offset, SEEK_SET);
+
+ if (result < 0) {
+ SetFfmpegError(error, result, "avio_seek() failed");
+ return false;
+ }
+
+ offset = result;
+ eof = false;
+ return true;
+}
+
+const InputPlugin input_plugin_ffmpeg = {
+ "ffmpeg",
+ input_ffmpeg_init,
+ nullptr,
+ input_ffmpeg_open,
+};
diff --git a/src/input/plugins/FfmpegInputPlugin.hxx b/src/input/plugins/FfmpegInputPlugin.hxx
new file mode 100644
index 000000000..43f829e89
--- /dev/null
+++ b/src/input/plugins/FfmpegInputPlugin.hxx
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2003-2014 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 InputPlugin input_plugin_ffmpeg;
+
+#endif
diff --git a/src/input/plugins/FileInputPlugin.cxx b/src/input/plugins/FileInputPlugin.cxx
new file mode 100644
index 000000000..867b5722d
--- /dev/null
+++ b/src/input/plugins/FileInputPlugin.cxx
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2003-2014 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 "../InputStream.hxx"
+#include "../InputPlugin.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "fs/FileSystem.hxx"
+#include "fs/Path.hxx"
+#include "system/fd_util.h"
+#include "open.h"
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+static constexpr Domain file_domain("file");
+
+class FileInputStream final : public InputStream {
+ const int fd;
+
+public:
+ FileInputStream(const char *path, int _fd, off_t _size,
+ Mutex &_mutex, Cond &_cond)
+ :InputStream(path, _mutex, _cond),
+ fd(_fd) {
+ size = _size;
+ seekable = true;
+ SetReady();
+ }
+
+ ~FileInputStream() {
+ close(fd);
+ }
+
+ /* virtual methods from InputStream */
+
+ bool IsEOF() override {
+ return GetOffset() >= GetSize();
+ }
+
+ size_t Read(void *ptr, size_t size, Error &error) override;
+ bool Seek(offset_type offset, Error &error) override;
+};
+
+InputStream *
+OpenFileInputStream(Path path,
+ Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ const int fd = OpenFile(path, O_RDONLY|O_BINARY, 0);
+ if (fd < 0) {
+ error.FormatErrno("Failed to open \"%s\"",
+ path.c_str());
+ return nullptr;
+ }
+
+ struct stat st;
+ if (fstat(fd, &st) < 0) {
+ error.FormatErrno("Failed to stat \"%s\"", path.c_str());
+ close(fd);
+ return nullptr;
+ }
+
+ if (!S_ISREG(st.st_mode)) {
+ error.Format(file_domain, "Not a regular file: %s",
+ path.c_str());
+ close(fd);
+ return nullptr;
+ }
+
+#ifdef POSIX_FADV_SEQUENTIAL
+ posix_fadvise(fd, (off_t)0, st.st_size, POSIX_FADV_SEQUENTIAL);
+#endif
+
+ return new FileInputStream(path.c_str(), fd, st.st_size, mutex, cond);
+}
+
+static InputStream *
+input_file_open(gcc_unused const char *filename,
+ gcc_unused Mutex &mutex, gcc_unused Cond &cond,
+ gcc_unused Error &error)
+{
+ /* dummy method; use OpenFileInputStream() instead */
+
+ return nullptr;
+}
+
+bool
+FileInputStream::Seek(offset_type new_offset, Error &error)
+{
+ auto result = lseek(fd, (off_t)new_offset, SEEK_SET);
+ if (result < 0) {
+ error.SetErrno("Failed to seek");
+ return false;
+ }
+
+ offset = (offset_type)result;
+ return true;
+}
+
+size_t
+FileInputStream::Read(void *ptr, size_t read_size, Error &error)
+{
+ ssize_t nbytes = read(fd, ptr, read_size);
+ if (nbytes < 0) {
+ error.SetErrno("Failed to read");
+ return 0;
+ }
+
+ offset += nbytes;
+ return (size_t)nbytes;
+}
+
+const InputPlugin input_plugin_file = {
+ "file",
+ nullptr,
+ nullptr,
+ input_file_open,
+};
diff --git a/src/input/plugins/FileInputPlugin.hxx b/src/input/plugins/FileInputPlugin.hxx
new file mode 100644
index 000000000..ee194ec34
--- /dev/null
+++ b/src/input/plugins/FileInputPlugin.hxx
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2003-2014 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
+
+class InputStream;
+class Path;
+class Mutex;
+class Cond;
+class Error;
+
+extern const struct InputPlugin input_plugin_file;
+
+InputStream *
+OpenFileInputStream(Path path,
+ Mutex &mutex, Cond &cond,
+ Error &error);
+
+#endif
diff --git a/src/input/plugins/MmsInputPlugin.cxx b/src/input/plugins/MmsInputPlugin.cxx
new file mode 100644
index 000000000..d01cff3b3
--- /dev/null
+++ b/src/input/plugins/MmsInputPlugin.cxx
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2003-2014 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 "input/ThreadInputStream.hxx"
+#include "input/InputPlugin.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <libmms/mmsx.h>
+
+static constexpr size_t MMS_BUFFER_SIZE = 256 * 1024;
+
+class MmsInputStream final : public ThreadInputStream {
+ mmsx_t *mms;
+
+public:
+ MmsInputStream(const char *_uri, Mutex &_mutex, Cond &_cond)
+ :ThreadInputStream(input_plugin_mms.name, _uri, _mutex, _cond,
+ MMS_BUFFER_SIZE) {
+ }
+
+protected:
+ virtual bool Open(gcc_unused Error &error) override;
+ virtual size_t ThreadRead(void *ptr, size_t size,
+ Error &error) override;
+
+ void Close() override {
+ mmsx_close(mms);
+ }
+};
+
+static constexpr Domain mms_domain("mms");
+
+bool
+MmsInputStream::Open(Error &error)
+{
+ Unlock();
+
+ mms = mmsx_connect(nullptr, nullptr, GetURI(), 128 * 1024);
+ if (mms == nullptr) {
+ Lock();
+ error.Set(mms_domain, "mmsx_connect() failed");
+ return false;
+ }
+
+ Lock();
+
+ /* TODO: is this correct? at least this selects the ffmpeg
+ decoder, which seems to work fine */
+ SetMimeType("audio/x-ms-wma");
+ return true;
+}
+
+static InputStream *
+input_mms_open(const char *url,
+ Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ if (!StringStartsWith(url, "mms://") &&
+ !StringStartsWith(url, "mmsh://") &&
+ !StringStartsWith(url, "mmst://") &&
+ !StringStartsWith(url, "mmsu://"))
+ return nullptr;
+
+ auto m = new MmsInputStream(url, mutex, cond);
+ auto is = m->Start(error);
+ if (is == nullptr)
+ delete m;
+
+ return is;
+}
+
+size_t
+MmsInputStream::ThreadRead(void *ptr, size_t read_size, Error &error)
+{
+ /* unfortunately, mmsx_read() blocks until the whole buffer
+ has been filled; to avoid big latencies, limit the size of
+ each chunk we read to a reasonable size */
+ constexpr size_t MAX_CHUNK = 16384;
+ if (read_size > MAX_CHUNK)
+ read_size = MAX_CHUNK;
+
+ int nbytes = mmsx_read(nullptr, mms, (char *)ptr, read_size);
+ if (nbytes <= 0) {
+ if (nbytes < 0)
+ error.SetErrno("mmsx_read() failed");
+ return 0;
+ }
+
+ return (size_t)nbytes;
+}
+
+const InputPlugin input_plugin_mms = {
+ "mms",
+ nullptr,
+ nullptr,
+ input_mms_open,
+};
diff --git a/src/input/plugins/MmsInputPlugin.hxx b/src/input/plugins/MmsInputPlugin.hxx
new file mode 100644
index 000000000..b4017ffd6
--- /dev/null
+++ b/src/input/plugins/MmsInputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 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_MMS_H
+#define INPUT_MMS_H
+
+extern const struct InputPlugin input_plugin_mms;
+
+#endif
diff --git a/src/input/plugins/NfsInputPlugin.cxx b/src/input/plugins/NfsInputPlugin.cxx
new file mode 100644
index 000000000..c6c0970b9
--- /dev/null
+++ b/src/input/plugins/NfsInputPlugin.cxx
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2003-2014 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 "NfsInputPlugin.hxx"
+#include "../AsyncInputStream.hxx"
+#include "../InputPlugin.hxx"
+#include "lib/nfs/Domain.hxx"
+#include "lib/nfs/Glue.hxx"
+#include "lib/nfs/FileReader.hxx"
+#include "util/HugeAllocator.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+
+extern "C" {
+#include <nfsc/libnfs.h>
+}
+
+#include <string.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+/**
+ * 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 NFS_MAX_BUFFERED = 512 * 1024;
+
+/**
+ * Resume the stream at this number of bytes after it has been paused.
+ */
+static const size_t NFS_RESUME_AT = 384 * 1024;
+
+class NfsInputStream final : public AsyncInputStream, NfsFileReader {
+ uint64_t next_offset;
+
+ bool reconnect_on_resume, reconnecting;
+
+public:
+ NfsInputStream(const char *_uri,
+ Mutex &_mutex, Cond &_cond,
+ void *_buffer)
+ :AsyncInputStream(_uri, _mutex, _cond,
+ _buffer, NFS_MAX_BUFFERED,
+ NFS_RESUME_AT),
+ reconnect_on_resume(false), reconnecting(false) {}
+
+ virtual ~NfsInputStream() {
+ DeferClose();
+ }
+
+ bool Open(Error &error) {
+ assert(!IsReady());
+
+ return NfsFileReader::Open(GetURI(), error);
+ }
+
+private:
+ bool DoRead();
+
+protected:
+ /* virtual methods from AsyncInputStream */
+ virtual void DoResume() override;
+ virtual void DoSeek(offset_type new_offset) override;
+
+private:
+ /* virtual methods from NfsFileReader */
+ void OnNfsFileOpen(uint64_t size) override;
+ void OnNfsFileRead(const void *data, size_t size) override;
+ void OnNfsFileError(Error &&error) override;
+};
+
+bool
+NfsInputStream::DoRead()
+{
+ assert(NfsFileReader::IsIdle());
+
+ int64_t remaining = size - next_offset;
+ if (remaining <= 0)
+ return true;
+
+ const size_t buffer_space = GetBufferSpace();
+ if (buffer_space == 0) {
+ Pause();
+ return true;
+ }
+
+ size_t nbytes = std::min<size_t>(std::min<uint64_t>(remaining, 32768),
+ buffer_space);
+
+ mutex.unlock();
+ Error error;
+ bool success = NfsFileReader::Read(next_offset, nbytes, error);
+ mutex.lock();
+
+ if (!success) {
+ PostponeError(std::move(error));
+ return false;
+ }
+
+ return true;
+}
+
+void
+NfsInputStream::DoResume()
+{
+ if (reconnect_on_resume) {
+ /* the NFS connection has died while this stream was
+ "paused" - attempt to reconnect */
+
+ reconnect_on_resume = false;
+ reconnecting = true;
+
+ mutex.unlock();
+ NfsFileReader::Close();
+
+ Error error;
+ bool success = NfsFileReader::Open(GetURI(), error);
+ mutex.lock();
+
+ if (!success) {
+ postponed_error = std::move(error);
+ cond.broadcast();
+ }
+
+ return;
+ }
+
+ assert(NfsFileReader::IsIdle());
+
+ DoRead();
+}
+
+void
+NfsInputStream::DoSeek(offset_type new_offset)
+{
+ mutex.unlock();
+ NfsFileReader::CancelRead();
+ mutex.lock();
+
+ next_offset = offset = new_offset;
+ SeekDone();
+ DoRead();
+}
+
+void
+NfsInputStream::OnNfsFileOpen(uint64_t _size)
+{
+ const ScopeLock protect(mutex);
+
+ if (reconnecting) {
+ /* reconnect has succeeded */
+
+ reconnecting = false;
+ DoRead();
+ return;
+ }
+
+ size = _size;
+ seekable = true;
+ next_offset = 0;
+ SetReady();
+ DoRead();
+}
+
+void
+NfsInputStream::OnNfsFileRead(const void *data, size_t data_size)
+{
+ const ScopeLock protect(mutex);
+ assert(!IsBufferFull());
+ assert(IsBufferFull() == (GetBufferSpace() == 0));
+ AppendToBuffer(data, data_size);
+
+ next_offset += data_size;
+
+ DoRead();
+}
+
+void
+NfsInputStream::OnNfsFileError(Error &&error)
+{
+ const ScopeLock protect(mutex);
+
+ if (IsPaused()) {
+ /* while we're paused, don't report this error to the
+ client just yet (it might just be timeout, maybe
+ playback has been paused for quite some time) -
+ wait until the stream gets resumed and try to
+ reconnect, to give it another chance */
+
+ reconnect_on_resume = true;
+ return;
+ }
+
+ postponed_error = std::move(error);
+
+ if (IsSeekPending())
+ SeekDone();
+ else if (!IsReady())
+ SetReady();
+ else
+ cond.broadcast();
+}
+
+/*
+ * InputPlugin methods
+ *
+ */
+
+static InputPlugin::InitResult
+input_nfs_init(const config_param &, Error &)
+{
+ nfs_init();
+ return InputPlugin::InitResult::SUCCESS;
+}
+
+static void
+input_nfs_finish()
+{
+ nfs_finish();
+}
+
+static InputStream *
+input_nfs_open(const char *uri,
+ Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ if (!StringStartsWith(uri, "nfs://"))
+ return nullptr;
+
+ void *buffer = HugeAllocate(NFS_MAX_BUFFERED);
+ if (buffer == nullptr) {
+ error.Set(nfs_domain, "Out of memory");
+ return nullptr;
+ }
+
+ NfsInputStream *is = new NfsInputStream(uri, mutex, cond, buffer);
+ if (!is->Open(error)) {
+ delete is;
+ return nullptr;
+ }
+
+ return is;
+}
+
+const InputPlugin input_plugin_nfs = {
+ "nfs",
+ input_nfs_init,
+ input_nfs_finish,
+ input_nfs_open,
+};
diff --git a/src/input/plugins/NfsInputPlugin.hxx b/src/input/plugins/NfsInputPlugin.hxx
new file mode 100644
index 000000000..d2cc87549
--- /dev/null
+++ b/src/input/plugins/NfsInputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 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_NFS_H
+#define MPD_INPUT_NFS_H
+
+extern const struct InputPlugin input_plugin_nfs;
+
+#endif
diff --git a/src/input/plugins/RewindInputPlugin.cxx b/src/input/plugins/RewindInputPlugin.cxx
new file mode 100644
index 000000000..95f604044
--- /dev/null
+++ b/src/input/plugins/RewindInputPlugin.cxx
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2003-2014 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 "../ProxyInputStream.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+class RewindInputStream final : public ProxyInputStream {
+ /**
+ * 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];
+
+public:
+ RewindInputStream(InputStream *_input)
+ :ProxyInputStream(_input),
+ tail(0) {
+ }
+
+ /* virtual methods from InputStream */
+
+ void Update() override {
+ if (!ReadingFromBuffer())
+ ProxyInputStream::Update();
+ }
+
+ bool IsEOF() override {
+ return !ReadingFromBuffer() && ProxyInputStream::IsEOF();
+ }
+
+ size_t Read(void *ptr, size_t size, Error &error) override;
+ bool Seek(offset_type offset, Error &error) override;
+
+private:
+ /**
+ * 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 && offset < input.GetOffset();
+ }
+};
+
+size_t
+RewindInputStream::Read(void *ptr, size_t read_size, Error &error)
+{
+ if (ReadingFromBuffer()) {
+ /* buffered read */
+
+ assert(head == (size_t)offset);
+ assert(tail == (size_t)input.GetOffset());
+
+ if (read_size > tail - head)
+ read_size = tail - head;
+
+ memcpy(ptr, buffer + head, read_size);
+ head += read_size;
+ offset += read_size;
+
+ return read_size;
+ } else {
+ /* pass method call to underlying stream */
+
+ size_t nbytes = input.Read(ptr, read_size, error);
+
+ if (input.GetOffset() > (offset_type)sizeof(buffer))
+ /* disable buffering */
+ tail = 0;
+ else if (tail == (size_t)offset) {
+ /* append to buffer */
+
+ memcpy(buffer + tail, ptr, nbytes);
+ tail += nbytes;
+
+ assert(tail == (size_t)input.GetOffset());
+ }
+
+ CopyAttributes();
+
+ return nbytes;
+ }
+}
+
+bool
+RewindInputStream::Seek(offset_type new_offset,
+ Error &error)
+{
+ assert(IsReady());
+
+ if (tail > 0 && new_offset <= (offset_type)tail) {
+ /* buffered seek */
+
+ assert(!ReadingFromBuffer() ||
+ head == (size_t)offset);
+ assert(tail == (size_t)input.GetOffset());
+
+ head = (size_t)new_offset;
+ offset = new_offset;
+
+ return true;
+ } else {
+ /* disable the buffer, because input has left the
+ buffered range now */
+ tail = 0;
+
+ return ProxyInputStream::Seek(new_offset, error);
+ }
+}
+
+InputStream *
+input_rewind_open(InputStream *is)
+{
+ assert(is != nullptr);
+ assert(!is->IsReady() || is->GetOffset() == 0);
+
+ if (is->IsReady() && is->IsSeekable())
+ /* seekable resources don't need this plugin */
+ return is;
+
+ return new RewindInputStream(is);
+}
diff --git a/src/input/plugins/RewindInputPlugin.hxx b/src/input/plugins/RewindInputPlugin.hxx
new file mode 100644
index 000000000..56b01b585
--- /dev/null
+++ b/src/input/plugins/RewindInputPlugin.hxx
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2003-2014 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"
+
+class InputStream;
+
+InputStream *
+input_rewind_open(InputStream *is);
+
+#endif
diff --git a/src/input/plugins/SmbclientInputPlugin.cxx b/src/input/plugins/SmbclientInputPlugin.cxx
new file mode 100644
index 000000000..79987180f
--- /dev/null
+++ b/src/input/plugins/SmbclientInputPlugin.cxx
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2003-2014 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 "SmbclientInputPlugin.hxx"
+#include "lib/smbclient/Init.hxx"
+#include "lib/smbclient/Mutex.hxx"
+#include "../InputStream.hxx"
+#include "../InputPlugin.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+
+#include <libsmbclient.h>
+
+class SmbclientInputStream final : public InputStream {
+ SMBCCTX *ctx;
+ int fd;
+
+public:
+ SmbclientInputStream(const char *_uri,
+ Mutex &_mutex, Cond &_cond,
+ SMBCCTX *_ctx, int _fd, const struct stat &st)
+ :InputStream(_uri, _mutex, _cond),
+ ctx(_ctx), fd(_fd) {
+ seekable = true;
+ size = st.st_size;
+ SetReady();
+ }
+
+ ~SmbclientInputStream() {
+ smbclient_mutex.lock();
+ smbc_close(fd);
+ smbc_free_context(ctx, 1);
+ smbclient_mutex.unlock();
+ }
+
+ /* virtual methods from InputStream */
+
+ bool IsEOF() override {
+ return offset >= size;
+ }
+
+ size_t Read(void *ptr, size_t size, Error &error) override;
+ bool Seek(offset_type offset, Error &error) override;
+};
+
+/*
+ * InputPlugin methods
+ *
+ */
+
+static InputPlugin::InitResult
+input_smbclient_init(gcc_unused const config_param &param, Error &error)
+{
+ if (!SmbclientInit(error))
+ return InputPlugin::InitResult::UNAVAILABLE;
+
+ // TODO: create one global SMBCCTX here?
+
+ // TODO: evaluate config_param, call smbc_setOption*()
+
+ return InputPlugin::InitResult::SUCCESS;
+}
+
+static InputStream *
+input_smbclient_open(const char *uri,
+ Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ if (!StringStartsWith(uri, "smb://"))
+ return nullptr;
+
+ const ScopeLock protect(smbclient_mutex);
+
+ SMBCCTX *ctx = smbc_new_context();
+ if (ctx == nullptr) {
+ error.SetErrno("smbc_new_context() failed");
+ return nullptr;
+ }
+
+ SMBCCTX *ctx2 = smbc_init_context(ctx);
+ if (ctx2 == nullptr) {
+ error.SetErrno("smbc_init_context() failed");
+ smbc_free_context(ctx, 1);
+ return nullptr;
+ }
+
+ ctx = ctx2;
+
+ int fd = smbc_open(uri, O_RDONLY, 0);
+ if (fd < 0) {
+ error.SetErrno("smbc_open() failed");
+ smbc_free_context(ctx, 1);
+ return nullptr;
+ }
+
+ struct stat st;
+ if (smbc_fstat(fd, &st) < 0) {
+ error.SetErrno("smbc_fstat() failed");
+ smbc_close(fd);
+ smbc_free_context(ctx, 1);
+ return nullptr;
+ }
+
+ return new SmbclientInputStream(uri, mutex, cond, ctx, fd, st);
+}
+
+size_t
+SmbclientInputStream::Read(void *ptr, size_t read_size, Error &error)
+{
+ smbclient_mutex.lock();
+ ssize_t nbytes = smbc_read(fd, ptr, read_size);
+ smbclient_mutex.unlock();
+ if (nbytes < 0) {
+ error.SetErrno("smbc_read() failed");
+ nbytes = 0;
+ }
+
+ return nbytes;
+}
+
+bool
+SmbclientInputStream::Seek(offset_type new_offset, Error &error)
+{
+ smbclient_mutex.lock();
+ off_t result = smbc_lseek(fd, new_offset, SEEK_SET);
+ smbclient_mutex.unlock();
+ if (result < 0) {
+ error.SetErrno("smbc_lseek() failed");
+ return false;
+ }
+
+ offset = result;
+ return true;
+}
+
+const InputPlugin input_plugin_smbclient = {
+ "smbclient",
+ input_smbclient_init,
+ nullptr,
+ input_smbclient_open,
+};
diff --git a/src/input/plugins/SmbclientInputPlugin.hxx b/src/input/plugins/SmbclientInputPlugin.hxx
new file mode 100644
index 000000000..a0539d020
--- /dev/null
+++ b/src/input/plugins/SmbclientInputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 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_SMBCLIENT_H
+#define MPD_INPUT_SMBCLIENT_H
+
+extern const struct InputPlugin input_plugin_smbclient;
+
+#endif