diff options
Diffstat (limited to 'src/input')
-rw-r--r-- | src/input/Init.cxx | 69 | ||||
-rw-r--r-- | src/input/Init.hxx | 36 | ||||
-rw-r--r-- | src/input/InputPlugin.hxx | 91 | ||||
-rw-r--r-- | src/input/InputStream.cxx | 214 | ||||
-rw-r--r-- | src/input/InputStream.hxx | 301 | ||||
-rw-r--r-- | src/input/Registry.cxx | 93 | ||||
-rw-r--r-- | src/input/Registry.hxx | 43 | ||||
-rw-r--r-- | src/input/TextInputStream.cxx | 78 | ||||
-rw-r--r-- | src/input/TextInputStream.hxx | 56 | ||||
-rw-r--r-- | src/input/plugins/AlsaInputPlugin.cxx | 431 | ||||
-rw-r--r-- | src/input/plugins/AlsaInputPlugin.hxx | 28 | ||||
-rw-r--r-- | src/input/plugins/ArchiveInputPlugin.cxx (renamed from src/input/ArchiveInputPlugin.cxx) | 30 | ||||
-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) | 13 | ||||
-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) | 26 | ||||
-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) | 22 | ||||
-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.cxx (renamed from src/input/MmsInputPlugin.cxx) | 19 | ||||
-rw-r--r-- | src/input/plugins/MmsInputPlugin.hxx (renamed from src/input/MmsInputPlugin.hxx) | 2 | ||||
-rw-r--r-- | src/input/plugins/NfsInputPlugin.cxx | 202 | ||||
-rw-r--r-- | src/input/plugins/NfsInputPlugin.hxx | 25 | ||||
-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.cxx | 195 | ||||
-rw-r--r-- | src/input/plugins/SmbclientInputPlugin.hxx | 25 |
31 files changed, 2042 insertions, 130 deletions
diff --git a/src/input/Init.cxx b/src/input/Init.cxx new file mode 100644 index 000000000..4b8eac320 --- /dev/null +++ b/src/input/Init.cxx @@ -0,0 +1,69 @@ +/* + * 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 <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 = ∅ + } else if (!param->GetBlockValue("enabled", true)) + /* the plugin is disabled in mpd.conf */ + continue; + + if (plugin->init == nullptr || plugin->init(*param, error)) + input_plugins_enabled[i] = true; + else { + error.FormatPrefix("Failed to initialize input plugin '%s': ", + plugin->name); + return false; + } + } + + return true; +} + +void input_stream_global_finish(void) +{ + input_plugins_for_each_enabled(plugin) + if (plugin->finish != 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..83e0ab26e --- /dev/null +++ b/src/input/InputPlugin.hxx @@ -0,0 +1,91 @@ +/* + * 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> + +struct config_param; +struct InputStream; +class Error; +struct Tag; + +struct InputPlugin { + 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 + */ + bool (*init)(const config_param ¶m, 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/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..25b9b42fe --- /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.data[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/plugins/AlsaInputPlugin.cxx b/src/input/plugins/AlsaInputPlugin.cxx new file mode 100644 index 000000000..da2d9a926 --- /dev/null +++ b/src/input/plugins/AlsaInputPlugin.cxx @@ -0,0 +1,431 @@ +/* + * 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.seekable = false; + base.size = -1; + 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..df1a72585 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,17 @@ #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 "util/Alloc.hxx" #include "Log.hxx" -#include <glib.h> +#include <stdlib.h> /** * select correct archive plugin to handle the input stream @@ -47,16 +47,16 @@ input_archive_open(const char *pathname, const struct archive_plugin *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 +65,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); 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..9267b50e1 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> @@ -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..a3c6c1d1a 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" @@ -199,8 +200,6 @@ public: Abandon() would be most appropriate, but it breaks the second case - is that a CURL bug? is there a better solution? */ - - Steal(); } /** @@ -780,8 +779,11 @@ copy_icy_tag(struct input_curl *c) delete c->tag; - if (!c->meta_name.empty() && !tag->HasType(TAG_NAME)) - tag->AddItem(TAG_NAME, c->meta_name.c_str()); + if (!c->meta_name.empty() && !tag->HasType(TAG_NAME)) { + TagBuilder tag_builder(std::move(*tag)); + tag_builder.AddItem(TAG_NAME, c->meta_name.c_str()); + *tag = tag_builder.Commit(); + } c->tag = tag; } @@ -910,8 +912,10 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) delete c->tag; - c->tag = new Tag(); - c->tag->AddItem(TAG_NAME, c->meta_name.c_str()); + TagBuilder tag_builder; + tag_builder.AddItem(TAG_NAME, c->meta_name.c_str()); + + c->tag = tag_builder.CommitNew(); } else if (StringEqualsCaseASCII(name, "icy-metaint")) { char buffer[64]; size_t icy_metaint; 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..0d0a4375e 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; @@ -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/MmsInputPlugin.cxx b/src/input/plugins/MmsInputPlugin.cxx index e97c1eb3f..a7068cb2b 100644 --- a/src/input/MmsInputPlugin.cxx +++ b/src/input/plugins/MmsInputPlugin.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,14 @@ #include "config.h" #include "MmsInputPlugin.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 <glib.h> #include <libmms/mmsx.h> -#include <string.h> -#include <errno.h> - struct MmsInputStream { InputStream base; @@ -61,10 +58,10 @@ 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://")) + if (!StringStartsWith(url, "mms://") && + !StringStartsWith(url, "mmsh://") && + !StringStartsWith(url, "mmst://") && + !StringStartsWith(url, "mmsu://")) return nullptr; const auto mms = mmsx_connect(nullptr, nullptr, url, 128 * 1024); 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..0380c8c10 --- /dev/null +++ b/src/input/plugins/NfsInputPlugin.cxx @@ -0,0 +1,202 @@ +/* + * 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 "util/StringUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +extern "C" { +#include <nfsc/libnfs.h> +} + +#include <sys/stat.h> +#include <fcntl.h> + +static constexpr Domain nfs_domain("nfs"); + +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, ¤t_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..6f2c191b0 --- /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 bool +input_smbclient_init(gcc_unused const config_param ¶m, Error &error) +{ + if (!SmbclientInit(error)) + return false; + + // TODO: create one global SMBCCTX here? + + // TODO: evaluate config_param, call smbc_setOption*() + + return true; +} + +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 |