aboutsummaryrefslogtreecommitdiffstats
path: root/src/input
diff options
context:
space:
mode:
Diffstat (limited to 'src/input')
-rw-r--r--src/input/Init.cxx87
-rw-r--r--src/input/Init.hxx36
-rw-r--r--src/input/InputPlugin.hxx120
-rw-r--r--src/input/InputStream.cxx214
-rw-r--r--src/input/InputStream.hxx301
-rw-r--r--src/input/MmsInputPlugin.cxx130
-rw-r--r--src/input/Registry.cxx93
-rw-r--r--src/input/Registry.hxx43
-rw-r--r--src/input/TextInputStream.cxx78
-rw-r--r--src/input/TextInputStream.hxx56
-rw-r--r--src/input/ThreadInputStream.cxx204
-rw-r--r--src/input/ThreadInputStream.hxx181
-rw-r--r--src/input/plugins/AlsaInputPlugin.cxx429
-rw-r--r--src/input/plugins/AlsaInputPlugin.hxx28
-rw-r--r--src/input/plugins/ArchiveInputPlugin.cxx (renamed from src/input/ArchiveInputPlugin.cxx)35
-rw-r--r--src/input/plugins/ArchiveInputPlugin.hxx (renamed from src/input/ArchiveInputPlugin.hxx)2
-rw-r--r--src/input/plugins/CdioParanoiaInputPlugin.cxx (renamed from src/input/CdioParanoiaInputPlugin.cxx)19
-rw-r--r--src/input/plugins/CdioParanoiaInputPlugin.hxx (renamed from src/input/CdioParanoiaInputPlugin.hxx)2
-rw-r--r--src/input/plugins/CurlInputPlugin.cxx (renamed from src/input/CurlInputPlugin.cxx)756
-rw-r--r--src/input/plugins/CurlInputPlugin.hxx (renamed from src/input/CurlInputPlugin.hxx)2
-rw-r--r--src/input/plugins/DespotifyInputPlugin.cxx (renamed from src/input/DespotifyInputPlugin.cxx)142
-rw-r--r--src/input/plugins/DespotifyInputPlugin.hxx (renamed from src/input/DespotifyInputPlugin.hxx)2
-rw-r--r--src/input/plugins/FfmpegInputPlugin.cxx (renamed from src/input/FfmpegInputPlugin.cxx)28
-rw-r--r--src/input/plugins/FfmpegInputPlugin.hxx (renamed from src/input/FfmpegInputPlugin.hxx)2
-rw-r--r--src/input/plugins/FileInputPlugin.cxx (renamed from src/input/FileInputPlugin.cxx)10
-rw-r--r--src/input/plugins/FileInputPlugin.hxx (renamed from src/input/FileInputPlugin.hxx)2
-rw-r--r--src/input/plugins/MmsInputPlugin.cxx117
-rw-r--r--src/input/plugins/MmsInputPlugin.hxx (renamed from src/input/MmsInputPlugin.hxx)2
-rw-r--r--src/input/plugins/NfsInputPlugin.cxx200
-rw-r--r--src/input/plugins/NfsInputPlugin.hxx25
-rw-r--r--src/input/plugins/RewindInputPlugin.cxx (renamed from src/input/RewindInputPlugin.cxx)7
-rw-r--r--src/input/plugins/RewindInputPlugin.hxx (renamed from src/input/RewindInputPlugin.hxx)2
-rw-r--r--src/input/plugins/SmbclientInputPlugin.cxx195
-rw-r--r--src/input/plugins/SmbclientInputPlugin.hxx25
34 files changed, 2948 insertions, 627 deletions
diff --git a/src/input/Init.cxx b/src/input/Init.cxx
new file mode 100644
index 000000000..5e64dcaed
--- /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..090c73df8
--- /dev/null
+++ b/src/input/InputPlugin.hxx
@@ -0,0 +1,120 @@
+/*
+ * 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;
+struct 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,
+ };
+
+ typedef int64_t offset_type;
+
+ 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);
+ void (*close)(InputStream *is);
+
+ /**
+ * Check for errors that may have occurred in the I/O thread.
+ * May be unimplemented for synchronous plugins.
+ *
+ * @return false on error
+ */
+ bool (*check)(InputStream *is, Error &error);
+
+ /**
+ * Update the public attributes. Call before access. Can be
+ * nullptr if the plugin always keeps its attributes up to date.
+ */
+ void (*update)(InputStream *is);
+
+ Tag *(*tag)(InputStream *is);
+
+ /**
+ * Returns true if the next read operation will not block:
+ * either data is available, or end-of-stream has been
+ * reached, or an error has occurred.
+ *
+ * If this method is unimplemented, then it is assumed that
+ * reading will never block.
+ */
+ bool (*available)(InputStream *is);
+
+ size_t (*read)(InputStream *is, void *ptr, size_t size,
+ Error &error);
+ bool (*eof)(InputStream *is);
+ bool (*seek)(InputStream *is, offset_type offset, int whence,
+ Error &error);
+};
+
+#endif
diff --git a/src/input/InputStream.cxx b/src/input/InputStream.cxx
new file mode 100644
index 000000000..0621437c4
--- /dev/null
+++ b/src/input/InputStream.cxx
@@ -0,0 +1,214 @@
+/*
+ * 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 "plugins/RewindInputPlugin.hxx"
+#include "util/UriUtil.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <assert.h>
+#include <stdio.h> /* for SEEK_SET */
+
+static constexpr Domain input_domain("input");
+
+InputStream *
+InputStream::Open(const char *url,
+ Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ input_plugins_for_each_enabled(plugin) {
+ InputStream *is;
+
+ is = plugin->open(url, mutex, cond, error);
+ if (is != nullptr) {
+ assert(is->plugin.close != nullptr);
+ assert(is->plugin.read != nullptr);
+ assert(is->plugin.eof != nullptr);
+ assert(!is->seekable || is->plugin.seek != 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) {
+ is->Close();
+ is = nullptr;
+ }
+
+ return is;
+}
+
+bool
+InputStream::Check(Error &error)
+{
+ return plugin.check == nullptr || plugin.check(this, error);
+}
+
+void
+InputStream::Update()
+{
+ if (plugin.update != nullptr)
+ plugin.update(this);
+}
+
+void
+InputStream::WaitReady()
+{
+ while (true) {
+ Update();
+ if (ready)
+ break;
+
+ cond.wait(mutex);
+ }
+}
+
+void
+InputStream::LockWaitReady()
+{
+ const ScopeLock protect(mutex);
+ WaitReady();
+}
+
+bool
+InputStream::CheapSeeking() const
+{
+ return IsSeekable() && !uri_has_scheme(uri.c_str());
+}
+
+bool
+InputStream::Seek(offset_type _offset, int whence, Error &error)
+{
+ if (plugin.seek == nullptr)
+ return false;
+
+ return plugin.seek(this, _offset, whence, error);
+}
+
+bool
+InputStream::LockSeek(offset_type _offset, int whence, Error &error)
+{
+ if (plugin.seek == nullptr)
+ return false;
+
+ const ScopeLock protect(mutex);
+ return Seek(_offset, whence, error);
+}
+
+bool
+InputStream::Rewind(Error &error)
+{
+ return Seek(0, SEEK_SET, error);
+}
+
+bool
+InputStream::LockRewind(Error &error)
+{
+ return LockSeek(0, SEEK_SET, error);
+}
+
+Tag *
+InputStream::ReadTag()
+{
+ return plugin.tag != nullptr
+ ? plugin.tag(this)
+ : nullptr;
+}
+
+Tag *
+InputStream::LockReadTag()
+{
+ if (plugin.tag == nullptr)
+ return nullptr;
+
+ const ScopeLock protect(mutex);
+ return ReadTag();
+}
+
+bool
+InputStream::IsAvailable()
+{
+ return plugin.available != nullptr
+ ? plugin.available(this)
+ : true;
+}
+
+size_t
+InputStream::Read(void *ptr, size_t _size, Error &error)
+{
+ assert(ptr != nullptr);
+ assert(_size > 0);
+
+ return plugin.read(this, ptr, _size, error);
+}
+
+size_t
+InputStream::LockRead(void *ptr, size_t _size, Error &error)
+{
+ assert(ptr != nullptr);
+ assert(_size > 0);
+
+ const ScopeLock protect(mutex);
+ return Read(ptr, _size, error);
+}
+
+void
+InputStream::Close()
+{
+ plugin.close(this);
+}
+
+bool
+InputStream::IsEOF()
+{
+ return plugin.eof(this);
+}
+
+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..c66091687
--- /dev/null
+++ b/src/input/InputStream.hxx
@@ -0,0 +1,301 @@
+/*
+ * 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 "thread/Mutex.hxx"
+#include "Compiler.h"
+
+#include <string>
+
+#include <assert.h>
+#include <stdint.h>
+
+class Cond;
+class Error;
+struct Tag;
+struct InputPlugin;
+
+struct InputStream {
+ typedef int64_t offset_type;
+
+ /**
+ * the plugin which implements this input stream
+ */
+ const InputPlugin &plugin;
+
+ /**
+ * The absolute URI which was used to open this stream.
+ */
+ std::string uri;
+
+ /**
+ * 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;
+
+ /**
+ * indicates whether the stream is ready for reading and
+ * whether the other attributes in this struct are valid
+ */
+ bool ready;
+
+ /**
+ * if true, then the stream is fully seekable
+ */
+ bool seekable;
+
+ /**
+ * the size of the resource, or -1 if unknown
+ */
+ offset_type size;
+
+ /**
+ * the current offset within the stream
+ */
+ offset_type offset;
+
+ /**
+ * the MIME content type of the resource, or empty if unknown.
+ */
+ std::string mime;
+
+ InputStream(const InputPlugin &_plugin,
+ const char *_uri, Mutex &_mutex, Cond &_cond)
+ :plugin(_plugin), uri(_uri),
+ mutex(_mutex), cond(_cond),
+ ready(false), seekable(false),
+ size(-1), offset(0) {
+ assert(_uri != nullptr);
+ }
+
+ /**
+ * 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);
+
+ /**
+ * Close the input stream and free resources.
+ *
+ * The caller must not lock the mutex.
+ */
+ void Close();
+
+ void Lock() {
+ mutex.lock();
+ }
+
+ void Unlock() {
+ mutex.unlock();
+ }
+
+ /**
+ * Check for errors that may have occurred in the I/O thread.
+ *
+ * @return false on error
+ */
+ bool Check(Error &error);
+
+ /**
+ * Update the public attributes. Call before accessing attributes
+ * such as "ready" or "offset".
+ */
+ void Update();
+
+ /**
+ * Wait until the stream becomes ready.
+ *
+ * The caller must lock the mutex.
+ */
+ void WaitReady();
+
+ /**
+ * Wrapper for WaitReady() which locks and unlocks the mutex;
+ * the caller must not be holding it already.
+ */
+ void LockWaitReady();
+
+ gcc_pure
+ const char *GetMimeType() const {
+ assert(ready);
+
+ return mime.empty() ? nullptr : mime.c_str();
+ }
+
+ gcc_nonnull_all
+ void OverrideMimeType(const char *_mime) {
+ assert(ready);
+
+ mime = _mime;
+ }
+
+ gcc_pure
+ offset_type GetSize() const {
+ assert(ready);
+
+ return size;
+ }
+
+ gcc_pure
+ offset_type GetOffset() const {
+ assert(ready);
+
+ return 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
+ * @param whence the base of the seek, one of SEEK_SET, SEEK_CUR, SEEK_END
+ */
+ bool Seek(offset_type offset, int whence, Error &error);
+
+ /**
+ * Wrapper for Seek() which locks and unlocks the mutex; the
+ * caller must not be holding it already.
+ */
+ bool LockSeek(offset_type offset, int whence, Error &error);
+
+ /**
+ * Rewind to the beginning of the stream. This is a wrapper
+ * for Seek(0, SEEK_SET, error).
+ */
+ bool Rewind(Error &error);
+ bool LockRewind(Error &error);
+
+ /**
+ * Returns true if the stream has reached end-of-file.
+ *
+ * The caller must lock the mutex.
+ */
+ gcc_pure
+ bool IsEOF();
+
+ /**
+ * 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
+ 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
+ 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
+ size_t Read(void *ptr, size_t size, Error &error);
+
+ /**
+ * 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/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/Registry.cxx b/src/input/Registry.cxx
new file mode 100644
index 000000000..2b981df1c
--- /dev/null
+++ b/src/input/Registry.cxx
@@ -0,0 +1,93 @@
+/*
+ * 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
+
+#ifdef ENABLE_DESPOTIFY
+#include "plugins/DespotifyInputPlugin.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
+#ifdef ENABLE_DESPOTIFY
+ &input_plugin_despotify,
+#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/TextInputStream.cxx b/src/input/TextInputStream.cxx
new file mode 100644
index 000000000..d7cb440b3
--- /dev/null
+++ b/src/input/TextInputStream.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 "TextInputStream.hxx"
+#include "InputStream.hxx"
+#include "util/CharUtil.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+bool TextInputStream::ReadLine(std::string &line)
+{
+ const char *src, *p;
+
+ do {
+ size_t nbytes;
+ auto dest = buffer.Write();
+ if (dest.size >= 2) {
+ /* reserve one byte for the null terminator if
+ the last line is not terminated by a
+ newline character */
+ --dest.size;
+
+ Error error;
+ nbytes = is.LockRead(dest.data, dest.size, error);
+ if (nbytes > 0)
+ buffer.Append(nbytes);
+ else if (error.IsDefined()) {
+ LogError(error);
+ return false;
+ }
+ } else
+ nbytes = 0;
+
+ auto src_p = buffer.Read();
+ if (src_p.IsEmpty())
+ return false;
+
+ src = src_p.data;
+
+ p = reinterpret_cast<const char*>(memchr(src, '\n', src_p.size));
+ if (p == nullptr && nbytes == 0) {
+ /* end of file (or line too long): terminate
+ the current line */
+ dest = buffer.Write();
+ assert(!dest.IsEmpty());
+ dest[0] = '\n';
+ buffer.Append(1);
+ }
+ } while (p == nullptr);
+
+ size_t length = p - src + 1;
+ while (p > src && IsWhitespaceOrNull(p[-1]))
+ --p;
+
+ line = std::string(src, p - src);
+ buffer.Consume(length);
+ return true;
+}
diff --git a/src/input/TextInputStream.hxx b/src/input/TextInputStream.hxx
new file mode 100644
index 000000000..86ae16e41
--- /dev/null
+++ b/src/input/TextInputStream.hxx
@@ -0,0 +1,56 @@
+/*
+ * 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/FifoBuffer.hxx"
+
+#include <string>
+
+struct InputStream;
+
+class TextInputStream {
+ InputStream &is;
+ FifoBuffer<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.
+ *
+ * @param line a string to put result to
+ * @return true if line is read successfully, false on end of file
+ * or error
+ */
+ bool ReadLine(std::string &line);
+};
+
+#endif
diff --git a/src/input/ThreadInputStream.cxx b/src/input/ThreadInputStream.cxx
new file mode 100644
index 000000000..5271171ef
--- /dev/null
+++ b/src/input/ThreadInputStream.cxx
@@ -0,0 +1,204 @@
+/*
+ * 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 "InputPlugin.hxx"
+#include "thread/Name.hxx"
+#include "util/CircularBuffer.hxx"
+#include "util/HugeAllocator.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+ThreadInputStream::~ThreadInputStream()
+{
+ 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 &base;
+}
+
+inline void
+ThreadInputStream::ThreadFunc()
+{
+ FormatThreadName("input:%s", base.plugin.name);
+
+ base.mutex.lock();
+ if (!Open(postponed_error)) {
+ base.cond.broadcast();
+ base.mutex.unlock();
+ return;
+ }
+
+ /* we're ready, tell it to our client */
+ base.ready = true;
+ base.cond.broadcast();
+
+ while (!close) {
+ assert(!postponed_error.IsDefined());
+
+ auto w = buffer->Write();
+ if (w.IsEmpty()) {
+ wake_cond.wait(base.mutex);
+ } else {
+ base.mutex.unlock();
+
+ Error error;
+ size_t nbytes = Read(w.data, w.size, error);
+
+ base.mutex.lock();
+ base.cond.broadcast();
+
+ if (nbytes == 0) {
+ eof = true;
+ postponed_error = std::move(error);
+ break;
+ }
+
+ buffer->Append(nbytes);
+ }
+ }
+
+ base.mutex.unlock();
+
+ Close();
+}
+
+void
+ThreadInputStream::ThreadFunc(void *ctx)
+{
+ ThreadInputStream &tis = *(ThreadInputStream *)ctx;
+ tis.ThreadFunc();
+}
+
+inline bool
+ThreadInputStream::Check2(Error &error)
+{
+ if (postponed_error.IsDefined()) {
+ error = std::move(postponed_error);
+ return false;
+ }
+
+ return true;
+}
+
+bool
+ThreadInputStream::Check(InputStream *is, Error &error)
+{
+ return Cast(is)->Check2(error);
+}
+
+inline bool
+ThreadInputStream::Available2()
+{
+ return !buffer->IsEmpty() || eof || postponed_error.IsDefined();
+}
+
+bool
+ThreadInputStream::Available(InputStream *is)
+{
+ return Cast(is)->Available2();
+}
+
+inline size_t
+ThreadInputStream::Read2(void *ptr, size_t size, Error &error)
+{
+ 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(size, r.size);
+ memcpy(ptr, r.data, nbytes);
+ buffer->Consume(nbytes);
+ wake_cond.broadcast();
+ base.offset += nbytes;
+ return nbytes;
+ }
+
+ if (eof)
+ return 0;
+
+ base.cond.wait(base.mutex);
+ }
+}
+
+size_t
+ThreadInputStream::Read(InputStream *is, void *ptr, size_t size,
+ Error &error)
+{
+ return Cast(is)->Read2(ptr, size, error);
+}
+
+inline void
+ThreadInputStream::Close2()
+{
+ base.mutex.lock();
+ close = true;
+ wake_cond.signal();
+ base.mutex.unlock();
+
+ Cancel();
+
+ thread.Join();
+
+ delete this;
+}
+
+void
+ThreadInputStream::Close(InputStream *is)
+{
+ Cast(is)->Close2();
+}
+
+inline bool
+ThreadInputStream::IsEOF2()
+{
+ return eof;
+}
+
+bool
+ThreadInputStream::IsEOF(InputStream *is)
+{
+ return Cast(is)->IsEOF2();
+}
diff --git a/src/input/ThreadInputStream.hxx b/src/input/ThreadInputStream.hxx
new file mode 100644
index 000000000..01428d717
--- /dev/null
+++ b/src/input/ThreadInputStream.hxx
@@ -0,0 +1,181 @@
+/*
+ * 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/Cast.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 {
+ InputStream base;
+
+ 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 InputPlugin &_plugin,
+ const char *_uri, Mutex &_mutex, Cond &_cond,
+ size_t _buffer_size)
+ :base(_plugin, _uri, _mutex, _cond),
+ 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);
+
+protected:
+ void Lock() {
+ base.mutex.lock();
+ }
+
+ void Unlock() {
+ base.mutex.unlock();
+ }
+
+ const char *GetURI() const {
+ assert(thread.IsInside());
+
+ return base.uri.c_str();
+ }
+
+ void SetMimeType(const char *mime) {
+ assert(thread.IsInside());
+
+ base.mime = 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 Read(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:
+
+#if GCC_CHECK_VERSION(4,6) || defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Winvalid-offsetof"
+#endif
+
+ static constexpr ThreadInputStream *Cast(InputStream *is) {
+ return ContainerCast(is, ThreadInputStream, base);
+ }
+
+#if GCC_CHECK_VERSION(4,6) || defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
+ void ThreadFunc();
+ static void ThreadFunc(void *ctx);
+
+ bool Check2(Error &error);
+ bool Available2();
+ size_t Read2(void *ptr, size_t size, Error &error);
+ void Close2();
+ bool IsEOF2();
+
+public:
+ /* InputPlugin callbacks */
+ static bool Check(InputStream *is, Error &error);
+ static bool Available(InputStream *is);
+ static size_t Read(InputStream *is, void *ptr, size_t size,
+ Error &error);
+ static void Close(InputStream *is);
+ static bool IsEOF(InputStream *is);
+};
+
+#endif
diff --git a/src/input/plugins/AlsaInputPlugin.cxx b/src/input/plugins/AlsaInputPlugin.cxx
new file mode 100644
index 000000000..eae06ba6c
--- /dev/null
+++ b/src/input/plugins/AlsaInputPlugin.cxx
@@ -0,0 +1,429 @@
+/*
+ * 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 "util/Cast.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 <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 : MultiSocketMonitor, DeferredMonitor {
+ InputStream base;
+ 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)
+ :MultiSocketMonitor(loop),
+ DeferredMonitor(loop),
+ base(input_plugin_alsa, uri, mutex, cond),
+ 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 */
+ base.mime = "audio/x-mpd-cdda-pcm";
+ base.ready = true;
+ 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);
+
+#if GCC_CHECK_VERSION(4,6) || defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Winvalid-offsetof"
+#endif
+
+ static constexpr AlsaInputStream *Cast(InputStream *is) {
+ return ContainerCast(is, AlsaInputStream, base);
+ }
+
+#if GCC_CHECK_VERSION(4,6) || defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
+ bool Available() {
+ 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);
+
+ bool IsEOF() {
+ return eof;
+ }
+
+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;
+ AlsaInputStream *stream = new AlsaInputStream(io_thread_get(),
+ uri, mutex, cond,
+ handle, frame_size);
+ return &stream->base;
+}
+
+inline size_t
+AlsaInputStream::Read(void *ptr, size_t size, Error &error)
+{
+ assert(ptr != nullptr);
+
+ int num_frames = 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;
+ base.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(base.mutex);
+ /* wake up the thread that is waiting for more data */
+ base.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);
+}
+
+static void
+alsa_input_close(InputStream *is)
+{
+ AlsaInputStream *ais = AlsaInputStream::Cast(is);
+ delete ais;
+}
+
+static bool
+alsa_input_available(InputStream *is)
+{
+ AlsaInputStream *ais = AlsaInputStream::Cast(is);
+ return ais->Available();
+}
+
+static size_t
+alsa_input_read(InputStream *is, void *ptr, size_t size, Error &error)
+{
+ AlsaInputStream *ais = AlsaInputStream::Cast(is);
+ return ais->Read(ptr, size, error);
+}
+
+static bool
+alsa_input_eof(gcc_unused InputStream *is)
+{
+ AlsaInputStream *ais = AlsaInputStream::Cast(is);
+ return ais->IsEOF();
+}
+
+const struct InputPlugin input_plugin_alsa = {
+ "alsa",
+ nullptr,
+ nullptr,
+ alsa_input_open,
+ alsa_input_close,
+ nullptr,
+ nullptr,
+ nullptr,
+ alsa_input_available,
+ alsa_input_read,
+ alsa_input_eof,
+ nullptr,
+};
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/ArchiveInputPlugin.cxx b/src/input/plugins/ArchiveInputPlugin.cxx
index 5288f2b3b..f49669674 100644
--- a/src/input/ArchiveInputPlugin.cxx
+++ b/src/input/plugins/ArchiveInputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,17 +19,18 @@
#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 "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 <glib.h>
+#include <stdlib.h>
/**
* select correct archive plugin to handle the input stream
@@ -44,19 +45,19 @@ input_archive_open(const char *pathname,
Mutex &mutex, Cond &cond,
Error &error)
{
- const struct archive_plugin *arplug;
+ const ArchivePlugin *arplug;
InputStream *is;
- if (!PathTraits::IsAbsoluteFS(pathname))
+ if (!PathTraitsFS::IsAbsolute(pathname))
return nullptr;
- char *pname = g_strdup(pathname);
+ char *pname = 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);
+ free(pname);
return nullptr;
}
@@ -65,19 +66,19 @@ input_archive_open(const char *pathname,
if (!arplug) {
FormatWarning(archive_domain,
"can't handle archive %s", archive);
- g_free(pname);
+ free(pname);
return nullptr;
}
- auto file = archive_file_open(arplug, archive, error);
+ auto file = archive_file_open(arplug, Path::FromFS(archive), error);
if (file == nullptr) {
- g_free(pname);
+ free(pname);
return nullptr;
}
//setup fileops
is = file->OpenStream(filename, mutex, cond, error);
- g_free(pname);
+ free(pname);
file->Close();
return is;
diff --git a/src/input/ArchiveInputPlugin.hxx b/src/input/plugins/ArchiveInputPlugin.hxx
index 9ac70b2fc..024723726 100644
--- a/src/input/ArchiveInputPlugin.hxx
+++ b/src/input/plugins/ArchiveInputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/input/CdioParanoiaInputPlugin.cxx b/src/input/plugins/CdioParanoiaInputPlugin.cxx
index b3ac57413..767b2600f 100644
--- a/src/input/CdioParanoiaInputPlugin.cxx
+++ b/src/input/plugins/CdioParanoiaInputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -23,15 +23,16 @@
#include "config.h"
#include "CdioParanoiaInputPlugin.hxx"
-#include "InputStream.hxx"
-#include "InputPlugin.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 "ConfigData.hxx"
-#include "ConfigError.hxx"
+#include "config/ConfigData.hxx"
+#include "config/ConfigError.hxx"
#include <stdio.h>
#include <stdint.h>
@@ -86,7 +87,7 @@ static constexpr Domain cdio_domain("cdio");
static bool default_reverse_endian;
-static bool
+static InputPlugin::InitResult
input_cdio_init(const config_param &param, Error &error)
{
const char *value = param.GetBlockValue("default_byte_order");
@@ -99,11 +100,11 @@ input_cdio_init(const config_param &param, Error &error)
error.Format(config_domain, 0,
"Unrecognized 'default_byte_order' setting: %s",
value);
- return false;
+ return InputPlugin::InitResult::ERROR;
}
}
- return true;
+ return InputPlugin::InitResult::SUCCESS;
}
static void
@@ -122,7 +123,7 @@ struct cdio_uri {
static bool
parse_cdio_uri(struct cdio_uri *dest, const char *src, Error &error)
{
- if (!g_str_has_prefix(src, "cdda://"))
+ if (!StringStartsWith(src, "cdda://"))
return false;
src += 7;
diff --git a/src/input/CdioParanoiaInputPlugin.hxx b/src/input/plugins/CdioParanoiaInputPlugin.hxx
index 847802a48..e2804e8c7 100644
--- a/src/input/CdioParanoiaInputPlugin.hxx
+++ b/src/input/plugins/CdioParanoiaInputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/input/CurlInputPlugin.cxx b/src/input/plugins/CurlInputPlugin.cxx
index b78545951..37509e998 100644
--- a/src/input/CurlInputPlugin.cxx
+++ b/src/input/plugins/CurlInputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,11 +19,12 @@
#include "config.h"
#include "CurlInputPlugin.hxx"
-#include "InputStream.hxx"
-#include "InputPlugin.hxx"
-#include "ConfigGlobal.hxx"
-#include "ConfigData.hxx"
+#include "../InputStream.hxx"
+#include "../InputPlugin.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigData.hxx"
#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
#include "IcyMetaDataParser.hxx"
#include "event/SocketMonitor.hxx"
#include "event/TimeoutMonitor.hxx"
@@ -32,6 +33,8 @@
#include "util/ASCII.hxx"
#include "util/CharUtil.hxx"
#include "util/NumberParser.hxx"
+#include "util/CircularBuffer.hxx"
+#include "util/HugeAllocator.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
@@ -45,12 +48,8 @@
#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
@@ -68,69 +67,7 @@ static const size_t CURL_MAX_BUFFERED = 512 * 1024;
*/
static const size_t CURL_RESUME_AT = 384 * 1024;
-/**
- * Buffers created by input_curl_writefunction().
- */
-class CurlInputBuffer {
- /** size of the payload */
- size_t size;
-
- /** how much has been consumed yet? */
- size_t consumed;
-
- /** the payload */
- uint8_t *data;
-
-public:
- CurlInputBuffer(const void *_data, size_t _size)
- :size(_size), consumed(0), data(new uint8_t[size]) {
- memcpy(data, _data, size);
- }
-
- ~CurlInputBuffer() {
- delete[] data;
- }
-
- CurlInputBuffer(const CurlInputBuffer &) = delete;
- CurlInputBuffer &operator=(const CurlInputBuffer &) = delete;
-
- const void *Begin() const {
- return data + consumed;
- }
-
- size_t TotalSize() const {
- return size;
- }
-
- size_t Available() const {
- return size - consumed;
- }
-
- /**
- * Mark a part of the buffer as consumed.
- *
- * @return false if the buffer is now empty
- */
- bool Consume(size_t length) {
- assert(consumed < size);
-
- consumed += length;
- if (consumed < size)
- return true;
-
- assert(consumed == size);
- return false;
- }
-
- bool Read(void *dest, size_t length) {
- assert(consumed + length <= size);
-
- memcpy(dest, data + consumed, length);
- return Consume(length);
- }
-};
-
-struct input_curl {
+struct CurlInputStream {
InputStream base;
/* some buffers which were passed to libcurl, which we have
@@ -141,9 +78,11 @@ struct input_curl {
/** 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;
+ /**
+ * A buffer where input_curl_writefunction() appends
+ * to, and input_curl_read() reads from.
+ */
+ CircularBuffer<uint8_t> buffer;
/**
* Is the connection currently paused? That happens when the
@@ -153,7 +92,7 @@ struct input_curl {
bool paused;
/** error message provided by libcurl */
- char error[CURL_ERROR_SIZE];
+ char error_buffer[CURL_ERROR_SIZE];
/** parser for icy-metadata */
IcyMetaDataParser icy;
@@ -167,16 +106,82 @@ struct input_curl {
Error postponed_error;
- input_curl(const char *url, Mutex &mutex, Cond &cond)
+ CurlInputStream(const char *url, Mutex &mutex, Cond &cond,
+ void *_buffer)
:base(input_plugin_curl, url, mutex, cond),
request_headers(nullptr),
+ buffer((uint8_t *)_buffer, CURL_MAX_BUFFERED),
paused(false),
tag(nullptr) {}
- ~input_curl();
+ ~CurlInputStream();
+
+ CurlInputStream(const CurlInputStream &) = delete;
+ CurlInputStream &operator=(const CurlInputStream &) = delete;
+
+ static InputStream *Open(const char *url, Mutex &mutex, Cond &cond,
+ Error &error);
+
+ bool Check(Error &error);
+
+ bool IsEOF() const {
+ return easy == nullptr && buffer.IsEmpty();
+ }
+
+ bool Seek(InputPlugin::offset_type offset, int whence, Error &error);
+
+ Tag *ReadTag();
+
+ bool IsAvailable() const {
+ return postponed_error.IsDefined() || easy == nullptr ||
+ !buffer.IsEmpty();
+ }
+
+ size_t Read(void *ptr, size_t size, 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();
+
+ void HeaderReceived(const char *name, std::string &&value);
+
+ size_t DataReceived(const void *ptr, size_t size);
+
+ void Resume();
+ bool FillBuffer(Error &error);
+
+ /**
+ * Returns the number of bytes stored in the buffer.
+ *
+ * The caller must lock the mutex.
+ */
+ gcc_pure
+ size_t GetTotalBufferSize() const {
+ return buffer.GetSize();
+ }
+
+ void CopyIcyTag();
- input_curl(const input_curl &) = delete;
- input_curl &operator=(const input_curl &) = delete;
+ /**
+ * A HTTP request is finished.
+ *
+ * Runs in the I/O thread. The caller must not hold locks.
+ */
+ void RequestDone(CURLcode result, long status);
};
class CurlMulti;
@@ -199,8 +204,6 @@ public:
Abandon() would be most appropriate, but it breaks
the second case - is that a CURL bug? is there a
better solution? */
-
- Steal();
}
/**
@@ -253,8 +256,8 @@ public:
curl_multi_cleanup(multi);
}
- bool Add(input_curl *c, Error &error);
- void Remove(input_curl *c);
+ bool Add(CurlInputStream *c, Error &error);
+ void Remove(CurlInputStream *c);
/**
* Check for finished HTTP responses.
@@ -324,7 +327,7 @@ CurlMulti::CurlMulti(EventLoop &_loop, CURLM *_multi)
* Runs in the I/O thread. No lock needed.
*/
gcc_pure
-static struct input_curl *
+static CurlInputStream *
input_curl_find_request(CURL *easy)
{
assert(io_thread_inside());
@@ -334,17 +337,17 @@ input_curl_find_request(CURL *easy)
if (code != CURLE_OK)
return nullptr;
- return (input_curl *)p;
+ return (CurlInputStream *)p;
}
-static void
-input_curl_resume(struct input_curl *c)
+inline void
+CurlInputStream::Resume()
{
assert(io_thread_inside());
- if (c->paused) {
- c->paused = false;
- curl_easy_pause(c->easy, CURLPAUSE_CONT);
+ if (paused) {
+ paused = false;
+ curl_easy_pause(easy, CURLPAUSE_CONT);
if (curl_version_num < 0x072000)
/* libcurl older than 7.32.0 does not update
@@ -406,7 +409,7 @@ CurlSocket::OnSocketReady(unsigned flags)
* Runs in the I/O thread. No lock needed.
*/
inline bool
-CurlMulti::Add(struct input_curl *c, Error &error)
+CurlMulti::Add(CurlInputStream *c, Error &error)
{
assert(io_thread_inside());
assert(c != nullptr);
@@ -429,7 +432,7 @@ CurlMulti::Add(struct input_curl *c, Error &error)
* any thread. Caller must not hold a mutex.
*/
static bool
-input_curl_easy_add_indirect(struct input_curl *c, Error &error)
+input_curl_easy_add_indirect(CurlInputStream *c, Error &error)
{
assert(c != nullptr);
assert(c->easy != nullptr);
@@ -442,92 +445,72 @@ input_curl_easy_add_indirect(struct input_curl *c, Error &error)
}
inline void
-CurlMulti::Remove(input_curl *c)
+CurlMulti::Remove(CurlInputStream *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)
+void
+CurlInputStream::FreeEasy()
{
assert(io_thread_inside());
- assert(c != nullptr);
- if (c->easy == nullptr)
+ if (easy == nullptr)
return;
- curl_multi->Remove(c);
+ curl_multi->Remove(this);
- curl_easy_cleanup(c->easy);
- c->easy = nullptr;
+ curl_easy_cleanup(easy);
+ easy = nullptr;
- curl_slist_free_all(c->request_headers);
- c->request_headers = nullptr;
+ curl_slist_free_all(request_headers);
+ 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)
+void
+CurlInputStream::FreeEasyIndirect()
{
- BlockingCall(io_thread_get(), [c](){
- input_curl_easy_free(c);
+ BlockingCall(io_thread_get(), [this](){
+ FreeEasy();
curl_multi->InvalidateSockets();
});
- assert(c->easy == nullptr);
+ assert(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)
+inline void
+CurlInputStream::RequestDone(CURLcode result, long status)
{
assert(io_thread_inside());
- assert(c != nullptr);
- assert(c->easy == nullptr);
- assert(!c->postponed_error.IsDefined());
+ assert(!postponed_error.IsDefined());
- const ScopeLock protect(c->base.mutex);
+ FreeEasy();
+
+ const ScopeLock protect(base.mutex);
if (result != CURLE_OK) {
- c->postponed_error.Format(curl_domain, result,
- "curl failed: %s", c->error);
+ postponed_error.Format(curl_domain, result,
+ "curl failed: %s", error_buffer);
} else if (status < 200 || status >= 300) {
- c->postponed_error.Format(http_domain, status,
- "got HTTP status %ld",
- status);
+ postponed_error.Format(http_domain, status,
+ "got HTTP status %ld",
+ status);
}
- c->base.ready = true;
-
- c->base.cond.broadcast();
+ base.ready = true;
+ base.cond.broadcast();
}
static void
input_curl_handle_done(CURL *easy_handle, CURLcode result)
{
- struct input_curl *c = input_curl_find_request(easy_handle);
+ CurlInputStream *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);
+ c->RequestDone(result, status);
}
void
@@ -597,7 +580,7 @@ CurlMulti::OnTimeout()
*
*/
-static bool
+static InputPlugin::InitResult
input_curl_init(const config_param &param, Error &error)
{
CURLcode code = curl_global_init(CURL_GLOBAL_ALL);
@@ -605,7 +588,7 @@ input_curl_init(const config_param &param, Error &error)
error.Format(curl_domain, code,
"curl_global_init() failed: %s",
curl_easy_strerror(code));
- return false;
+ return InputPlugin::InitResult::UNAVAILABLE;
}
const auto version_info = curl_version_info(CURLVERSION_FIRST);
@@ -636,12 +619,14 @@ input_curl_init(const config_param &param, Error &error)
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 false;
+ return InputPlugin::InitResult::UNAVAILABLE;
}
curl_multi = new CurlMulti(io_thread_get(), multi);
- return true;
+ return InputPlugin::InitResult::SUCCESS;
}
static void
@@ -656,112 +641,100 @@ input_curl_finish(void)
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)
+CurlInputStream::~CurlInputStream()
{
- size_t total = 0;
+ delete tag;
- for (const auto &i : c->buffers)
- total += i.TotalSize();
+ FreeEasyIndirect();
- return total;
+ buffer.Clear();
+ HugeFree(buffer.Write().data, CURL_MAX_BUFFERED);
}
-input_curl::~input_curl()
+inline bool
+CurlInputStream::Check(Error &error)
{
- delete tag;
+ bool success = !postponed_error.IsDefined();
+ if (!success) {
+ error = std::move(postponed_error);
+ postponed_error.Clear();
+ }
- input_curl_easy_free_indirect(this);
+ return success;
}
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();
- }
+ CurlInputStream &c = *(CurlInputStream *)is;
+ return c.Check(error);
+}
- return success;
+inline Tag *
+CurlInputStream::ReadTag()
+{
+ Tag *result = tag;
+ tag = nullptr;
+ return result;
}
static Tag *
input_curl_tag(InputStream *is)
{
- struct input_curl *c = (struct input_curl *)is;
- Tag *tag = c->tag;
-
- c->tag = nullptr;
- return tag;
+ CurlInputStream &c = *(CurlInputStream *)is;
+ return c.ReadTag();
}
-static bool
-fill_buffer(struct input_curl *c, Error &error)
+inline bool
+CurlInputStream::FillBuffer(Error &error)
{
- while (c->easy != nullptr && c->buffers.empty())
- c->base.cond.wait(c->base.mutex);
+ while (easy != nullptr && buffer.IsEmpty())
+ base.cond.wait(base.mutex);
- if (c->postponed_error.IsDefined()) {
- error = std::move(c->postponed_error);
- c->postponed_error.Clear();
+ if (postponed_error.IsDefined()) {
+ error = std::move(postponed_error);
+ postponed_error.Clear();
return false;
}
- return !c->buffers.empty();
+ return !buffer.IsEmpty();
}
static size_t
-read_from_buffer(IcyMetaDataParser &icy, std::list<CurlInputBuffer> &buffers,
+read_from_buffer(IcyMetaDataParser &icy, CircularBuffer<uint8_t> &buffer,
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;
+ auto r = buffer.Read();
+ if (r.IsEmpty())
+ break;
- chunk = icy.Data(length);
+ if (r.size > length)
+ r.size = length;
+
+ size_t chunk = icy.Data(r.size);
if (chunk > 0) {
- const bool empty = !buffer.Read(dest, chunk);
+ memcpy(dest, r.data, chunk);
+ buffer.Consume(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;
- }
+ r = buffer.Read();
+ if (r.IsEmpty())
+ break;
+ chunk = icy.Meta(r.data, r.size);
+ if (chunk > 0) {
+ buffer.Consume(chunk);
if (length == 0)
break;
}
@@ -770,80 +743,75 @@ read_from_buffer(IcyMetaDataParser &icy, std::list<CurlInputBuffer> &buffers,
return nbytes;
}
-static void
-copy_icy_tag(struct input_curl *c)
+inline void
+CurlInputStream::CopyIcyTag()
{
- Tag *tag = c->icy.ReadTag();
-
- if (tag == nullptr)
+ Tag *new_tag = icy.ReadTag();
+ if (new_tag == nullptr)
return;
- delete c->tag;
+ delete tag;
- if (!c->meta_name.empty() && !tag->HasType(TAG_NAME))
- tag->AddItem(TAG_NAME, c->meta_name.c_str());
+ if (!meta_name.empty() && !new_tag->HasType(TAG_NAME)) {
+ TagBuilder tag_builder(std::move(*new_tag));
+ tag_builder.AddItem(TAG_NAME, meta_name.c_str());
+ *new_tag = tag_builder.Commit();
+ }
- c->tag = tag;
+ tag = new_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();
+ const CurlInputStream &c = *(const CurlInputStream *)is;
+ return c.IsAvailable();
}
-static size_t
-input_curl_read(InputStream *is, void *ptr, size_t size,
- Error &error)
+inline size_t
+CurlInputStream::Read(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;
+ size_t nbytes;
do {
/* fill the buffer */
- success = fill_buffer(c, error);
- if (!success)
+ if (!FillBuffer(error))
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;
- }
+ nbytes = read_from_buffer(icy, buffer, ptr, size);
} while (nbytes == 0);
- if (c->icy.IsDefined())
- copy_icy_tag(c);
+ if (icy.IsDefined())
+ CopyIcyTag();
- is->offset += (InputPlugin::offset_type)nbytes;
+ base.offset += (InputPlugin::offset_type)nbytes;
- if (c->paused && curl_total_buffer_size(c) < CURL_RESUME_AT) {
- c->base.mutex.unlock();
+ if (paused && GetTotalBufferSize() < CURL_RESUME_AT) {
+ base.mutex.unlock();
- BlockingCall(io_thread_get(), [c](){
- input_curl_resume(c);
+ BlockingCall(io_thread_get(), [this](){
+ Resume();
});
- c->base.mutex.lock();
+ base.mutex.lock();
}
return nbytes;
}
+static size_t
+input_curl_read(InputStream *is, void *ptr, size_t size,
+ Error &error)
+{
+ CurlInputStream &c = *(CurlInputStream *)is;
+ return c.Read(ptr, size, error);
+}
+
static void
input_curl_close(InputStream *is)
{
- struct input_curl *c = (struct input_curl *)is;
+ CurlInputStream *c = (CurlInputStream *)is;
delete c;
}
@@ -851,23 +819,62 @@ input_curl_close(InputStream *is)
static bool
input_curl_eof(gcc_unused InputStream *is)
{
- struct input_curl *c = (struct input_curl *)is;
+ const CurlInputStream &c = *(const CurlInputStream *)is;
+ return c.IsEOF();
+}
- return c->easy == nullptr && c->buffers.empty();
+inline void
+CurlInputStream::HeaderReceived(const char *name, std::string &&value)
+{
+ if (StringEqualsCaseASCII(name, "accept-ranges")) {
+ /* a stream with icy-metadata is not seekable */
+ if (!icy.IsDefined())
+ base.seekable = true;
+ } else if (StringEqualsCaseASCII(name, "content-length")) {
+ base.size = base.offset + ParseUint64(value.c_str());
+ } else if (StringEqualsCaseASCII(name, "content-type")) {
+ base.mime = std::move(value);
+ } else if (StringEqualsCaseASCII(name, "icy-name") ||
+ StringEqualsCaseASCII(name, "ice-name") ||
+ StringEqualsCaseASCII(name, "x-audiocast-name")) {
+ meta_name = std::move(value);
+
+ delete tag;
+
+ TagBuilder tag_builder;
+ tag_builder.AddItem(TAG_NAME, meta_name.c_str());
+
+ tag = tag_builder.CommitNew();
+ } else if (StringEqualsCaseASCII(name, "icy-metaint")) {
+ if (icy.IsDefined())
+ return;
+
+ size_t icy_metaint = ParseUint64(value.c_str());
+ FormatDebug(curl_domain, "icy-metaint=%zu", icy_metaint);
+
+ if (icy_metaint > 0) {
+ icy.Start(icy_metaint);
+
+ /* a stream with icy-metadata is not
+ seekable */
+ base.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)
{
- struct input_curl *c = (struct input_curl *)stream;
- char name[64];
+ CurlInputStream &c = *(CurlInputStream *)stream;
size *= nmemb;
const char *header = (const char *)ptr;
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;
@@ -887,54 +894,41 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
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);
+ c.HeaderReceived(name, std::string(value, end));
+ return size;
+}
- delete c->tag;
+inline size_t
+CurlInputStream::DataReceived(const void *ptr, size_t size)
+{
+ assert(size > 0);
- 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;
+ const ScopeLock protect(base.mutex);
- if ((size_t)(end - header) >= sizeof(buffer) ||
- c->icy.IsDefined())
- return size;
+ if (size > buffer.GetSpace()) {
+ paused = true;
+ return CURL_WRITEFUNC_PAUSE;
+ }
- memcpy(buffer, value, end - value);
- buffer[end - value] = 0;
+ auto w = buffer.Write();
+ assert(!w.IsEmpty());
- icy_metaint = ParseUint64(buffer);
- FormatDebug(curl_domain, "icy-metaint=%zu", icy_metaint);
+ size_t nbytes = std::min(w.size, size);
+ memcpy(w.data, ptr, nbytes);
+ buffer.Append(nbytes);
- if (icy_metaint > 0) {
- c->icy.Start(icy_metaint);
+ const size_t remaining = size - nbytes;
+ if (remaining > 0) {
+ w = buffer.Write();
+ assert(!w.IsEmpty());
+ assert(w.size >= remaining);
- /* a stream with icy-metadata is not
- seekable */
- c->base.seekable = false;
- }
+ memcpy(w.data, (const uint8_t *)ptr + nbytes, remaining);
+ buffer.Append(size);
}
+ base.ready = true;
+ base.cond.broadcast();
return size;
}
@@ -942,71 +936,58 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
static size_t
input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream)
{
- struct input_curl *c = (struct input_curl *)stream;
+ CurlInputStream &c = *(CurlInputStream *)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;
+ return c.DataReceived(ptr, size);
}
-static bool
-input_curl_easy_init(struct input_curl *c, Error &error)
+bool
+CurlInputStream::InitEasy(Error &error)
{
- CURLcode code;
-
- c->easy = curl_easy_init();
- if (c->easy == nullptr) {
+ easy = curl_easy_init();
+ if (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,
+ curl_easy_setopt(easy, CURLOPT_PRIVATE, (void *)this);
+ curl_easy_setopt(easy, CURLOPT_USERAGENT,
"Music Player Daemon " VERSION);
- curl_easy_setopt(c->easy, CURLOPT_HEADERFUNCTION,
+ curl_easy_setopt(easy, CURLOPT_HEADERFUNCTION,
input_curl_headerfunction);
- curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, c);
- curl_easy_setopt(c->easy, CURLOPT_WRITEFUNCTION,
+ curl_easy_setopt(easy, CURLOPT_WRITEHEADER, this);
+ curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION,
input_curl_writefunction);
- curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, c);
- curl_easy_setopt(c->easy, CURLOPT_HTTP200ALIASES, http_200_aliases);
- curl_easy_setopt(c->easy, CURLOPT_FOLLOWLOCATION, 1);
- curl_easy_setopt(c->easy, CURLOPT_NETRC, 1);
- curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5);
- curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, true);
- curl_easy_setopt(c->easy, CURLOPT_ERRORBUFFER, c->error);
- curl_easy_setopt(c->easy, CURLOPT_NOPROGRESS, 1l);
- curl_easy_setopt(c->easy, CURLOPT_NOSIGNAL, 1l);
- curl_easy_setopt(c->easy, CURLOPT_CONNECTTIMEOUT, 10l);
+ curl_easy_setopt(easy, CURLOPT_WRITEDATA, this);
+ curl_easy_setopt(easy, CURLOPT_HTTP200ALIASES, http_200_aliases);
+ curl_easy_setopt(easy, CURLOPT_FOLLOWLOCATION, 1);
+ curl_easy_setopt(easy, CURLOPT_NETRC, 1);
+ curl_easy_setopt(easy, CURLOPT_MAXREDIRS, 5);
+ curl_easy_setopt(easy, CURLOPT_FAILONERROR, true);
+ 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(c->easy, CURLOPT_PROXY, proxy);
+ curl_easy_setopt(easy, CURLOPT_PROXY, proxy);
if (proxy_port > 0)
- curl_easy_setopt(c->easy, CURLOPT_PROXYPORT, (long)proxy_port);
+ 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(c->easy, CURLOPT_PROXYUSERPWD, proxy_auth_str);
+ curl_easy_setopt(easy, CURLOPT_PROXYUSERPWD, proxy_auth_str);
}
- code = curl_easy_setopt(c->easy, CURLOPT_URL, c->base.uri.c_str());
+ CURLcode code = curl_easy_setopt(easy, CURLOPT_URL, base.uri.c_str());
if (code != CURLE_OK) {
error.Format(curl_domain, code,
"curl_easy_setopt() failed: %s",
@@ -1014,29 +995,25 @@ input_curl_easy_init(struct input_curl *c, Error &error)
return false;
}
- c->request_headers = nullptr;
- c->request_headers = curl_slist_append(c->request_headers,
+ request_headers = nullptr;
+ request_headers = curl_slist_append(request_headers,
"Icy-Metadata: 1");
- curl_easy_setopt(c->easy, CURLOPT_HTTPHEADER, c->request_headers);
+ curl_easy_setopt(easy, CURLOPT_HTTPHEADER, request_headers);
return true;
}
-static bool
-input_curl_seek(InputStream *is, InputPlugin::offset_type offset,
- int whence,
- Error &error)
+inline bool
+CurlInputStream::Seek(InputPlugin::offset_type offset, int whence,
+ Error &error)
{
- struct input_curl *c = (struct input_curl *)is;
- bool ret;
+ assert(base.ready);
- assert(is->ready);
-
- if (whence == SEEK_SET && offset == is->offset)
+ if (whence == SEEK_SET && offset == base.offset)
/* no-op */
return true;
- if (!is->seekable)
+ if (!base.seekable)
return false;
/* calculate the absolute offset */
@@ -1046,15 +1023,15 @@ input_curl_seek(InputStream *is, InputPlugin::offset_type offset,
break;
case SEEK_CUR:
- offset += is->offset;
+ offset += base.offset;
break;
case SEEK_END:
- if (is->size < 0)
+ if (base.size < 0)
/* stream size is not known */
return false;
- offset += is->size;
+ offset += base.size;
break;
default:
@@ -1066,83 +1043,89 @@ input_curl_seek(InputStream *is, InputPlugin::offset_type offset,
/* 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;
+ while (offset > base.offset) {
+ auto r = buffer.Read();
+ if (r.IsEmpty())
+ break;
- const bool empty = !buffer.Consume(length);
- if (empty)
- c->buffers.pop_front();
+ const size_t nbytes =
+ offset - base.offset < (InputPlugin::offset_type)r.size
+ ? offset - base.offset
+ : r.size;
- is->offset += length;
+ buffer.Consume(nbytes);
+ base.offset += nbytes;
}
- if (offset == is->offset)
+ if (offset == base.offset)
return true;
/* close the old connection and open a new one */
- c->base.mutex.unlock();
+ base.mutex.unlock();
- input_curl_easy_free_indirect(c);
- c->buffers.clear();
+ FreeEasyIndirect();
+ buffer.Clear();
- is->offset = offset;
- if (is->offset == is->size) {
+ base.offset = offset;
+ if (base.offset == base.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)
+ if (!InitEasy(error))
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);
+ if (base.offset > 0) {
+ sprintf(range, "%lld-", (long long)base.offset);
+ curl_easy_setopt(easy, CURLOPT_RANGE, range);
}
- c->base.ready = false;
+ base.ready = false;
- if (!input_curl_easy_add_indirect(c, error))
+ if (!input_curl_easy_add_indirect(this, error))
return false;
- c->base.mutex.lock();
+ base.mutex.lock();
- while (!c->base.ready)
- c->base.cond.wait(c->base.mutex);
+ while (!base.ready)
+ base.cond.wait(base.mutex);
- if (c->postponed_error.IsDefined()) {
- error = std::move(c->postponed_error);
- c->postponed_error.Clear();
+ if (postponed_error.IsDefined()) {
+ error = std::move(postponed_error);
+ postponed_error.Clear();
return false;
}
return true;
}
-static InputStream *
-input_curl_open(const char *url, Mutex &mutex, Cond &cond,
+static bool
+input_curl_seek(InputStream *is, InputPlugin::offset_type offset,
+ int whence,
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);
+ CurlInputStream &c = *(CurlInputStream *)is;
+ return c.Seek(offset, whence, error);
+}
- if (!input_curl_easy_init(c, error)) {
- delete c;
+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;
}
- if (!input_curl_easy_add_indirect(c, error)) {
+ CurlInputStream *c = new CurlInputStream(url, mutex, cond, buffer);
+
+ if (!c->InitEasy(error) || !input_curl_easy_add_indirect(c, error)) {
delete c;
return nullptr;
}
@@ -1150,6 +1133,17 @@ input_curl_open(const char *url, Mutex &mutex, Cond &cond,
return &c->base;
}
+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,
diff --git a/src/input/CurlInputPlugin.hxx b/src/input/plugins/CurlInputPlugin.hxx
index 30e917257..4acb18bfc 100644
--- a/src/input/CurlInputPlugin.hxx
+++ b/src/input/plugins/CurlInputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/input/DespotifyInputPlugin.cxx b/src/input/plugins/DespotifyInputPlugin.cxx
index b08299516..152fda95f 100644
--- a/src/input/DespotifyInputPlugin.cxx
+++ b/src/input/plugins/DespotifyInputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011-2013 The Music Player Daemon Project
+ * 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
@@ -19,30 +19,29 @@
#include "config.h"
#include "DespotifyInputPlugin.hxx"
-#include "DespotifyUtils.hxx"
-#include "InputStream.hxx"
-#include "InputPlugin.hxx"
+#include "lib/despotify/DespotifyUtils.hxx"
+#include "../InputStream.hxx"
+#include "../InputPlugin.hxx"
#include "tag/Tag.hxx"
+#include "util/StringUtil.hxx"
#include "Log.hxx"
extern "C" {
#include <despotify.h>
}
-#include <glib.h>
-
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
-struct DespotifyInputStream {
+class DespotifyInputStream {
InputStream base;
struct despotify_session *session;
struct ds_track *track;
- Tag *tag;
+ Tag tag;
struct ds_pcm_data pcm;
size_t len_available;
bool eof;
@@ -53,7 +52,7 @@ struct DespotifyInputStream {
ds_track *_track)
:base(input_plugin_despotify, uri, mutex, cond),
session(_session), track(_track),
- tag(mpd_despotify_tag_from_track(track)),
+ tag(mpd_despotify_tag_from_track(*track)),
len_available(0), eof(false) {
memset(&pcm, 0, sizeof(pcm));
@@ -63,30 +62,53 @@ struct DespotifyInputStream {
base.ready = true;
}
+public:
~DespotifyInputStream() {
- delete tag;
-
despotify_free_track(track);
}
+
+ static InputStream *Open(const char *url, Mutex &mutex, Cond &cond,
+ Error &error);
+
+ bool IsEOF() const {
+ return eof;
+ }
+
+ size_t Read(void *ptr, size_t size, Error &error);
+
+ Tag *ReadTag() {
+ if (tag.IsEmpty())
+ return nullptr;
+
+ Tag *result = new Tag(std::move(tag));
+ tag.Clear();
+ return result;
+ }
+
+ void Callback(int sig);
+
+private:
+ void FillBuffer();
};
-static void
-refill_buffer(DespotifyInputStream *ctx)
+inline void
+DespotifyInputStream::FillBuffer()
{
/* Wait until there is data */
while (1) {
- int rc = despotify_get_pcm(ctx->session, &ctx->pcm);
+ int rc = despotify_get_pcm(session, &pcm);
- if (rc == 0 && ctx->pcm.len) {
- ctx->len_available = ctx->pcm.len;
+ if (rc == 0 && pcm.len) {
+ len_available = pcm.len;
break;
}
- if (ctx->eof == true)
+
+ if (eof == true)
break;
if (rc < 0) {
LogDebug(despotify_domain, "despotify_get_pcm error");
- ctx->eof = true;
+ eof = true;
break;
}
@@ -95,11 +117,9 @@ refill_buffer(DespotifyInputStream *ctx)
}
}
-static void callback(gcc_unused struct despotify_session* ds,
- int sig, gcc_unused void* data, void* callback_data)
+inline void
+DespotifyInputStream::Callback(int sig)
{
- DespotifyInputStream *ctx = (DespotifyInputStream *)callback_data;
-
switch (sig) {
case DESPOTIFY_NEW_TRACK:
break;
@@ -109,35 +129,38 @@ static void callback(gcc_unused struct despotify_session* ds,
case DESPOTIFY_TRACK_PLAY_ERROR:
LogWarning(despotify_domain, "Track play error");
- ctx->eof = true;
- ctx->len_available = 0;
+ eof = true;
+ len_available = 0;
break;
case DESPOTIFY_END_OF_PLAYLIST:
- ctx->eof = true;
- FormatDebug(despotify_domain, "End of playlist: %d", ctx->eof);
+ eof = true;
+ LogDebug(despotify_domain, "End of playlist");
break;
}
}
-
-static InputStream *
-input_despotify_open(const char *url,
- Mutex &mutex, Cond &cond,
- gcc_unused Error &error)
+static void callback(gcc_unused struct despotify_session* ds,
+ int sig, gcc_unused void* data, void* callback_data)
{
- struct despotify_session *session;
- struct ds_link *ds_link;
- struct ds_track *track;
+ DespotifyInputStream *ctx = (DespotifyInputStream *)callback_data;
- if (!g_str_has_prefix(url, "spt://"))
+ ctx->Callback(sig);
+}
+
+inline InputStream *
+DespotifyInputStream::Open(const char *url,
+ Mutex &mutex, Cond &cond,
+ gcc_unused Error &error)
+{
+ if (!StringStartsWith(url, "spt://"))
return nullptr;
- session = mpd_despotify_get_session();
- if (!session)
+ despotify_session *session = mpd_despotify_get_session();
+ if (session == nullptr)
return nullptr;
- ds_link = despotify_link_from_uri(url + 6);
+ ds_link *ds_link = despotify_link_from_uri(url + 6);
if (!ds_link) {
FormatDebug(despotify_domain, "Can't find %s", url);
return nullptr;
@@ -147,7 +170,7 @@ input_despotify_open(const char *url,
return nullptr;
}
- track = despotify_link_get_track(session, ds_link);
+ ds_track *track = despotify_link_get_track(session, ds_link);
despotify_free_link(ds_link);
if (!track)
return nullptr;
@@ -170,26 +193,34 @@ input_despotify_open(const char *url,
return &ctx->base;
}
-static size_t
-input_despotify_read(InputStream *is, void *ptr, size_t size,
- gcc_unused Error &error)
+static InputStream *
+input_despotify_open(const char *url, Mutex &mutex, Cond &cond, Error &error)
{
- DespotifyInputStream *ctx = (DespotifyInputStream *)is;
- size_t to_cpy = size;
+ return DespotifyInputStream::Open(url, mutex, cond, error);
+}
- if (ctx->len_available == 0)
- refill_buffer(ctx);
+inline size_t
+DespotifyInputStream::Read(void *ptr, size_t size, gcc_unused Error &error)
+{
+ if (len_available == 0)
+ FillBuffer();
- if (ctx->len_available < size)
- to_cpy = ctx->len_available;
- memcpy(ptr, ctx->pcm.buf, to_cpy);
- ctx->len_available -= to_cpy;
+ size_t to_cpy = std::min(size, len_available);
+ memcpy(ptr, pcm.buf, to_cpy);
+ len_available -= to_cpy;
- is->offset += to_cpy;
+ base.offset += to_cpy;
return to_cpy;
}
+static size_t
+input_despotify_read(InputStream *is, void *ptr, size_t size, Error &error)
+{
+ DespotifyInputStream *ctx = (DespotifyInputStream *)is;
+ return ctx->Read(ptr, size, error);
+}
+
static void
input_despotify_close(InputStream *is)
{
@@ -204,22 +235,19 @@ input_despotify_eof(InputStream *is)
{
DespotifyInputStream *ctx = (DespotifyInputStream *)is;
- return ctx->eof;
+ return ctx->IsEOF();
}
static Tag *
input_despotify_tag(InputStream *is)
{
DespotifyInputStream *ctx = (DespotifyInputStream *)is;
- Tag *tag = ctx->tag;
-
- ctx->tag = nullptr;
- return tag;
+ return ctx->ReadTag();
}
const InputPlugin input_plugin_despotify = {
- "spt",
+ "despotify",
nullptr,
nullptr,
input_despotify_open,
diff --git a/src/input/DespotifyInputPlugin.hxx b/src/input/plugins/DespotifyInputPlugin.hxx
index f1911f235..83f963520 100644
--- a/src/input/DespotifyInputPlugin.hxx
+++ b/src/input/plugins/DespotifyInputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/input/FfmpegInputPlugin.cxx b/src/input/plugins/FfmpegInputPlugin.cxx
index 8f9cd0b86..dab4b59fb 100644
--- a/src/input/FfmpegInputPlugin.cxx
+++ b/src/input/plugins/FfmpegInputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -22,19 +22,17 @@
#include "config.h"
#include "FfmpegInputPlugin.hxx"
-#include "InputStream.hxx"
-#include "InputPlugin.hxx"
+#include "../InputStream.hxx"
+#include "../InputPlugin.hxx"
+#include "util/StringUtil.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;
@@ -71,7 +69,7 @@ input_ffmpeg_supported(void)
return avio_enum_protocols(&opaque, 0) != nullptr;
}
-static bool
+static InputPlugin::InitResult
input_ffmpeg_init(gcc_unused const config_param &param,
Error &error)
{
@@ -80,10 +78,10 @@ input_ffmpeg_init(gcc_unused const config_param &param,
/* disable this plugin if there's no registered protocol */
if (!input_ffmpeg_supported()) {
error.Set(ffmpeg_domain, "No protocol");
- return false;
+ return InputPlugin::InitResult::UNAVAILABLE;
}
- return true;
+ return InputPlugin::InitResult::SUCCESS;
}
static InputStream *
@@ -91,12 +89,12 @@ 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://"))
+ if (!StringStartsWith(uri, "gopher://") &&
+ !StringStartsWith(uri, "rtp://") &&
+ !StringStartsWith(uri, "rtsp://") &&
+ !StringStartsWith(uri, "rtmp://") &&
+ !StringStartsWith(uri, "rtmpt://") &&
+ !StringStartsWith(uri, "rtmps://"))
return nullptr;
AVIOContext *h;
diff --git a/src/input/FfmpegInputPlugin.hxx b/src/input/plugins/FfmpegInputPlugin.hxx
index 9bc2eeaea..43f829e89 100644
--- a/src/input/FfmpegInputPlugin.hxx
+++ b/src/input/plugins/FfmpegInputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/input/FileInputPlugin.cxx b/src/input/plugins/FileInputPlugin.cxx
index 26e40d609..780e93263 100644
--- a/src/input/FileInputPlugin.cxx
+++ b/src/input/plugins/FileInputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,8 +19,8 @@
#include "config.h" /* must be first for large file support */
#include "FileInputPlugin.hxx"
-#include "InputStream.hxx"
-#include "InputPlugin.hxx"
+#include "../InputStream.hxx"
+#include "../InputPlugin.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "fs/Traits.hxx"
@@ -30,8 +30,6 @@
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
-#include <string.h>
-#include <glib.h>
static constexpr Domain file_domain("file");
@@ -62,7 +60,7 @@ input_file_open(const char *filename,
int fd, ret;
struct stat st;
- if (!PathTraits::IsAbsoluteFS(filename))
+ if (!PathTraitsFS::IsAbsolute(filename))
return nullptr;
fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0);
diff --git a/src/input/FileInputPlugin.hxx b/src/input/plugins/FileInputPlugin.hxx
index f76f4dd0e..4aef94637 100644
--- a/src/input/FileInputPlugin.hxx
+++ b/src/input/plugins/FileInputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/input/plugins/MmsInputPlugin.cxx b/src/input/plugins/MmsInputPlugin.cxx
new file mode 100644
index 000000000..845fe5d5d
--- /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, uri, mutex, cond,
+ MMS_BUFFER_SIZE) {
+ }
+
+protected:
+ virtual bool Open(gcc_unused Error &error) override;
+ virtual size_t Read(void *ptr, size_t size, Error &error) override;
+
+ virtual void Close() {
+ 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::Read(void *ptr, size_t size, Error &error)
+{
+ int nbytes = mmsx_read(nullptr, mms, (char *)ptr, 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,
+ ThreadInputStream::Close,
+ ThreadInputStream::Check,
+ nullptr,
+ nullptr,
+ ThreadInputStream::Available,
+ ThreadInputStream::Read,
+ ThreadInputStream::IsEOF,
+ nullptr,
+};
diff --git a/src/input/MmsInputPlugin.hxx b/src/input/plugins/MmsInputPlugin.hxx
index e3d3ba3c8..b4017ffd6 100644
--- a/src/input/MmsInputPlugin.hxx
+++ b/src/input/plugins/MmsInputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/input/plugins/NfsInputPlugin.cxx b/src/input/plugins/NfsInputPlugin.cxx
new file mode 100644
index 000000000..c16698b1c
--- /dev/null
+++ b/src/input/plugins/NfsInputPlugin.cxx
@@ -0,0 +1,200 @@
+/*
+ * 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 "../InputStream.hxx"
+#include "../InputPlugin.hxx"
+#include "lib/nfs/Domain.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+
+extern "C" {
+#include <nfsc/libnfs.h>
+}
+
+#include <sys/stat.h>
+#include <fcntl.h>
+
+class NfsInputStream {
+ InputStream base;
+
+ nfs_context *ctx;
+ nfsfh *fh;
+
+public:
+ NfsInputStream(const char *uri,
+ Mutex &mutex, Cond &cond,
+ nfs_context *_ctx, nfsfh *_fh,
+ InputStream::offset_type size)
+ :base(input_plugin_nfs, uri, mutex, cond),
+ ctx(_ctx), fh(_fh) {
+ base.ready = true;
+ base.seekable = true;
+ base.size = size;
+ }
+
+ ~NfsInputStream() {
+ nfs_close(ctx, fh);
+ nfs_destroy_context(ctx);
+ }
+
+ InputStream *GetBase() {
+ return &base;
+ }
+
+ bool IsEOF() const {
+ return base.offset >= base.size;
+ }
+
+ size_t Read(void *ptr, size_t size, Error &error) {
+ int nbytes = nfs_read(ctx, fh, size, (char *)ptr);
+ if (nbytes < 0) {
+ error.SetErrno(-nbytes, "nfs_read() failed");
+ nbytes = 0;
+ }
+
+ return nbytes;
+ }
+
+ bool Seek(InputStream::offset_type offset, int whence, Error &error) {
+ uint64_t current_offset;
+ int result = nfs_lseek(ctx, fh, offset, whence, &current_offset);
+ if (result < 0) {
+ error.SetErrno(-result, "smbc_lseek() failed");
+ return false;
+ }
+
+ base.offset = current_offset;
+ return true;
+ }
+};
+
+/*
+ * InputPlugin methods
+ *
+ */
+
+static InputStream *
+input_nfs_open(const char *uri,
+ Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ if (!StringStartsWith(uri, "nfs://"))
+ return nullptr;
+
+ uri += 6;
+
+ const char *slash = strchr(uri, '/');
+ if (slash == nullptr) {
+ error.Set(nfs_domain, "Malformed nfs:// URI");
+ return nullptr;
+ }
+
+ const std::string server(uri, slash);
+
+ uri = slash;
+ slash = strrchr(uri + 1, '/');
+ if (slash == nullptr || slash[1] == 0) {
+ error.Set(nfs_domain, "Malformed nfs:// URI");
+ return nullptr;
+ }
+
+ const std::string mount(uri, slash);
+ uri = slash;
+
+ nfs_context *ctx = nfs_init_context();
+ if (ctx == nullptr) {
+ error.Set(nfs_domain, "nfs_init_context() failed");
+ return nullptr;
+ }
+
+ int result = nfs_mount(ctx, server.c_str(), mount.c_str());
+ if (result < 0) {
+ nfs_destroy_context(ctx);
+ error.SetErrno(-result, "nfs_mount() failed");
+ return nullptr;
+ }
+
+ nfsfh *fh;
+ result = nfs_open(ctx, uri, O_RDONLY, &fh);
+ if (result < 0) {
+ nfs_destroy_context(ctx);
+ error.SetErrno(-result, "nfs_open() failed");
+ return nullptr;
+ }
+
+ struct stat st;
+ result = nfs_fstat(ctx, fh, &st);
+ if (result < 0) {
+ nfs_close(ctx, fh);
+ nfs_destroy_context(ctx);
+ error.SetErrno(-result, "nfs_fstat() failed");
+ return nullptr;
+ }
+
+ auto is = new NfsInputStream(uri, mutex, cond, ctx, fh, st.st_size);
+ return is->GetBase();
+}
+
+static size_t
+input_nfs_read(InputStream *is, void *ptr, size_t size,
+ Error &error)
+{
+ NfsInputStream &s = *(NfsInputStream *)is;
+ return s.Read(ptr, size, error);
+}
+
+static void
+input_nfs_close(InputStream *is)
+{
+ NfsInputStream *s = (NfsInputStream *)is;
+ delete s;
+}
+
+static bool
+input_nfs_eof(InputStream *is)
+{
+ NfsInputStream &s = *(NfsInputStream *)is;
+ return s.IsEOF();
+}
+
+static bool
+input_nfs_seek(InputStream *is,
+ InputPlugin::offset_type offset, int whence,
+ Error &error)
+{
+ NfsInputStream &s = *(NfsInputStream *)is;
+ return s.Seek(offset, whence, error);
+}
+
+const InputPlugin input_plugin_nfs = {
+ "nfs",
+ nullptr,
+ nullptr,
+ input_nfs_open,
+ input_nfs_close,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ input_nfs_read,
+ input_nfs_eof,
+ input_nfs_seek,
+};
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/RewindInputPlugin.cxx b/src/input/plugins/RewindInputPlugin.cxx
index e11f56631..1a930ac53 100644
--- a/src/input/RewindInputPlugin.cxx
+++ b/src/input/plugins/RewindInputPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,9 +19,8 @@
#include "config.h"
#include "RewindInputPlugin.hxx"
-#include "InputStream.hxx"
-#include "InputPlugin.hxx"
-#include "tag/Tag.hxx"
+#include "../InputStream.hxx"
+#include "../InputPlugin.hxx"
#include <assert.h>
#include <string.h>
diff --git a/src/input/RewindInputPlugin.hxx b/src/input/plugins/RewindInputPlugin.hxx
index 2d461970a..f19705154 100644
--- a/src/input/RewindInputPlugin.hxx
+++ b/src/input/plugins/RewindInputPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/input/plugins/SmbclientInputPlugin.cxx b/src/input/plugins/SmbclientInputPlugin.cxx
new file mode 100644
index 000000000..9709d089a
--- /dev/null
+++ b/src/input/plugins/SmbclientInputPlugin.cxx
@@ -0,0 +1,195 @@
+/*
+ * 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 {
+ InputStream base;
+
+ SMBCCTX *ctx;
+ int fd;
+
+public:
+ SmbclientInputStream(const char *uri,
+ Mutex &mutex, Cond &cond,
+ SMBCCTX *_ctx, int _fd, const struct stat &st)
+ :base(input_plugin_smbclient, uri, mutex, cond),
+ ctx(_ctx), fd(_fd) {
+ base.ready = true;
+ base.seekable = true;
+ base.size = st.st_size;
+ }
+
+ ~SmbclientInputStream() {
+ smbclient_mutex.lock();
+ smbc_close(fd);
+ smbc_free_context(ctx, 1);
+ smbclient_mutex.unlock();
+ }
+
+ InputStream *GetBase() {
+ return &base;
+ }
+
+ bool IsEOF() const {
+ return base.offset >= base.size;
+ }
+
+ size_t Read(void *ptr, size_t size, Error &error) {
+ smbclient_mutex.lock();
+ ssize_t nbytes = smbc_read(fd, ptr, size);
+ smbclient_mutex.unlock();
+ if (nbytes < 0) {
+ error.SetErrno("smbc_read() failed");
+ nbytes = 0;
+ }
+
+ return nbytes;
+ }
+
+ bool Seek(InputStream::offset_type offset, int whence, Error &error) {
+ smbclient_mutex.lock();
+ off_t result = smbc_lseek(fd, offset, whence);
+ smbclient_mutex.unlock();
+ if (result < 0) {
+ error.SetErrno("smbc_lseek() failed");
+ return false;
+ }
+
+ base.offset = result;
+ return true;
+ }
+};
+
+/*
+ * 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;
+ }
+
+ auto s = new SmbclientInputStream(uri, mutex, cond, ctx, fd, st);
+ return s->GetBase();
+}
+
+static size_t
+input_smbclient_read(InputStream *is, void *ptr, size_t size,
+ Error &error)
+{
+ SmbclientInputStream &s = *(SmbclientInputStream *)is;
+ return s.Read(ptr, size, error);
+}
+
+static void
+input_smbclient_close(InputStream *is)
+{
+ SmbclientInputStream *s = (SmbclientInputStream *)is;
+ delete s;
+}
+
+static bool
+input_smbclient_eof(InputStream *is)
+{
+ SmbclientInputStream &s = *(SmbclientInputStream *)is;
+ return s.IsEOF();
+}
+
+static bool
+input_smbclient_seek(InputStream *is,
+ InputPlugin::offset_type offset, int whence,
+ Error &error)
+{
+ SmbclientInputStream &s = *(SmbclientInputStream *)is;
+ return s.Seek(offset, whence, error);
+}
+
+const InputPlugin input_plugin_smbclient = {
+ "smbclient",
+ input_smbclient_init,
+ nullptr,
+ input_smbclient_open,
+ input_smbclient_close,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ input_smbclient_read,
+ input_smbclient_eof,
+ input_smbclient_seek,
+};
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